/* * Copyright (c) 1997, 2014, 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 sun.datatransfer.DataFlavorUtil; import sun.datatransfer.DesktopDatatransferService; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; /** * 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. * * @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:"; private static final Object FLAVOR_MAP_KEY = new Object(); /** * 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"; /** * A String representing text/html MIME type. */ private static final String HTML_TEXT_BASE_TYPE = "text/html"; /** * Maps native Strings to Lists of DataFlavors (or base type Strings for * text DataFlavors). * Do not use the field directly, use getNativeToFlavor() instead. */ private final 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 final 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; } /** * Maps a text DataFlavor primary mime-type to the native. Used only to store * standard mappings registered in the flavormap.properties * Do not use this field directly, use getTextTypeToNative() instead. */ private Map> textTypeToNative = new HashMap<>(); /** * Shows if the object has been initialized. */ private boolean isMapInitialized = false; /** * An accessor to textTypeToNative 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 textTypeToNative */ private synchronized Map> getTextTypeToNative() { if (!isMapInitialized) { initSystemFlavorMap(); // From this point the map should not be modified textTypeToNative = Collections.unmodifiableMap(textTypeToNative); } return textTypeToNative; } /** * Caches the result of getNativesForFlavor(). Maps DataFlavors to * SoftReferences which reference LinkedHashSet of String natives. */ private final SoftCache nativesForFlavorCache = new SoftCache<>(); /** * Caches the result getFlavorsForNative(). Maps String natives to * SoftReferences which reference LinkedHashSet of DataFlavors. */ private final SoftCache flavorsForNativeCache = new SoftCache<>(); /** * 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. * * @return the default FlavorMap for this thread's ClassLoader */ public static FlavorMap getDefaultFlavorMap() { return DataFlavorUtil.getDesktopService().getFlavorMap(SystemFlavorMap::new); } private SystemFlavorMap() { } /** * Initializes a SystemFlavorMap by reading flavormap.properties * For thread-safety must be called under lock on this. */ private void initSystemFlavorMap() { if (isMapInitialized) { return; } isMapInitialized = true; InputStream is = SystemFlavorMap.class.getResourceAsStream("/sun/datatransfer/resources/flavormap.properties"); if (is == null) { throw new InternalError("Default flavor mapping not found"); } try (InputStreamReader isr = new InputStreamReader(is); BufferedReader reader = new BufferedReader(isr)) { String line; while ((line = reader.readLine()) != null) { line = line.trim(); if (line.startsWith("#") || line.isEmpty()) continue; while (line.endsWith("\\")) { line = line.substring(0, line.length() - 1) + reader.readLine().trim(); } int delimiterPosition = line.indexOf('='); String key = line.substring(0, delimiterPosition).replaceAll("\\ ", " "); String[] values = line.substring(delimiterPosition + 1, line.length()).split(","); for (String value : values) { try { value = loadConvert(value); MimeType mime = new MimeType(value); if ("text".equals(mime.getPrimaryType())) { String charset = mime.getParameter("charset"); if (DataFlavorUtil.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. DesktopDatatransferService desktopService = DataFlavorUtil.getDesktopService(); if (desktopService.isDesktopPresent()) { desktopService.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, null); } catch (Exception ee) { ee.printStackTrace(); continue; } } final LinkedHashSet dfs = new LinkedHashSet<>(); dfs.add(flavor); if ("text".equals(flavor.getPrimaryType())) { dfs.addAll(convertMimeTypeToDataFlavors(value)); store(flavor.mimeType.getBaseType(), key, getTextTypeToNative()); } for (DataFlavor df : dfs) { store(df, key, getFlavorToNative()); store(key, df, getNativeToFlavor()); } } } } catch (IOException e) { throw new InternalError("Error reading default flavor mapping", e); } } // Copied from java.util.Properties private static 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(H hashed, L listed, Map> map) { LinkedHashSet list = map.get(hashed); if (list == null) { list = new LinkedHashSet<>(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 LinkedHashSet nativeToFlavorLookup(String nat) { LinkedHashSet flavors = getNativeToFlavor().get(nat); if (nat != null && !disabledMappingGenerationKeys.contains(nat)) { DesktopDatatransferService desktopService = DataFlavorUtil.getDesktopService(); if (desktopService.isDesktopPresent()) { LinkedHashSet platformFlavors = desktopService.getPlatformMappingsForNative(nat); if (!platformFlavors.isEmpty()) { if (flavors != null) { // 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 LinkedHashSet<>(1); getNativeToFlavor().put(nat, flavors); flavors.add(flavor); flavorsForNativeCache.remove(nat); LinkedHashSet natives = getFlavorToNative().get(flavor); if (natives == null) { natives = new LinkedHashSet<>(1); getFlavorToNative().put(flavor, natives); } natives.add(nat); nativesForFlavorCache.remove(flavor); } } return (flavors != null) ? flavors : new LinkedHashSet<>(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 LinkedHashSet flavorToNativeLookup(final DataFlavor flav, final boolean synthesize) { LinkedHashSet natives = getFlavorToNative().get(flav); if (flav != null && !disabledMappingGenerationKeys.contains(flav)) { DesktopDatatransferService desktopService = DataFlavorUtil.getDesktopService(); if (desktopService.isDesktopPresent()) { LinkedHashSet platformNatives = desktopService.getPlatformMappingsForFlavor(flav); if (!platformNatives.isEmpty()) { if (natives != null) { // 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 LinkedHashSet<>(1); getFlavorToNative().put(flav, natives); natives.add(encoded); LinkedHashSet flavors = getNativeToFlavor().get(encoded); if (flavors == null) { flavors = new LinkedHashSet<>(1); getNativeToFlavor().put(encoded, flavors); } flavors.add(flav); nativesForFlavorCache.remove(flav); flavorsForNativeCache.remove(encoded); } else { natives = new LinkedHashSet<>(0); } } return new LinkedHashSet<>(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 */ @Override public synchronized List getNativesForFlavor(DataFlavor flav) { LinkedHashSet retval = nativesForFlavorCache.check(flav); if (retval != null) { return new ArrayList<>(retval); } if (flav == null) { retval = new LinkedHashSet<>(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, false); } else if (DataFlavorUtil.isFlavorCharsetTextType(flav)) { retval = new LinkedHashSet<>(0); // For text/* flavors, flavor-to-native mappings specified in // flavormap.properties are stored per flavor's base type. if ("text".equals(flav.getPrimaryType())) { LinkedHashSet textTypeNatives = getTextTypeToNative().get(flav.mimeType.getBaseType()); if (textTypeNatives != null) { retval.addAll(textTypeNatives); } } // Also include text/plain natives, but don't duplicate Strings LinkedHashSet textTypeNatives = getTextTypeToNative().get(TEXT_PLAIN_BASE_TYPE); if (textTypeNatives != null) { retval.addAll(textTypeNatives); } if (retval.isEmpty()) { retval = flavorToNativeLookup(flav, true); } 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. retval.addAll(flavorToNativeLookup(flav, false)); } } else if (DataFlavorUtil.isFlavorNoncharsetTextType(flav)) { retval = getTextTypeToNative().get(flav.mimeType.getBaseType()); if (retval == null || retval.isEmpty()) { retval = flavorToNativeLookup(flav, true); } 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. retval.addAll(flavorToNativeLookup(flav, false)); } } else { retval = flavorToNativeLookup(flav, true); } nativesForFlavorCache.put(flav, 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 */ @Override public synchronized List getFlavorsForNative(String nat) { LinkedHashSet returnValue = flavorsForNativeCache.check(nat); if (returnValue != null) { return new ArrayList<>(returnValue); } else { returnValue = new LinkedHashSet<>(); } if (nat == null) { for (String n : getNativesForFlavor(null)) { returnValue.addAll(getFlavorsForNative(n)); } } else { final LinkedHashSet flavors = nativeToFlavorLookup(nat); if (disabledMappingGenerationKeys.contains(nat)) { return new ArrayList<>(flavors); } final LinkedHashSet flavorsWithSynthesized = nativeToFlavorLookup(nat); for (DataFlavor df : flavorsWithSynthesized) { returnValue.add(df); if ("text".equals(df.getPrimaryType())) { String baseType = df.mimeType.getBaseType(); returnValue.addAll(convertMimeTypeToDataFlavors(baseType)); } } } flavorsForNativeCache.put(nat, returnValue); return new ArrayList<>(returnValue); } @SuppressWarnings("deprecation") private static Set convertMimeTypeToDataFlavors( final String baseType) { final Set returnValue = new LinkedHashSet<>(); String subType = null; try { final MimeType mimeType = new MimeType(baseType); subType = mimeType.getSubType(); } catch (MimeTypeParseException mtpe) { // Cannot happen, since we checked all mappings // on load from flavormap.properties. } if (DataFlavorUtil.doesSubtypeSupportCharset(subType, null)) { if (TEXT_PLAIN_BASE_TYPE.equals(baseType)) { returnValue.add(DataFlavor.stringFlavor); } for (String unicodeClassName : UNICODE_TEXT_CLASSES) { final String mimeType = baseType + ";charset=Unicode;class=" + unicodeClassName; final LinkedHashSet mimeTypes = handleHtmlMimeTypes(baseType, mimeType); for (String mt : mimeTypes) { DataFlavor toAdd = null; try { toAdd = new DataFlavor(mt); } catch (ClassNotFoundException cannotHappen) { } returnValue.add(toAdd); } } for (String charset : DataFlavorUtil.standardEncodings()) { for (String encodedTextClass : ENCODED_TEXT_CLASSES) { final String mimeType = baseType + ";charset=" + charset + ";class=" + encodedTextClass; final LinkedHashSet mimeTypes = handleHtmlMimeTypes(baseType, mimeType); for (String mt : mimeTypes) { DataFlavor df = null; try { df = new DataFlavor(mt); // 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 (df.equals(DataFlavor.plainTextFlavor)) { df = DataFlavor.plainTextFlavor; } } catch (ClassNotFoundException cannotHappen) { } returnValue.add(df); } } } if (TEXT_PLAIN_BASE_TYPE.equals(baseType)) { returnValue.add(DataFlavor.plainTextFlavor); } } else { // Non-charset text natives should be treated as // opaque, 8-bit data in any of its various // representations. for (String encodedTextClassName : ENCODED_TEXT_CLASSES) { DataFlavor toAdd = null; try { toAdd = new DataFlavor(baseType + ";class=" + encodedTextClassName); } catch (ClassNotFoundException cannotHappen) { } returnValue.add(toAdd); } } return returnValue; } private static final String [] htmlDocumentTypes = new String [] {"all", "selection", "fragment"}; private static LinkedHashSet handleHtmlMimeTypes(String baseType, String mimeType) { LinkedHashSet returnValues = new LinkedHashSet<>(); if (HTML_TEXT_BASE_TYPE.equals(baseType)) { for (String documentType : htmlDocumentTypes) { returnValues.add(mimeType + ";document=" + documentType); } } else { returnValues.add(mimeType); } return returnValues; } /** * 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 */ @Override 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); } Map retval = new HashMap<>(flavors.length, 1.0f); for (DataFlavor flavor : flavors) { List natives = getNativesForFlavor(flavor); String nat = (natives.isEmpty()) ? null : natives.get(0); retval.put(flavor, 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 */ @Override public synchronized Map getFlavorsForNatives(String[] natives) { // Use getFlavorsForNative to generate extra flavors for text natives if (natives == null) { List nativesList = getNativesForFlavor(null); natives = new String[nativesList.size()]; nativesList.toArray(natives); } Map retval = new HashMap<>(natives.length, 1.0f); for (String aNative : natives) { List flavors = getFlavorsForNative(aNative); DataFlavor flav = (flavors.isEmpty())? null : flavors.get(0); retval.put(aNative, 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) { Objects.requireNonNull(nat, "Null native not permitted"); Objects.requireNonNull(flav, "Null flavor not permitted"); LinkedHashSet natives = getFlavorToNative().get(flav); if (natives == null) { natives = new LinkedHashSet<>(1); getFlavorToNative().put(flav, natives); } natives.add(nat); nativesForFlavorCache.remove(flav); } /** * 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) { Objects.requireNonNull(natives, "Null natives not permitted"); Objects.requireNonNull(flav, "Null flavors not permitted"); getFlavorToNative().remove(flav); for (String aNative : natives) { addUnencodedNativeForFlavor(flav, aNative); } disabledMappingGenerationKeys.add(flav); nativesForFlavorCache.remove(flav); } /** * 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) { Objects.requireNonNull(nat, "Null native not permitted"); Objects.requireNonNull(flav, "Null flavor not permitted"); LinkedHashSet flavors = getNativeToFlavor().get(nat); if (flavors == null) { flavors = new LinkedHashSet<>(1); getNativeToFlavor().put(nat, flavors); } flavors.add(flav); flavorsForNativeCache.remove(nat); } /** * 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) { Objects.requireNonNull(nat, "Null native not permitted"); Objects.requireNonNull(flavors, "Null flavors not permitted"); getNativeToFlavor().remove(nat); for (DataFlavor flavor : flavors) { addFlavorForUnencodedNative(nat, flavor); } disabledMappingGenerationKeys.add(nat); flavorsForNativeCache.remove(nat); } /** * 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: *

    *
  • The encoded representation is null if and only if the * MIME type String is null.
  • *
  • The encoded representations for two non-null MIME type * Strings are equal if and only if these Strings * are equal according to String.equals(Object).
  • *
*

* The 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: *

    *
  • The encoded representation is null if and only if the * specified DataFlavor is null or its MIME type * String is null.
  • *
  • The encoded representations for two non-null * DataFlavors with non-null MIME type * Strings are equal if and only if the MIME type * Strings of these DataFlavors are equal * according to String.equals(Object).
  • *
*

* The 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 * @throws ClassNotFoundException if the class of the data flavor * is not loaded */ public static DataFlavor decodeDataFlavor(String nat) throws ClassNotFoundException { String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat); return (retval_str != null) ? new DataFlavor(retval_str) : null; } private static final class SoftCache { Map>> cache; public void put(K key, LinkedHashSet value) { if (cache == null) { cache = new HashMap<>(1); } cache.put(key, new SoftReference<>(value)); } public void remove(K key) { if (cache == null) return; cache.remove(null); cache.remove(key); } public LinkedHashSet check(K key) { if (cache == null) return null; SoftReference> ref = cache.get(key); if (ref != null) { return ref.get(); } return null; } } }