/* * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.awt.datatransfer; import java.awt.Toolkit; import java.lang.ref.SoftReference; import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader; import java.io.IOException; import java.net.URL; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import sun.awt.datatransfer.DataTransferer; /** * The SystemFlavorMap is a configurable map between "natives" (Strings), which * correspond to platform-specific data formats, and "flavors" (DataFlavors), * which correspond to platform-independent MIME types. This mapping is used * by the data transfer subsystem to transfer data between Java and native * applications, and between Java applications in separate VMs. *

* In the Sun reference implementation, the default SystemFlavorMap is * initialized by the file jre/lib/flavormap.properties and the * contents of the URL referenced by the AWT property * AWT.DnD.flavorMapFileURL. See flavormap.properties * for details. * * @since 1.2 */ public final class SystemFlavorMap implements FlavorMap, FlavorTable { /** * Constant prefix used to tag Java types converted to native platform * type. */ private static String JavaMIME = "JAVA_DATAFLAVOR:"; /** * System singleton which maps a thread's ClassLoader to a SystemFlavorMap. */ private static final WeakHashMap flavorMaps = new WeakHashMap(); /** * Copied from java.util.Properties. */ private static final String keyValueSeparators = "=: \t\r\n\f"; private static final String strictKeyValueSeparators = "=:"; private static final String whiteSpaceChars = " \t\r\n\f"; /** * The list of valid, decoded text flavor representation classes, in order * from best to worst. */ private static final String[] UNICODE_TEXT_CLASSES = { "java.io.Reader", "java.lang.String", "java.nio.CharBuffer", "\"[C\"" }; /** * The list of valid, encoded text flavor representation classes, in order * from best to worst. */ private static final String[] ENCODED_TEXT_CLASSES = { "java.io.InputStream", "java.nio.ByteBuffer", "\"[B\"" }; /** * A String representing text/plain MIME type. */ private static final String TEXT_PLAIN_BASE_TYPE = "text/plain"; /** * This constant is passed to flavorToNativeLookup() to indicate that a * a native should be synthesized, stored, and returned by encoding the * DataFlavor's MIME type in case if the DataFlavor is not found in * 'flavorToNative' map. */ private static final boolean SYNTHESIZE_IF_NOT_FOUND = true; /** * Maps native Strings to Lists of DataFlavors (or base type Strings for * text DataFlavors). * Do not use the field directly, use getNativeToFlavor() instead. */ private Map nativeToFlavor = new HashMap(); /** * Accessor to nativeToFlavor map. Since we use lazy initialization we must * use this accessor instead of direct access to the field which may not be * initialized yet. This method will initialize the field if needed. * * @return nativeToFlavor */ private Map getNativeToFlavor() { if (!isMapInitialized) { initSystemFlavorMap(); } return nativeToFlavor; } /** * Maps DataFlavors (or base type Strings for text DataFlavors) to Lists of * native Strings. * Do not use the field directly, use getFlavorToNative() instead. */ private Map flavorToNative = new HashMap(); /** * Accessor to flavorToNative map. Since we use lazy initialization we must * use this accessor instead of direct access to the field which may not be * initialized yet. This method will initialize the field if needed. * * @return flavorToNative */ private synchronized Map getFlavorToNative() { if (!isMapInitialized) { initSystemFlavorMap(); } return flavorToNative; } /** * Shows if the object has been initialized. */ private boolean isMapInitialized = false; /** * Caches the result of getNativesForFlavor(). Maps DataFlavors to * SoftReferences which reference Lists of String natives. */ private Map getNativesForFlavorCache = new HashMap(); /** * Caches the result getFlavorsForNative(). Maps String natives to * SoftReferences which reference Lists of DataFlavors. */ private Map getFlavorsForNativeCache = new HashMap(); /** * Dynamic mapping generation used for text mappings should not be applied * to the DataFlavors and String natives for which the mappings have been * explicitly specified with setFlavorsForNative() or * setNativesForFlavor(). This keeps all such keys. */ private Set disabledMappingGenerationKeys = new HashSet(); /** * Returns the default FlavorMap for this thread's ClassLoader. */ public static FlavorMap getDefaultFlavorMap() { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); if (contextClassLoader == null) { contextClassLoader = ClassLoader.getSystemClassLoader(); } FlavorMap fm; synchronized(flavorMaps) { fm = (FlavorMap)flavorMaps.get(contextClassLoader); if (fm == null) { fm = new SystemFlavorMap(); flavorMaps.put(contextClassLoader, fm); } } return fm; } private SystemFlavorMap() { } /** * Initializes a SystemFlavorMap by reading flavormap.properties and * AWT.DnD.flavorMapFileURL. * For thread-safety must be called under lock on this. */ private void initSystemFlavorMap() { if (isMapInitialized) { return; } isMapInitialized = true; BufferedReader flavormapDotProperties = java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public BufferedReader run() { String fileName = System.getProperty("java.home") + File.separator + "lib" + File.separator + "flavormap.properties"; try { return new BufferedReader (new InputStreamReader (new File(fileName).toURI().toURL().openStream(), "ISO-8859-1")); } catch (MalformedURLException e) { System.err.println("MalformedURLException:" + e + " while loading default flavormap.properties file:" + fileName); } catch (IOException e) { System.err.println("IOException:" + e + " while loading default flavormap.properties file:" + fileName); } return null; } }); BufferedReader flavormapURL = java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public BufferedReader run() { String url = Toolkit.getProperty("AWT.DnD.flavorMapFileURL", null); if (url == null) { return null; } try { return new BufferedReader (new InputStreamReader (new URL(url).openStream(), "ISO-8859-1")); } catch (MalformedURLException e) { System.err.println("MalformedURLException:" + e + " while reading AWT.DnD.flavorMapFileURL:" + url); } catch (IOException e) { System.err.println("IOException:" + e + " while reading AWT.DnD.flavorMapFileURL:" + url); } return null; } }); if (flavormapDotProperties != null) { try { parseAndStoreReader(flavormapDotProperties); } catch (IOException e) { System.err.println("IOException:" + e + " while parsing default flavormap.properties file"); } } if (flavormapURL != null) { try { parseAndStoreReader(flavormapURL); } catch (IOException e) { System.err.println("IOException:" + e + " while parsing AWT.DnD.flavorMapFileURL"); } } } /** * Copied code from java.util.Properties. Parsing the data ourselves is the * only way to handle duplicate keys and values. */ private void parseAndStoreReader(BufferedReader in) throws IOException { while (true) { // Get next line String line = in.readLine(); if (line == null) { return; } if (line.length() > 0) { // Continue lines that end in slashes if they are not comments char firstChar = line.charAt(0); if (firstChar != '#' && firstChar != '!') { while (continueLine(line)) { String nextLine = in.readLine(); if (nextLine == null) { nextLine = ""; } String loppedLine = line.substring(0, line.length() - 1); // Advance beyond whitespace on new line int startIndex = 0; for(; startIndex < nextLine.length(); startIndex++) { if (whiteSpaceChars. indexOf(nextLine.charAt(startIndex)) == -1) { break; } } nextLine = nextLine.substring(startIndex, nextLine.length()); line = loppedLine+nextLine; } // Find start of key int len = line.length(); int keyStart = 0; for(; keyStart < len; keyStart++) { if(whiteSpaceChars. indexOf(line.charAt(keyStart)) == -1) { break; } } // Blank lines are ignored if (keyStart == len) { continue; } // Find separation between key and value int separatorIndex = keyStart; for(; separatorIndex < len; separatorIndex++) { char currentChar = line.charAt(separatorIndex); if (currentChar == '\\') { separatorIndex++; } else if (keyValueSeparators. indexOf(currentChar) != -1) { break; } } // Skip over whitespace after key if any int valueIndex = separatorIndex; for (; valueIndex < len; valueIndex++) { if (whiteSpaceChars. indexOf(line.charAt(valueIndex)) == -1) { break; } } // Skip over one non whitespace key value separators if any if (valueIndex < len) { if (strictKeyValueSeparators. indexOf(line.charAt(valueIndex)) != -1) { valueIndex++; } } // Skip over white space after other separators if any while (valueIndex < len) { if (whiteSpaceChars. indexOf(line.charAt(valueIndex)) == -1) { break; } valueIndex++; } String key = line.substring(keyStart, separatorIndex); String value = (separatorIndex < len) ? line.substring(valueIndex, len) : ""; // Convert then store key and value key = loadConvert(key); value = loadConvert(value); try { MimeType mime = new MimeType(value); if ("text".equals(mime.getPrimaryType())) { String charset = mime.getParameter("charset"); if (DataTransferer.doesSubtypeSupportCharset (mime.getSubType(), charset)) { // We need to store the charset and eoln // parameters, if any, so that the // DataTransferer will have this information // for conversion into the native format. DataTransferer transferer = DataTransferer.getInstance(); if (transferer != null) { transferer.registerTextFlavorProperties (key, charset, mime.getParameter("eoln"), mime.getParameter("terminators")); } } // But don't store any of these parameters in the // DataFlavor itself for any text natives (even // non-charset ones). The SystemFlavorMap will // synthesize the appropriate mappings later. mime.removeParameter("charset"); mime.removeParameter("class"); mime.removeParameter("eoln"); mime.removeParameter("terminators"); value = mime.toString(); } } catch (MimeTypeParseException e) { e.printStackTrace(); continue; } DataFlavor flavor; try { flavor = new DataFlavor(value); } catch (Exception e) { try { flavor = new DataFlavor(value, (String)null); } catch (Exception ee) { ee.printStackTrace(); continue; } } // For text/* flavors, store mappings in separate maps to // enable dynamic mapping generation at a run-time. if ("text".equals(flavor.getPrimaryType())) { store(value, key, getFlavorToNative()); store(key, value, getNativeToFlavor()); } else { store(flavor, key, getFlavorToNative()); store(key, flavor, getNativeToFlavor()); } } } } } /** * Copied from java.util.Properties. */ private boolean continueLine (String line) { int slashCount = 0; int index = line.length() - 1; while((index >= 0) && (line.charAt(index--) == '\\')) { slashCount++; } return (slashCount % 2 == 1); } /** * Copied from java.util.Properties. */ private String loadConvert(String theString) { char aChar; int len = theString.length(); StringBuilder outBuffer = new StringBuilder(len); for (int x = 0; x < len; ) { aChar = theString.charAt(x++); if (aChar == '\\') { aChar = theString.charAt(x++); if (aChar == 'u') { // Read the xxxx int value = 0; for (int i = 0; i < 4; i++) { aChar = theString.charAt(x++); switch (aChar) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { value = (value << 4) + aChar - '0'; break; } case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': { value = (value << 4) + 10 + aChar - 'a'; break; } case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': { value = (value << 4) + 10 + aChar - 'A'; break; } default: { throw new IllegalArgumentException( "Malformed \\uxxxx encoding."); } } } outBuffer.append((char)value); } else { if (aChar == 't') { aChar = '\t'; } else if (aChar == 'r') { aChar = '\r'; } else if (aChar == 'n') { aChar = '\n'; } else if (aChar == 'f') { aChar = '\f'; } outBuffer.append(aChar); } } else { outBuffer.append(aChar); } } return outBuffer.toString(); } /** * Stores the listed object under the specified hash key in map. Unlike a * standard map, the listed object will not replace any object already at * the appropriate Map location, but rather will be appended to a List * stored in that location. */ private void store(Object hashed, Object listed, Map map) { List list = (List)map.get(hashed); if (list == null) { list = new ArrayList(1); map.put(hashed, list); } if (!list.contains(listed)) { list.add(listed); } } /** * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method * handles the case where 'nat' is not found in 'nativeToFlavor'. In that * case, a new DataFlavor is synthesized, stored, and returned, if and * only if the specified native is encoded as a Java MIME type. */ private List nativeToFlavorLookup(String nat) { List flavors = (List)getNativeToFlavor().get(nat); if (nat != null && !disabledMappingGenerationKeys.contains(nat)) { DataTransferer transferer = DataTransferer.getInstance(); if (transferer != null) { List platformFlavors = transferer.getPlatformMappingsForNative(nat); if (!platformFlavors.isEmpty()) { if (flavors != null) { platformFlavors.removeAll(new HashSet(flavors)); // Prepending the platform-specific mappings ensures // that the flavors added with // addFlavorForUnencodedNative() are at the end of // list. platformFlavors.addAll(flavors); } flavors = platformFlavors; } } } if (flavors == null && isJavaMIMEType(nat)) { String decoded = decodeJavaMIMEType(nat); DataFlavor flavor = null; try { flavor = new DataFlavor(decoded); } catch (Exception e) { System.err.println("Exception \"" + e.getClass().getName() + ": " + e.getMessage() + "\"while constructing DataFlavor for: " + decoded); } if (flavor != null) { flavors = new ArrayList(1); getNativeToFlavor().put(nat, flavors); flavors.add(flavor); getFlavorsForNativeCache.remove(nat); getFlavorsForNativeCache.remove(null); List natives = (List)getFlavorToNative().get(flavor); if (natives == null) { natives = new ArrayList(1); getFlavorToNative().put(flavor, natives); } natives.add(nat); getNativesForFlavorCache.remove(flavor); getNativesForFlavorCache.remove(null); } } return (flavors != null) ? flavors : new ArrayList(0); } /** * Semantically equivalent to 'flavorToNative.get(flav)'. This method * handles the case where 'flav' is not found in 'flavorToNative' depending * on the value of passes 'synthesize' parameter. If 'synthesize' is * SYNTHESIZE_IF_NOT_FOUND a native is synthesized, stored, and returned by * encoding the DataFlavor's MIME type. Otherwise an empty List is returned * and 'flavorToNative' remains unaffected. */ private List flavorToNativeLookup(final DataFlavor flav, final boolean synthesize) { List natives = (List)getFlavorToNative().get(flav); if (flav != null && !disabledMappingGenerationKeys.contains(flav)) { DataTransferer transferer = DataTransferer.getInstance(); if (transferer != null) { List platformNatives = transferer.getPlatformMappingsForFlavor(flav); if (!platformNatives.isEmpty()) { if (natives != null) { platformNatives.removeAll(new HashSet(natives)); // Prepend the platform-specific mappings to ensure // that the natives added with // addUnencodedNativeForFlavor() are at the end of // list. platformNatives.addAll(natives); } natives = platformNatives; } } } if (natives == null) { if (synthesize) { String encoded = encodeDataFlavor(flav); natives = new ArrayList(1); getFlavorToNative().put(flav, natives); natives.add(encoded); getNativesForFlavorCache.remove(flav); getNativesForFlavorCache.remove(null); List flavors = (List)getNativeToFlavor().get(encoded); if (flavors == null) { flavors = new ArrayList(1); getNativeToFlavor().put(encoded, flavors); } flavors.add(flav); getFlavorsForNativeCache.remove(encoded); getFlavorsForNativeCache.remove(null); } else { natives = new ArrayList(0); } } return natives; } /** * Returns a List of String natives to which the * specified DataFlavor can be translated by the data transfer * subsystem. The List will be sorted from best native to * worst. That is, the first native will best reflect data in the specified * flavor to the underlying native platform. *

* If the specified DataFlavor is previously unknown to the * data transfer subsystem and the data transfer subsystem is unable to * translate this DataFlavor to any existing native, then * invoking this method will establish a * mapping in both directions between the specified DataFlavor * and an encoded version of its MIME type as its native. * * @param flav the DataFlavor whose corresponding natives * should be returned. If null is specified, all * natives currently known to the data transfer subsystem are * returned in a non-deterministic order. * @return a java.util.List of java.lang.String * objects which are platform-specific representations of platform- * specific data formats * * @see #encodeDataFlavor * @since 1.4 */ public synchronized List getNativesForFlavor(DataFlavor flav) { List retval = null; // Check cache, even for null flav SoftReference ref = (SoftReference)getNativesForFlavorCache.get(flav); if (ref != null) { retval = (List)ref.get(); if (retval != null) { // Create a copy, because client code can modify the returned // list. return new ArrayList(retval); } } if (flav == null) { retval = new ArrayList(getNativeToFlavor().keySet()); } else if (disabledMappingGenerationKeys.contains(flav)) { // In this case we shouldn't synthesize a native for this flavor, // since its mappings were explicitly specified. retval = flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND); } else if (DataTransferer.isFlavorCharsetTextType(flav)) { // For text/* flavors, flavor-to-native mappings specified in // flavormap.properties are stored per flavor's base type. if ("text".equals(flav.getPrimaryType())) { retval = (List)getFlavorToNative().get(flav.mimeType.getBaseType()); if (retval != null) { // To prevent the List stored in the map from modification. retval = new ArrayList(retval); } } // Also include text/plain natives, but don't duplicate Strings List textPlainList = (List)getFlavorToNative().get(TEXT_PLAIN_BASE_TYPE); if (textPlainList != null && !textPlainList.isEmpty()) { // To prevent the List stored in the map from modification. // This also guarantees that removeAll() is supported. textPlainList = new ArrayList(textPlainList); if (retval != null && !retval.isEmpty()) { // Use HashSet to get constant-time performance for search. textPlainList.removeAll(new HashSet(retval)); retval.addAll(textPlainList); } else { retval = textPlainList; } } if (retval == null || retval.isEmpty()) { retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND); } else { // In this branch it is guaranteed that natives explicitly // listed for flav's MIME type were added with // addUnencodedNativeForFlavor(), so they have lower priority. List explicitList = flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND); // flavorToNativeLookup() never returns null. // It can return an empty List, however. if (!explicitList.isEmpty()) { // To prevent the List stored in the map from modification. // This also guarantees that removeAll() is supported. explicitList = new ArrayList(explicitList); // Use HashSet to get constant-time performance for search. explicitList.removeAll(new HashSet(retval)); retval.addAll(explicitList); } } } else if (DataTransferer.isFlavorNoncharsetTextType(flav)) { retval = (List)getFlavorToNative().get(flav.mimeType.getBaseType()); if (retval == null || retval.isEmpty()) { retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND); } else { // In this branch it is guaranteed that natives explicitly // listed for flav's MIME type were added with // addUnencodedNativeForFlavor(), so they have lower priority. List explicitList = flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND); // flavorToNativeLookup() never returns null. // It can return an empty List, however. if (!explicitList.isEmpty()) { // To prevent the List stored in the map from modification. // This also guarantees that add/removeAll() are supported. retval = new ArrayList(retval); explicitList = new ArrayList(explicitList); // Use HashSet to get constant-time performance for search. explicitList.removeAll(new HashSet(retval)); retval.addAll(explicitList); } } } else { retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND); } getNativesForFlavorCache.put(flav, new SoftReference(retval)); // Create a copy, because client code can modify the returned list. return new ArrayList(retval); } /** * Returns a List of DataFlavors to which the * specified String native can be translated by the data * transfer subsystem. The List will be sorted from best * DataFlavor to worst. That is, the first * DataFlavor will best reflect data in the specified * native to a Java application. *

* If the specified native is previously unknown to the data transfer * subsystem, and that native has been properly encoded, then invoking this * method will establish a mapping in both directions between the specified * native and a DataFlavor whose MIME type is a decoded * version of the native. *

* If the specified native is not a properly encoded native and the * mappings for this native have not been altered with * setFlavorsForNative, then the contents of the * List is platform dependent, but null * cannot be returned. * * @param nat the native whose corresponding DataFlavors * should be returned. If null is specified, all * DataFlavors currently known to the data transfer * subsystem are returned in a non-deterministic order. * @return a java.util.List of DataFlavor * objects into which platform-specific data in the specified, * platform-specific native can be translated * * @see #encodeJavaMIMEType * @since 1.4 */ public synchronized List getFlavorsForNative(String nat) { // Check cache, even for null nat SoftReference ref = (SoftReference)getFlavorsForNativeCache.get(nat); if (ref != null) { ArrayList retval = (ArrayList)ref.get(); if (retval != null) { return (List)retval.clone(); } } LinkedList retval = new LinkedList(); if (nat == null) { List natives = getNativesForFlavor(null); HashSet dups = new HashSet(natives.size()); for (Iterator natives_iter = natives.iterator(); natives_iter.hasNext(); ) { List flavors = getFlavorsForNative((String)natives_iter.next()); for (Iterator flavors_iter = flavors.iterator(); flavors_iter.hasNext(); ) { Object flavor = flavors_iter.next(); if (dups.add(flavor)) { retval.add(flavor); } } } } else { List flavors = nativeToFlavorLookup(nat); if (disabledMappingGenerationKeys.contains(nat)) { return flavors; } HashSet dups = new HashSet(flavors.size()); List flavorsAndbaseTypes = nativeToFlavorLookup(nat); for (Iterator flavorsAndbaseTypes_iter = flavorsAndbaseTypes.iterator(); flavorsAndbaseTypes_iter.hasNext(); ) { Object value = flavorsAndbaseTypes_iter.next(); if (value instanceof String) { String baseType = (String)value; String subType = null; try { MimeType mimeType = new MimeType(baseType); subType = mimeType.getSubType(); } catch (MimeTypeParseException mtpe) { // Cannot happen, since we checked all mappings // on load from flavormap.properties. assert(false); } if (DataTransferer.doesSubtypeSupportCharset(subType, null)) { if (TEXT_PLAIN_BASE_TYPE.equals(baseType) && dups.add(DataFlavor.stringFlavor)) { retval.add(DataFlavor.stringFlavor); } for (int i = 0; i < UNICODE_TEXT_CLASSES.length; i++) { DataFlavor toAdd = null; try { toAdd = new DataFlavor (baseType + ";charset=Unicode;class=" + UNICODE_TEXT_CLASSES[i]); } catch (ClassNotFoundException cannotHappen) { } if (dups.add(toAdd)) { retval.add(toAdd); } } for (Iterator charset_iter = DataTransferer.standardEncodings(); charset_iter.hasNext(); ) { String charset = (String)charset_iter.next(); for (int i = 0; i < ENCODED_TEXT_CLASSES.length; i++) { DataFlavor toAdd = null; try { toAdd = new DataFlavor (baseType + ";charset=" + charset + ";class=" + ENCODED_TEXT_CLASSES[i]); } catch (ClassNotFoundException cannotHappen) { } // Check for equality to plainTextFlavor so // that we can ensure that the exact charset of // plainTextFlavor, not the canonical charset // or another equivalent charset with a // different name, is used. if (toAdd.equals(DataFlavor.plainTextFlavor)) { toAdd = DataFlavor.plainTextFlavor; } if (dups.add(toAdd)) { retval.add(toAdd); } } } if (TEXT_PLAIN_BASE_TYPE.equals(baseType) && dups.add(DataFlavor.plainTextFlavor)) { retval.add(DataFlavor.plainTextFlavor); } } else { // Non-charset text natives should be treated as // opaque, 8-bit data in any of its various // representations. for (int i = 0; i < ENCODED_TEXT_CLASSES.length; i++) { DataFlavor toAdd = null; try { toAdd = new DataFlavor(baseType + ";class=" + ENCODED_TEXT_CLASSES[i]); } catch (ClassNotFoundException cannotHappen) { } if (dups.add(toAdd)) { retval.add(toAdd); } } } } else { DataFlavor flavor = (DataFlavor)value; if (dups.add(flavor)) { retval.add(flavor); } } } } ArrayList arrayList = new ArrayList(retval); getFlavorsForNativeCache.put(nat, new SoftReference(arrayList)); return (List)arrayList.clone(); } /** * Returns a Map of the specified DataFlavors to * their most preferred String native. Each native value will * be the same as the first native in the List returned by * getNativesForFlavor for the specified flavor. *

* If a specified DataFlavor is previously unknown to the * data transfer subsystem, then invoking this method will establish a * mapping in both directions between the specified DataFlavor * and an encoded version of its MIME type as its native. * * @param flavors an array of DataFlavors which will be the * key set of the returned Map. If null is * specified, a mapping of all DataFlavors known to the * data transfer subsystem to their most preferred * String natives will be returned. * @return a java.util.Map of DataFlavors to * String natives * * @see #getNativesForFlavor * @see #encodeDataFlavor */ public synchronized Map getNativesForFlavors(DataFlavor[] flavors) { // Use getNativesForFlavor to generate extra natives for text flavors // and stringFlavor if (flavors == null) { List flavor_list = getFlavorsForNative(null); flavors = new DataFlavor[flavor_list.size()]; flavor_list.toArray(flavors); } HashMap retval = new HashMap(flavors.length, 1.0f); for (int i = 0; i < flavors.length; i++) { List natives = getNativesForFlavor(flavors[i]); String nat = (natives.isEmpty()) ? null : (String)natives.get(0); retval.put(flavors[i], nat); } return retval; } /** * Returns a Map of the specified String natives * to their most preferred DataFlavor. Each * DataFlavor value will be the same as the first * DataFlavor in the List returned by * getFlavorsForNative for the specified native. *

* If a specified native is previously unknown to the data transfer * subsystem, and that native has been properly encoded, then invoking this * method will establish a mapping in both directions between the specified * native and a DataFlavor whose MIME type is a decoded * version of the native. * * @param natives an array of Strings which will be the * key set of the returned Map. If null is * specified, a mapping of all supported String natives * to their most preferred DataFlavors will be * returned. * @return a java.util.Map of String natives to * DataFlavors * * @see #getFlavorsForNative * @see #encodeJavaMIMEType */ public synchronized Map getFlavorsForNatives(String[] natives) { // Use getFlavorsForNative to generate extra flavors for text natives if (natives == null) { List native_list = getNativesForFlavor(null); natives = new String[native_list.size()]; native_list.toArray(natives); } HashMap retval = new HashMap(natives.length, 1.0f); for (int i = 0; i < natives.length; i++) { List flavors = getFlavorsForNative(natives[i]); DataFlavor flav = (flavors.isEmpty()) ? null : (DataFlavor)flavors.get(0); retval.put(natives[i], flav); } return retval; } /** * Adds a mapping from the specified DataFlavor (and all * DataFlavors equal to the specified DataFlavor) * to the specified String native. * Unlike getNativesForFlavor, the mapping will only be * established in one direction, and the native will not be encoded. To * establish a two-way mapping, call * addFlavorForUnencodedNative as well. The new mapping will * be of lower priority than any existing mapping. * This method has no effect if a mapping from the specified or equal * DataFlavor to the specified String native * already exists. * * @param flav the DataFlavor key for the mapping * @param nat the String native value for the mapping * @throws NullPointerException if flav or nat is null * * @see #addFlavorForUnencodedNative * @since 1.4 */ public synchronized void addUnencodedNativeForFlavor(DataFlavor flav, String nat) { if (flav == null || nat == null) { throw new NullPointerException("null arguments not permitted"); } List natives = (List)getFlavorToNative().get(flav); if (natives == null) { natives = new ArrayList(1); getFlavorToNative().put(flav, natives); } else if (natives.contains(nat)) { return; } natives.add(nat); getNativesForFlavorCache.remove(flav); getNativesForFlavorCache.remove(null); } /** * Discards the current mappings for the specified DataFlavor * and all DataFlavors equal to the specified * DataFlavor, and creates new mappings to the * specified String natives. * Unlike getNativesForFlavor, the mappings will only be * established in one direction, and the natives will not be encoded. To * establish two-way mappings, call setFlavorsForNative * as well. The first native in the array will represent the highest * priority mapping. Subsequent natives will represent mappings of * decreasing priority. *

* If the array contains several elements that reference equal * String natives, this method will establish new mappings * for the first of those elements and ignore the rest of them. *

* It is recommended that client code not reset mappings established by the * data transfer subsystem. This method should only be used for * application-level mappings. * * @param flav the DataFlavor key for the mappings * @param natives the String native values for the mappings * @throws NullPointerException if flav or natives is null * or if natives contains null elements * * @see #setFlavorsForNative * @since 1.4 */ public synchronized void setNativesForFlavor(DataFlavor flav, String[] natives) { if (flav == null || natives == null) { throw new NullPointerException("null arguments not permitted"); } getFlavorToNative().remove(flav); for (int i = 0; i < natives.length; i++) { addUnencodedNativeForFlavor(flav, natives[i]); } disabledMappingGenerationKeys.add(flav); // Clear the cache to handle the case of empty natives. getNativesForFlavorCache.remove(flav); getNativesForFlavorCache.remove(null); } /** * Adds a mapping from a single String native to a single * DataFlavor. Unlike getFlavorsForNative, the * mapping will only be established in one direction, and the native will * not be encoded. To establish a two-way mapping, call * addUnencodedNativeForFlavor as well. The new mapping will * be of lower priority than any existing mapping. * This method has no effect if a mapping from the specified * String native to the specified or equal * DataFlavor already exists. * * @param nat the String native key for the mapping * @param flav the DataFlavor value for the mapping * @throws NullPointerException if nat or flav is null * * @see #addUnencodedNativeForFlavor * @since 1.4 */ public synchronized void addFlavorForUnencodedNative(String nat, DataFlavor flav) { if (nat == null || flav == null) { throw new NullPointerException("null arguments not permitted"); } List flavors = (List)getNativeToFlavor().get(nat); if (flavors == null) { flavors = new ArrayList(1); getNativeToFlavor().put(nat, flavors); } else if (flavors.contains(flav)) { return; } flavors.add(flav); getFlavorsForNativeCache.remove(nat); getFlavorsForNativeCache.remove(null); } /** * Discards the current mappings for the specified String * native, and creates new mappings to the specified * DataFlavors. Unlike getFlavorsForNative, the * mappings will only be established in one direction, and the natives need * not be encoded. To establish two-way mappings, call * setNativesForFlavor as well. The first * DataFlavor in the array will represent the highest priority * mapping. Subsequent DataFlavors will represent mappings of * decreasing priority. *

* If the array contains several elements that reference equal * DataFlavors, this method will establish new mappings * for the first of those elements and ignore the rest of them. *

* It is recommended that client code not reset mappings established by the * data transfer subsystem. This method should only be used for * application-level mappings. * * @param nat the String native key for the mappings * @param flavors the DataFlavor values for the mappings * @throws NullPointerException if nat or flavors is null * or if flavors contains null elements * * @see #setNativesForFlavor * @since 1.4 */ public synchronized void setFlavorsForNative(String nat, DataFlavor[] flavors) { if (nat == null || flavors == null) { throw new NullPointerException("null arguments not permitted"); } getNativeToFlavor().remove(nat); for (int i = 0; i < flavors.length; i++) { addFlavorForUnencodedNative(nat, flavors[i]); } disabledMappingGenerationKeys.add(nat); // Clear the cache to handle the case of empty flavors. getFlavorsForNativeCache.remove(nat); getFlavorsForNativeCache.remove(null); } /** * Encodes a MIME type for use as a String native. The format * of an encoded representation of a MIME type is implementation-dependent. * The only restrictions are: *

*

* Sun's reference implementation of this method returns the specified MIME * type String prefixed with JAVA_DATAFLAVOR:. * * @param mimeType the MIME type to encode * @return the encoded String, or null if * mimeType is null */ public static String encodeJavaMIMEType(String mimeType) { return (mimeType != null) ? JavaMIME + mimeType : null; } /** * Encodes a DataFlavor for use as a String * native. The format of an encoded DataFlavor is * implementation-dependent. The only restrictions are: *

*

* Sun's reference implementation of this method returns the MIME type * String of the specified DataFlavor prefixed * with JAVA_DATAFLAVOR:. * * @param flav the DataFlavor to encode * @return the encoded String, or null if * flav is null or has a null MIME type */ public static String encodeDataFlavor(DataFlavor flav) { return (flav != null) ? SystemFlavorMap.encodeJavaMIMEType(flav.getMimeType()) : null; } /** * Returns whether the specified String is an encoded Java * MIME type. * * @param str the String to test * @return true if the String is encoded; * false otherwise */ public static boolean isJavaMIMEType(String str) { return (str != null && str.startsWith(JavaMIME, 0)); } /** * Decodes a String native for use as a Java MIME type. * * @param nat the String to decode * @return the decoded Java MIME type, or null if nat is not * an encoded String native */ public static String decodeJavaMIMEType(String nat) { return (isJavaMIMEType(nat)) ? nat.substring(JavaMIME.length(), nat.length()).trim() : null; } /** * Decodes a String native for use as a * DataFlavor. * * @param nat the String to decode * @return the decoded DataFlavor, or null if * nat is not an encoded String native */ public static DataFlavor decodeDataFlavor(String nat) throws ClassNotFoundException { String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat); return (retval_str != null) ? new DataFlavor(retval_str) : null; } }