| /* |
| * 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. |
| * <p> |
| * In the Sun reference implementation, the default SystemFlavorMap is |
| * initialized by the file <code>jre/lib/flavormap.properties</code> and the |
| * contents of the URL referenced by the AWT property |
| * <code>AWT.DnD.flavorMapFileURL</code>. See <code>flavormap.properties</code> |
| * 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<BufferedReader>() { |
| 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<BufferedReader>() { |
| 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 <code>List</code> of <code>String</code> natives to which the |
| * specified <code>DataFlavor</code> can be translated by the data transfer |
| * subsystem. The <code>List</code> 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. |
| * <p> |
| * If the specified <code>DataFlavor</code> is previously unknown to the |
| * data transfer subsystem and the data transfer subsystem is unable to |
| * translate this <code>DataFlavor</code> to any existing native, then |
| * invoking this method will establish a |
| * mapping in both directions between the specified <code>DataFlavor</code> |
| * and an encoded version of its MIME type as its native. |
| * |
| * @param flav the <code>DataFlavor</code> whose corresponding natives |
| * should be returned. If <code>null</code> is specified, all |
| * natives currently known to the data transfer subsystem are |
| * returned in a non-deterministic order. |
| * @return a <code>java.util.List</code> of <code>java.lang.String</code> |
| * objects which are platform-specific representations of platform- |
| * specific data formats |
| * |
| * @see #encodeDataFlavor |
| * @since 1.4 |
| */ |
| public synchronized List<String> 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 <code>List</code> of <code>DataFlavor</code>s to which the |
| * specified <code>String</code> native can be translated by the data |
| * transfer subsystem. The <code>List</code> will be sorted from best |
| * <code>DataFlavor</code> to worst. That is, the first |
| * <code>DataFlavor</code> will best reflect data in the specified |
| * native to a Java application. |
| * <p> |
| * 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 <code>DataFlavor</code> whose MIME type is a decoded |
| * version of the native. |
| * <p> |
| * If the specified native is not a properly encoded native and the |
| * mappings for this native have not been altered with |
| * <code>setFlavorsForNative</code>, then the contents of the |
| * <code>List</code> is platform dependent, but <code>null</code> |
| * cannot be returned. |
| * |
| * @param nat the native whose corresponding <code>DataFlavor</code>s |
| * should be returned. If <code>null</code> is specified, all |
| * <code>DataFlavor</code>s currently known to the data transfer |
| * subsystem are returned in a non-deterministic order. |
| * @return a <code>java.util.List</code> of <code>DataFlavor</code> |
| * objects into which platform-specific data in the specified, |
| * platform-specific native can be translated |
| * |
| * @see #encodeJavaMIMEType |
| * @since 1.4 |
| */ |
| public synchronized List<DataFlavor> 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 <code>Map</code> of the specified <code>DataFlavor</code>s to |
| * their most preferred <code>String</code> native. Each native value will |
| * be the same as the first native in the List returned by |
| * <code>getNativesForFlavor</code> for the specified flavor. |
| * <p> |
| * If a specified <code>DataFlavor</code> is previously unknown to the |
| * data transfer subsystem, then invoking this method will establish a |
| * mapping in both directions between the specified <code>DataFlavor</code> |
| * and an encoded version of its MIME type as its native. |
| * |
| * @param flavors an array of <code>DataFlavor</code>s which will be the |
| * key set of the returned <code>Map</code>. If <code>null</code> is |
| * specified, a mapping of all <code>DataFlavor</code>s known to the |
| * data transfer subsystem to their most preferred |
| * <code>String</code> natives will be returned. |
| * @return a <code>java.util.Map</code> of <code>DataFlavor</code>s to |
| * <code>String</code> natives |
| * |
| * @see #getNativesForFlavor |
| * @see #encodeDataFlavor |
| */ |
| public synchronized Map<DataFlavor,String> |
| 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 <code>Map</code> of the specified <code>String</code> natives |
| * to their most preferred <code>DataFlavor</code>. Each |
| * <code>DataFlavor</code> value will be the same as the first |
| * <code>DataFlavor</code> in the List returned by |
| * <code>getFlavorsForNative</code> for the specified native. |
| * <p> |
| * 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 <code>DataFlavor</code> whose MIME type is a decoded |
| * version of the native. |
| * |
| * @param natives an array of <code>String</code>s which will be the |
| * key set of the returned <code>Map</code>. If <code>null</code> is |
| * specified, a mapping of all supported <code>String</code> natives |
| * to their most preferred <code>DataFlavor</code>s will be |
| * returned. |
| * @return a <code>java.util.Map</code> of <code>String</code> natives to |
| * <code>DataFlavor</code>s |
| * |
| * @see #getFlavorsForNative |
| * @see #encodeJavaMIMEType |
| */ |
| public synchronized Map<String,DataFlavor> |
| 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 <code>DataFlavor</code> (and all |
| * <code>DataFlavor</code>s equal to the specified <code>DataFlavor</code>) |
| * to the specified <code>String</code> native. |
| * Unlike <code>getNativesForFlavor</code>, the mapping will only be |
| * established in one direction, and the native will not be encoded. To |
| * establish a two-way mapping, call |
| * <code>addFlavorForUnencodedNative</code> 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 |
| * <code>DataFlavor</code> to the specified <code>String</code> native |
| * already exists. |
| * |
| * @param flav the <code>DataFlavor</code> key for the mapping |
| * @param nat the <code>String</code> native value for the mapping |
| * @throws NullPointerException if flav or nat is <code>null</code> |
| * |
| * @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 <code>DataFlavor</code> |
| * and all <code>DataFlavor</code>s equal to the specified |
| * <code>DataFlavor</code>, and creates new mappings to the |
| * specified <code>String</code> natives. |
| * Unlike <code>getNativesForFlavor</code>, the mappings will only be |
| * established in one direction, and the natives will not be encoded. To |
| * establish two-way mappings, call <code>setFlavorsForNative</code> |
| * as well. The first native in the array will represent the highest |
| * priority mapping. Subsequent natives will represent mappings of |
| * decreasing priority. |
| * <p> |
| * If the array contains several elements that reference equal |
| * <code>String</code> natives, this method will establish new mappings |
| * for the first of those elements and ignore the rest of them. |
| * <p> |
| * 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 <code>DataFlavor</code> key for the mappings |
| * @param natives the <code>String</code> native values for the mappings |
| * @throws NullPointerException if flav or natives is <code>null</code> |
| * or if natives contains <code>null</code> 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 <code>String</code> native to a single |
| * <code>DataFlavor</code>. Unlike <code>getFlavorsForNative</code>, the |
| * mapping will only be established in one direction, and the native will |
| * not be encoded. To establish a two-way mapping, call |
| * <code>addUnencodedNativeForFlavor</code> 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 |
| * <code>String</code> native to the specified or equal |
| * <code>DataFlavor</code> already exists. |
| * |
| * @param nat the <code>String</code> native key for the mapping |
| * @param flav the <code>DataFlavor</code> value for the mapping |
| * @throws NullPointerException if nat or flav is <code>null</code> |
| * |
| * @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 <code>String</code> |
| * native, and creates new mappings to the specified |
| * <code>DataFlavor</code>s. Unlike <code>getFlavorsForNative</code>, the |
| * mappings will only be established in one direction, and the natives need |
| * not be encoded. To establish two-way mappings, call |
| * <code>setNativesForFlavor</code> as well. The first |
| * <code>DataFlavor</code> in the array will represent the highest priority |
| * mapping. Subsequent <code>DataFlavor</code>s will represent mappings of |
| * decreasing priority. |
| * <p> |
| * If the array contains several elements that reference equal |
| * <code>DataFlavor</code>s, this method will establish new mappings |
| * for the first of those elements and ignore the rest of them. |
| * <p> |
| * 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 <code>String</code> native key for the mappings |
| * @param flavors the <code>DataFlavor</code> values for the mappings |
| * @throws NullPointerException if nat or flavors is <code>null</code> |
| * or if flavors contains <code>null</code> 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 <code>String</code> native. The format |
| * of an encoded representation of a MIME type is implementation-dependent. |
| * The only restrictions are: |
| * <ul> |
| * <li>The encoded representation is <code>null</code> if and only if the |
| * MIME type <code>String</code> is <code>null</code>.</li> |
| * <li>The encoded representations for two non-<code>null</code> MIME type |
| * <code>String</code>s are equal if and only if these <code>String</code>s |
| * are equal according to <code>String.equals(Object)</code>.</li> |
| * </ul> |
| * <p> |
| * Sun's reference implementation of this method returns the specified MIME |
| * type <code>String</code> prefixed with <code>JAVA_DATAFLAVOR:</code>. |
| * |
| * @param mimeType the MIME type to encode |
| * @return the encoded <code>String</code>, or <code>null</code> if |
| * mimeType is <code>null</code> |
| */ |
| public static String encodeJavaMIMEType(String mimeType) { |
| return (mimeType != null) |
| ? JavaMIME + mimeType |
| : null; |
| } |
| |
| /** |
| * Encodes a <code>DataFlavor</code> for use as a <code>String</code> |
| * native. The format of an encoded <code>DataFlavor</code> is |
| * implementation-dependent. The only restrictions are: |
| * <ul> |
| * <li>The encoded representation is <code>null</code> if and only if the |
| * specified <code>DataFlavor</code> is <code>null</code> or its MIME type |
| * <code>String</code> is <code>null</code>.</li> |
| * <li>The encoded representations for two non-<code>null</code> |
| * <code>DataFlavor</code>s with non-<code>null</code> MIME type |
| * <code>String</code>s are equal if and only if the MIME type |
| * <code>String</code>s of these <code>DataFlavor</code>s are equal |
| * according to <code>String.equals(Object)</code>.</li> |
| * </ul> |
| * <p> |
| * Sun's reference implementation of this method returns the MIME type |
| * <code>String</code> of the specified <code>DataFlavor</code> prefixed |
| * with <code>JAVA_DATAFLAVOR:</code>. |
| * |
| * @param flav the <code>DataFlavor</code> to encode |
| * @return the encoded <code>String</code>, or <code>null</code> if |
| * flav is <code>null</code> or has a <code>null</code> MIME type |
| */ |
| public static String encodeDataFlavor(DataFlavor flav) { |
| return (flav != null) |
| ? SystemFlavorMap.encodeJavaMIMEType(flav.getMimeType()) |
| : null; |
| } |
| |
| /** |
| * Returns whether the specified <code>String</code> is an encoded Java |
| * MIME type. |
| * |
| * @param str the <code>String</code> to test |
| * @return <code>true</code> if the <code>String</code> is encoded; |
| * <code>false</code> otherwise |
| */ |
| public static boolean isJavaMIMEType(String str) { |
| return (str != null && str.startsWith(JavaMIME, 0)); |
| } |
| |
| /** |
| * Decodes a <code>String</code> native for use as a Java MIME type. |
| * |
| * @param nat the <code>String</code> to decode |
| * @return the decoded Java MIME type, or <code>null</code> if nat is not |
| * an encoded <code>String</code> native |
| */ |
| public static String decodeJavaMIMEType(String nat) { |
| return (isJavaMIMEType(nat)) |
| ? nat.substring(JavaMIME.length(), nat.length()).trim() |
| : null; |
| } |
| |
| /** |
| * Decodes a <code>String</code> native for use as a |
| * <code>DataFlavor</code>. |
| * |
| * @param nat the <code>String</code> to decode |
| * @return the decoded <code>DataFlavor</code>, or <code>null</code> if |
| * nat is not an encoded <code>String</code> native |
| */ |
| public static DataFlavor decodeDataFlavor(String nat) |
| throws ClassNotFoundException |
| { |
| String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat); |
| return (retval_str != null) |
| ? new DataFlavor(retval_str) |
| : null; |
| } |
| } |