jdk/src/java.desktop/share/classes/java/awt/datatransfer/SystemFlavorMap.java
changeset 29037 8a11fed0d1a0
parent 28961 868dc757eab6
parent 29036 63d5aec20b48
child 29038 c2058b635c17
equal deleted inserted replaced
28961:868dc757eab6 29037:8a11fed0d1a0
     1 /*
       
     2  * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package java.awt.datatransfer;
       
    27 
       
    28 import sun.datatransfer.DataFlavorUtil;
       
    29 import sun.datatransfer.DesktopDatatransferService;
       
    30 
       
    31 import java.io.BufferedReader;
       
    32 import java.io.IOException;
       
    33 import java.io.InputStream;
       
    34 import java.io.InputStreamReader;
       
    35 import java.lang.ref.SoftReference;
       
    36 import java.util.ArrayList;
       
    37 import java.util.Collections;
       
    38 import java.util.HashMap;
       
    39 import java.util.HashSet;
       
    40 import java.util.LinkedHashSet;
       
    41 import java.util.List;
       
    42 import java.util.Map;
       
    43 import java.util.Objects;
       
    44 import java.util.Set;
       
    45 
       
    46 /**
       
    47  * The SystemFlavorMap is a configurable map between "natives" (Strings), which
       
    48  * correspond to platform-specific data formats, and "flavors" (DataFlavors),
       
    49  * which correspond to platform-independent MIME types. This mapping is used
       
    50  * by the data transfer subsystem to transfer data between Java and native
       
    51  * applications, and between Java applications in separate VMs.
       
    52  *
       
    53  * @since 1.2
       
    54  */
       
    55 public final class SystemFlavorMap implements FlavorMap, FlavorTable {
       
    56 
       
    57     /**
       
    58      * Constant prefix used to tag Java types converted to native platform
       
    59      * type.
       
    60      */
       
    61     private static String JavaMIME = "JAVA_DATAFLAVOR:";
       
    62 
       
    63     private static final Object FLAVOR_MAP_KEY = new Object();
       
    64 
       
    65     /**
       
    66      * The list of valid, decoded text flavor representation classes, in order
       
    67      * from best to worst.
       
    68      */
       
    69     private static final String[] UNICODE_TEXT_CLASSES = {
       
    70         "java.io.Reader", "java.lang.String", "java.nio.CharBuffer", "\"[C\""
       
    71     };
       
    72 
       
    73     /**
       
    74      * The list of valid, encoded text flavor representation classes, in order
       
    75      * from best to worst.
       
    76      */
       
    77     private static final String[] ENCODED_TEXT_CLASSES = {
       
    78         "java.io.InputStream", "java.nio.ByteBuffer", "\"[B\""
       
    79     };
       
    80 
       
    81     /**
       
    82      * A String representing text/plain MIME type.
       
    83      */
       
    84     private static final String TEXT_PLAIN_BASE_TYPE = "text/plain";
       
    85 
       
    86     /**
       
    87      * A String representing text/html MIME type.
       
    88      */
       
    89     private static final String HTML_TEXT_BASE_TYPE = "text/html";
       
    90 
       
    91     /**
       
    92      * Maps native Strings to Lists of DataFlavors (or base type Strings for
       
    93      * text DataFlavors).
       
    94      * Do not use the field directly, use getNativeToFlavor() instead.
       
    95      */
       
    96     private final Map<String, LinkedHashSet<DataFlavor>> nativeToFlavor = new HashMap<>();
       
    97 
       
    98     /**
       
    99      * Accessor to nativeToFlavor map.  Since we use lazy initialization we must
       
   100      * use this accessor instead of direct access to the field which may not be
       
   101      * initialized yet.  This method will initialize the field if needed.
       
   102      *
       
   103      * @return nativeToFlavor
       
   104      */
       
   105     private Map<String, LinkedHashSet<DataFlavor>> getNativeToFlavor() {
       
   106         if (!isMapInitialized) {
       
   107             initSystemFlavorMap();
       
   108         }
       
   109         return nativeToFlavor;
       
   110     }
       
   111 
       
   112     /**
       
   113      * Maps DataFlavors (or base type Strings for text DataFlavors) to Lists of
       
   114      * native Strings.
       
   115      * Do not use the field directly, use getFlavorToNative() instead.
       
   116      */
       
   117     private final Map<DataFlavor, LinkedHashSet<String>> flavorToNative = new HashMap<>();
       
   118 
       
   119     /**
       
   120      * Accessor to flavorToNative map.  Since we use lazy initialization we must
       
   121      * use this accessor instead of direct access to the field which may not be
       
   122      * initialized yet.  This method will initialize the field if needed.
       
   123      *
       
   124      * @return flavorToNative
       
   125      */
       
   126     private synchronized Map<DataFlavor, LinkedHashSet<String>> getFlavorToNative() {
       
   127         if (!isMapInitialized) {
       
   128             initSystemFlavorMap();
       
   129         }
       
   130         return flavorToNative;
       
   131     }
       
   132 
       
   133     /**
       
   134      * Maps a text DataFlavor primary mime-type to the native. Used only to store
       
   135      * standard mappings registered in the flavormap.properties
       
   136      * Do not use this field directly, use getTextTypeToNative() instead.
       
   137      */
       
   138     private Map<String, LinkedHashSet<String>> textTypeToNative = new HashMap<>();
       
   139 
       
   140     /**
       
   141      * Shows if the object has been initialized.
       
   142      */
       
   143     private boolean isMapInitialized = false;
       
   144 
       
   145     /**
       
   146      * An accessor to textTypeToNative map.  Since we use lazy initialization we
       
   147      * must use this accessor instead of direct access to the field which may not
       
   148      * be initialized yet. This method will initialize the field if needed.
       
   149      *
       
   150      * @return textTypeToNative
       
   151      */
       
   152     private synchronized Map<String, LinkedHashSet<String>> getTextTypeToNative() {
       
   153         if (!isMapInitialized) {
       
   154             initSystemFlavorMap();
       
   155             // From this point the map should not be modified
       
   156             textTypeToNative = Collections.unmodifiableMap(textTypeToNative);
       
   157         }
       
   158         return textTypeToNative;
       
   159     }
       
   160 
       
   161     /**
       
   162      * Caches the result of getNativesForFlavor(). Maps DataFlavors to
       
   163      * SoftReferences which reference LinkedHashSet of String natives.
       
   164      */
       
   165     private final SoftCache<DataFlavor, String> nativesForFlavorCache = new SoftCache<>();
       
   166 
       
   167     /**
       
   168      * Caches the result getFlavorsForNative(). Maps String natives to
       
   169      * SoftReferences which reference LinkedHashSet of DataFlavors.
       
   170      */
       
   171     private final SoftCache<String, DataFlavor> flavorsForNativeCache = new SoftCache<>();
       
   172 
       
   173     /**
       
   174      * Dynamic mapping generation used for text mappings should not be applied
       
   175      * to the DataFlavors and String natives for which the mappings have been
       
   176      * explicitly specified with setFlavorsForNative() or
       
   177      * setNativesForFlavor(). This keeps all such keys.
       
   178      */
       
   179     private Set<Object> disabledMappingGenerationKeys = new HashSet<>();
       
   180 
       
   181     /**
       
   182      * Returns the default FlavorMap for this thread's ClassLoader.
       
   183      *
       
   184      * @return the default FlavorMap for this thread's ClassLoader
       
   185      */
       
   186     public static FlavorMap getDefaultFlavorMap() {
       
   187         return DataFlavorUtil.getDesktopService().getFlavorMap(SystemFlavorMap::new);
       
   188     }
       
   189 
       
   190     private SystemFlavorMap() {
       
   191     }
       
   192 
       
   193     /**
       
   194      * Initializes a SystemFlavorMap by reading flavormap.properties
       
   195      * For thread-safety must be called under lock on this.
       
   196      */
       
   197     private void initSystemFlavorMap() {
       
   198         if (isMapInitialized) {
       
   199             return;
       
   200         }
       
   201         isMapInitialized = true;
       
   202 
       
   203         InputStream is = SystemFlavorMap.class.getResourceAsStream("/sun/datatransfer/resources/flavormap.properties");
       
   204         if (is == null) {
       
   205             throw new InternalError("Default flavor mapping not found");
       
   206         }
       
   207 
       
   208         try (InputStreamReader isr = new InputStreamReader(is);
       
   209              BufferedReader reader = new BufferedReader(isr)) {
       
   210             String line;
       
   211             while ((line = reader.readLine()) != null) {
       
   212                 line = line.trim();
       
   213                 if (line.startsWith("#") || line.isEmpty()) continue;
       
   214                 while (line.endsWith("\\")) {
       
   215                     line = line.substring(0, line.length() - 1) + reader.readLine().trim();
       
   216                 }
       
   217                 int delimiterPosition = line.indexOf('=');
       
   218                 String key = line.substring(0, delimiterPosition).replaceAll("\\ ", " ");
       
   219                 String[] values = line.substring(delimiterPosition + 1, line.length()).split(",");
       
   220                 for (String value : values) {
       
   221                     try {
       
   222                         value = loadConvert(value);
       
   223                         MimeType mime = new MimeType(value);
       
   224                         if ("text".equals(mime.getPrimaryType())) {
       
   225                             String charset = mime.getParameter("charset");
       
   226                             if (DataFlavorUtil.doesSubtypeSupportCharset(mime.getSubType(), charset))
       
   227                             {
       
   228                                 // We need to store the charset and eoln
       
   229                                 // parameters, if any, so that the
       
   230                                 // DataTransferer will have this information
       
   231                                 // for conversion into the native format.
       
   232                                 DesktopDatatransferService desktopService =
       
   233                                         DataFlavorUtil.getDesktopService();
       
   234                                 if (desktopService.isDesktopPresent()) {
       
   235                                     desktopService.registerTextFlavorProperties(
       
   236                                             key, charset,
       
   237                                             mime.getParameter("eoln"),
       
   238                                             mime.getParameter("terminators"));
       
   239                                 }
       
   240                             }
       
   241 
       
   242                             // But don't store any of these parameters in the
       
   243                             // DataFlavor itself for any text natives (even
       
   244                             // non-charset ones). The SystemFlavorMap will
       
   245                             // synthesize the appropriate mappings later.
       
   246                             mime.removeParameter("charset");
       
   247                             mime.removeParameter("class");
       
   248                             mime.removeParameter("eoln");
       
   249                             mime.removeParameter("terminators");
       
   250                             value = mime.toString();
       
   251                         }
       
   252                     } catch (MimeTypeParseException e) {
       
   253                         e.printStackTrace();
       
   254                         continue;
       
   255                     }
       
   256 
       
   257                     DataFlavor flavor;
       
   258                     try {
       
   259                         flavor = new DataFlavor(value);
       
   260                     } catch (Exception e) {
       
   261                         try {
       
   262                             flavor = new DataFlavor(value, null);
       
   263                         } catch (Exception ee) {
       
   264                             ee.printStackTrace();
       
   265                             continue;
       
   266                         }
       
   267                     }
       
   268 
       
   269                     final LinkedHashSet<DataFlavor> dfs = new LinkedHashSet<>();
       
   270                     dfs.add(flavor);
       
   271 
       
   272                     if ("text".equals(flavor.getPrimaryType())) {
       
   273                         dfs.addAll(convertMimeTypeToDataFlavors(value));
       
   274                         store(flavor.mimeType.getBaseType(), key, getTextTypeToNative());
       
   275                     }
       
   276 
       
   277                     for (DataFlavor df : dfs) {
       
   278                         store(df, key, getFlavorToNative());
       
   279                         store(key, df, getNativeToFlavor());
       
   280                     }
       
   281                 }
       
   282             }
       
   283         } catch (IOException e) {
       
   284             throw new InternalError("Error reading default flavor mapping", e);
       
   285         }
       
   286     }
       
   287 
       
   288     // Copied from java.util.Properties
       
   289     private static String loadConvert(String theString) {
       
   290         char aChar;
       
   291         int len = theString.length();
       
   292         StringBuilder outBuffer = new StringBuilder(len);
       
   293 
       
   294         for (int x = 0; x < len; ) {
       
   295             aChar = theString.charAt(x++);
       
   296             if (aChar == '\\') {
       
   297                 aChar = theString.charAt(x++);
       
   298                 if (aChar == 'u') {
       
   299                     // Read the xxxx
       
   300                     int value = 0;
       
   301                     for (int i = 0; i < 4; i++) {
       
   302                         aChar = theString.charAt(x++);
       
   303                         switch (aChar) {
       
   304                             case '0': case '1': case '2': case '3': case '4':
       
   305                             case '5': case '6': case '7': case '8': case '9': {
       
   306                                 value = (value << 4) + aChar - '0';
       
   307                                 break;
       
   308                             }
       
   309                             case 'a': case 'b': case 'c':
       
   310                             case 'd': case 'e': case 'f': {
       
   311                                 value = (value << 4) + 10 + aChar - 'a';
       
   312                                 break;
       
   313                             }
       
   314                             case 'A': case 'B': case 'C':
       
   315                             case 'D': case 'E': case 'F': {
       
   316                                 value = (value << 4) + 10 + aChar - 'A';
       
   317                                 break;
       
   318                             }
       
   319                             default: {
       
   320                                 throw new IllegalArgumentException(
       
   321                                         "Malformed \\uxxxx encoding.");
       
   322                             }
       
   323                         }
       
   324                     }
       
   325                     outBuffer.append((char)value);
       
   326                 } else {
       
   327                     if (aChar == 't') {
       
   328                         aChar = '\t';
       
   329                     } else if (aChar == 'r') {
       
   330                         aChar = '\r';
       
   331                     } else if (aChar == 'n') {
       
   332                         aChar = '\n';
       
   333                     } else if (aChar == 'f') {
       
   334                         aChar = '\f';
       
   335                     }
       
   336                     outBuffer.append(aChar);
       
   337                 }
       
   338             } else {
       
   339                 outBuffer.append(aChar);
       
   340             }
       
   341         }
       
   342         return outBuffer.toString();
       
   343     }
       
   344 
       
   345     /**
       
   346      * Stores the listed object under the specified hash key in map. Unlike a
       
   347      * standard map, the listed object will not replace any object already at
       
   348      * the appropriate Map location, but rather will be appended to a List
       
   349      * stored in that location.
       
   350      */
       
   351     private <H, L> void store(H hashed, L listed, Map<H, LinkedHashSet<L>> map) {
       
   352         LinkedHashSet<L> list = map.get(hashed);
       
   353         if (list == null) {
       
   354             list = new LinkedHashSet<>(1);
       
   355             map.put(hashed, list);
       
   356         }
       
   357         if (!list.contains(listed)) {
       
   358             list.add(listed);
       
   359         }
       
   360     }
       
   361 
       
   362     /**
       
   363      * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method
       
   364      * handles the case where 'nat' is not found in 'nativeToFlavor'. In that
       
   365      * case, a new DataFlavor is synthesized, stored, and returned, if and
       
   366      * only if the specified native is encoded as a Java MIME type.
       
   367      */
       
   368     private LinkedHashSet<DataFlavor> nativeToFlavorLookup(String nat) {
       
   369         LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat);
       
   370 
       
   371         if (nat != null && !disabledMappingGenerationKeys.contains(nat)) {
       
   372             DesktopDatatransferService desktopService = DataFlavorUtil.getDesktopService();
       
   373             if (desktopService.isDesktopPresent()) {
       
   374                 LinkedHashSet<DataFlavor> platformFlavors =
       
   375                         desktopService.getPlatformMappingsForNative(nat);
       
   376                 if (!platformFlavors.isEmpty()) {
       
   377                     if (flavors != null) {
       
   378                         // Prepending the platform-specific mappings ensures
       
   379                         // that the flavors added with
       
   380                         // addFlavorForUnencodedNative() are at the end of
       
   381                         // list.
       
   382                         platformFlavors.addAll(flavors);
       
   383                     }
       
   384                     flavors = platformFlavors;
       
   385                 }
       
   386             }
       
   387         }
       
   388 
       
   389         if (flavors == null && isJavaMIMEType(nat)) {
       
   390             String decoded = decodeJavaMIMEType(nat);
       
   391             DataFlavor flavor = null;
       
   392 
       
   393             try {
       
   394                 flavor = new DataFlavor(decoded);
       
   395             } catch (Exception e) {
       
   396                 System.err.println("Exception \"" + e.getClass().getName() +
       
   397                                    ": " + e.getMessage()  +
       
   398                                    "\"while constructing DataFlavor for: " +
       
   399                                    decoded);
       
   400             }
       
   401 
       
   402             if (flavor != null) {
       
   403                 flavors = new LinkedHashSet<>(1);
       
   404                 getNativeToFlavor().put(nat, flavors);
       
   405                 flavors.add(flavor);
       
   406                 flavorsForNativeCache.remove(nat);
       
   407 
       
   408                 LinkedHashSet<String> natives = getFlavorToNative().get(flavor);
       
   409                 if (natives == null) {
       
   410                     natives = new LinkedHashSet<>(1);
       
   411                     getFlavorToNative().put(flavor, natives);
       
   412                 }
       
   413                 natives.add(nat);
       
   414                 nativesForFlavorCache.remove(flavor);
       
   415             }
       
   416         }
       
   417 
       
   418         return (flavors != null) ? flavors : new LinkedHashSet<>(0);
       
   419     }
       
   420 
       
   421     /**
       
   422      * Semantically equivalent to 'flavorToNative.get(flav)'. This method
       
   423      * handles the case where 'flav' is not found in 'flavorToNative' depending
       
   424      * on the value of passes 'synthesize' parameter. If 'synthesize' is
       
   425      * SYNTHESIZE_IF_NOT_FOUND a native is synthesized, stored, and returned by
       
   426      * encoding the DataFlavor's MIME type. Otherwise an empty List is returned
       
   427      * and 'flavorToNative' remains unaffected.
       
   428      */
       
   429     private LinkedHashSet<String> flavorToNativeLookup(final DataFlavor flav,
       
   430                                                        final boolean synthesize) {
       
   431 
       
   432         LinkedHashSet<String> natives = getFlavorToNative().get(flav);
       
   433 
       
   434         if (flav != null && !disabledMappingGenerationKeys.contains(flav)) {
       
   435             DesktopDatatransferService desktopService = DataFlavorUtil.getDesktopService();
       
   436             if (desktopService.isDesktopPresent()) {
       
   437                 LinkedHashSet<String> platformNatives =
       
   438                         desktopService.getPlatformMappingsForFlavor(flav);
       
   439                 if (!platformNatives.isEmpty()) {
       
   440                     if (natives != null) {
       
   441                         // Prepend the platform-specific mappings to ensure
       
   442                         // that the natives added with
       
   443                         // addUnencodedNativeForFlavor() are at the end of
       
   444                         // list.
       
   445                         platformNatives.addAll(natives);
       
   446                     }
       
   447                     natives = platformNatives;
       
   448                 }
       
   449             }
       
   450         }
       
   451 
       
   452         if (natives == null) {
       
   453             if (synthesize) {
       
   454                 String encoded = encodeDataFlavor(flav);
       
   455                 natives = new LinkedHashSet<>(1);
       
   456                 getFlavorToNative().put(flav, natives);
       
   457                 natives.add(encoded);
       
   458 
       
   459                 LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(encoded);
       
   460                 if (flavors == null) {
       
   461                     flavors = new LinkedHashSet<>(1);
       
   462                     getNativeToFlavor().put(encoded, flavors);
       
   463                 }
       
   464                 flavors.add(flav);
       
   465 
       
   466                 nativesForFlavorCache.remove(flav);
       
   467                 flavorsForNativeCache.remove(encoded);
       
   468             } else {
       
   469                 natives = new LinkedHashSet<>(0);
       
   470             }
       
   471         }
       
   472 
       
   473         return new LinkedHashSet<>(natives);
       
   474     }
       
   475 
       
   476     /**
       
   477      * Returns a <code>List</code> of <code>String</code> natives to which the
       
   478      * specified <code>DataFlavor</code> can be translated by the data transfer
       
   479      * subsystem. The <code>List</code> will be sorted from best native to
       
   480      * worst. That is, the first native will best reflect data in the specified
       
   481      * flavor to the underlying native platform.
       
   482      * <p>
       
   483      * If the specified <code>DataFlavor</code> is previously unknown to the
       
   484      * data transfer subsystem and the data transfer subsystem is unable to
       
   485      * translate this <code>DataFlavor</code> to any existing native, then
       
   486      * invoking this method will establish a
       
   487      * mapping in both directions between the specified <code>DataFlavor</code>
       
   488      * and an encoded version of its MIME type as its native.
       
   489      *
       
   490      * @param flav the <code>DataFlavor</code> whose corresponding natives
       
   491      *        should be returned. If <code>null</code> is specified, all
       
   492      *        natives currently known to the data transfer subsystem are
       
   493      *        returned in a non-deterministic order.
       
   494      * @return a <code>java.util.List</code> of <code>java.lang.String</code>
       
   495      *         objects which are platform-specific representations of platform-
       
   496      *         specific data formats
       
   497      *
       
   498      * @see #encodeDataFlavor
       
   499      * @since 1.4
       
   500      */
       
   501     @Override
       
   502     public synchronized List<String> getNativesForFlavor(DataFlavor flav) {
       
   503         LinkedHashSet<String> retval = nativesForFlavorCache.check(flav);
       
   504         if (retval != null) {
       
   505             return new ArrayList<>(retval);
       
   506         }
       
   507 
       
   508         if (flav == null) {
       
   509             retval = new LinkedHashSet<>(getNativeToFlavor().keySet());
       
   510         } else if (disabledMappingGenerationKeys.contains(flav)) {
       
   511             // In this case we shouldn't synthesize a native for this flavor,
       
   512             // since its mappings were explicitly specified.
       
   513             retval = flavorToNativeLookup(flav, false);
       
   514         } else if (DataFlavorUtil.isFlavorCharsetTextType(flav)) {
       
   515             retval = new LinkedHashSet<>(0);
       
   516 
       
   517             // For text/* flavors, flavor-to-native mappings specified in
       
   518             // flavormap.properties are stored per flavor's base type.
       
   519             if ("text".equals(flav.getPrimaryType())) {
       
   520                 LinkedHashSet<String> textTypeNatives =
       
   521                         getTextTypeToNative().get(flav.mimeType.getBaseType());
       
   522                 if (textTypeNatives != null) {
       
   523                     retval.addAll(textTypeNatives);
       
   524                 }
       
   525             }
       
   526 
       
   527             // Also include text/plain natives, but don't duplicate Strings
       
   528             LinkedHashSet<String> textTypeNatives =
       
   529                     getTextTypeToNative().get(TEXT_PLAIN_BASE_TYPE);
       
   530             if (textTypeNatives != null) {
       
   531                 retval.addAll(textTypeNatives);
       
   532             }
       
   533 
       
   534             if (retval.isEmpty()) {
       
   535                 retval = flavorToNativeLookup(flav, true);
       
   536             } else {
       
   537                 // In this branch it is guaranteed that natives explicitly
       
   538                 // listed for flav's MIME type were added with
       
   539                 // addUnencodedNativeForFlavor(), so they have lower priority.
       
   540                 retval.addAll(flavorToNativeLookup(flav, false));
       
   541             }
       
   542         } else if (DataFlavorUtil.isFlavorNoncharsetTextType(flav)) {
       
   543             retval = getTextTypeToNative().get(flav.mimeType.getBaseType());
       
   544 
       
   545             if (retval == null || retval.isEmpty()) {
       
   546                 retval = flavorToNativeLookup(flav, true);
       
   547             } else {
       
   548                 // In this branch it is guaranteed that natives explicitly
       
   549                 // listed for flav's MIME type were added with
       
   550                 // addUnencodedNativeForFlavor(), so they have lower priority.
       
   551                 retval.addAll(flavorToNativeLookup(flav, false));
       
   552             }
       
   553         } else {
       
   554             retval = flavorToNativeLookup(flav, true);
       
   555         }
       
   556 
       
   557         nativesForFlavorCache.put(flav, retval);
       
   558         // Create a copy, because client code can modify the returned list.
       
   559         return new ArrayList<>(retval);
       
   560     }
       
   561 
       
   562     /**
       
   563      * Returns a <code>List</code> of <code>DataFlavor</code>s to which the
       
   564      * specified <code>String</code> native can be translated by the data
       
   565      * transfer subsystem. The <code>List</code> will be sorted from best
       
   566      * <code>DataFlavor</code> to worst. That is, the first
       
   567      * <code>DataFlavor</code> will best reflect data in the specified
       
   568      * native to a Java application.
       
   569      * <p>
       
   570      * If the specified native is previously unknown to the data transfer
       
   571      * subsystem, and that native has been properly encoded, then invoking this
       
   572      * method will establish a mapping in both directions between the specified
       
   573      * native and a <code>DataFlavor</code> whose MIME type is a decoded
       
   574      * version of the native.
       
   575      * <p>
       
   576      * If the specified native is not a properly encoded native and the
       
   577      * mappings for this native have not been altered with
       
   578      * <code>setFlavorsForNative</code>, then the contents of the
       
   579      * <code>List</code> is platform dependent, but <code>null</code>
       
   580      * cannot be returned.
       
   581      *
       
   582      * @param nat the native whose corresponding <code>DataFlavor</code>s
       
   583      *        should be returned. If <code>null</code> is specified, all
       
   584      *        <code>DataFlavor</code>s currently known to the data transfer
       
   585      *        subsystem are returned in a non-deterministic order.
       
   586      * @return a <code>java.util.List</code> of <code>DataFlavor</code>
       
   587      *         objects into which platform-specific data in the specified,
       
   588      *         platform-specific native can be translated
       
   589      *
       
   590      * @see #encodeJavaMIMEType
       
   591      * @since 1.4
       
   592      */
       
   593     @Override
       
   594     public synchronized List<DataFlavor> getFlavorsForNative(String nat) {
       
   595         LinkedHashSet<DataFlavor> returnValue = flavorsForNativeCache.check(nat);
       
   596         if (returnValue != null) {
       
   597             return new ArrayList<>(returnValue);
       
   598         } else {
       
   599             returnValue = new LinkedHashSet<>();
       
   600         }
       
   601 
       
   602         if (nat == null) {
       
   603             for (String n : getNativesForFlavor(null)) {
       
   604                 returnValue.addAll(getFlavorsForNative(n));
       
   605             }
       
   606         } else {
       
   607             final LinkedHashSet<DataFlavor> flavors = nativeToFlavorLookup(nat);
       
   608             if (disabledMappingGenerationKeys.contains(nat)) {
       
   609                 return new ArrayList<>(flavors);
       
   610             }
       
   611 
       
   612             final LinkedHashSet<DataFlavor> flavorsWithSynthesized =
       
   613                     nativeToFlavorLookup(nat);
       
   614 
       
   615             for (DataFlavor df : flavorsWithSynthesized) {
       
   616                 returnValue.add(df);
       
   617                 if ("text".equals(df.getPrimaryType())) {
       
   618                     String baseType = df.mimeType.getBaseType();
       
   619                     returnValue.addAll(convertMimeTypeToDataFlavors(baseType));
       
   620                 }
       
   621             }
       
   622         }
       
   623         flavorsForNativeCache.put(nat, returnValue);
       
   624         return new ArrayList<>(returnValue);
       
   625     }
       
   626 
       
   627     @SuppressWarnings("deprecation")
       
   628     private static Set<DataFlavor> convertMimeTypeToDataFlavors(
       
   629         final String baseType) {
       
   630 
       
   631         final Set<DataFlavor> returnValue = new LinkedHashSet<>();
       
   632 
       
   633         String subType = null;
       
   634 
       
   635         try {
       
   636             final MimeType mimeType = new MimeType(baseType);
       
   637             subType = mimeType.getSubType();
       
   638         } catch (MimeTypeParseException mtpe) {
       
   639             // Cannot happen, since we checked all mappings
       
   640             // on load from flavormap.properties.
       
   641         }
       
   642 
       
   643         if (DataFlavorUtil.doesSubtypeSupportCharset(subType, null)) {
       
   644             if (TEXT_PLAIN_BASE_TYPE.equals(baseType))
       
   645             {
       
   646                 returnValue.add(DataFlavor.stringFlavor);
       
   647             }
       
   648 
       
   649             for (String unicodeClassName : UNICODE_TEXT_CLASSES) {
       
   650                 final String mimeType = baseType + ";charset=Unicode;class=" +
       
   651                                             unicodeClassName;
       
   652 
       
   653                 final LinkedHashSet<String> mimeTypes =
       
   654                     handleHtmlMimeTypes(baseType, mimeType);
       
   655                 for (String mt : mimeTypes) {
       
   656                     DataFlavor toAdd = null;
       
   657                     try {
       
   658                         toAdd = new DataFlavor(mt);
       
   659                     } catch (ClassNotFoundException cannotHappen) {
       
   660                     }
       
   661                     returnValue.add(toAdd);
       
   662                 }
       
   663             }
       
   664 
       
   665             for (String charset : DataFlavorUtil.standardEncodings()) {
       
   666 
       
   667                 for (String encodedTextClass : ENCODED_TEXT_CLASSES) {
       
   668                     final String mimeType =
       
   669                             baseType + ";charset=" + charset +
       
   670                             ";class=" + encodedTextClass;
       
   671 
       
   672                     final LinkedHashSet<String> mimeTypes =
       
   673                         handleHtmlMimeTypes(baseType, mimeType);
       
   674 
       
   675                     for (String mt : mimeTypes) {
       
   676 
       
   677                         DataFlavor df = null;
       
   678 
       
   679                         try {
       
   680                             df = new DataFlavor(mt);
       
   681                             // Check for equality to plainTextFlavor so
       
   682                             // that we can ensure that the exact charset of
       
   683                             // plainTextFlavor, not the canonical charset
       
   684                             // or another equivalent charset with a
       
   685                             // different name, is used.
       
   686                             if (df.equals(DataFlavor.plainTextFlavor)) {
       
   687                                 df = DataFlavor.plainTextFlavor;
       
   688                             }
       
   689                         } catch (ClassNotFoundException cannotHappen) {
       
   690                         }
       
   691 
       
   692                         returnValue.add(df);
       
   693                     }
       
   694                 }
       
   695             }
       
   696 
       
   697             if (TEXT_PLAIN_BASE_TYPE.equals(baseType))
       
   698             {
       
   699                 returnValue.add(DataFlavor.plainTextFlavor);
       
   700             }
       
   701         } else {
       
   702             // Non-charset text natives should be treated as
       
   703             // opaque, 8-bit data in any of its various
       
   704             // representations.
       
   705             for (String encodedTextClassName : ENCODED_TEXT_CLASSES) {
       
   706                 DataFlavor toAdd = null;
       
   707                 try {
       
   708                     toAdd = new DataFlavor(baseType +
       
   709                          ";class=" + encodedTextClassName);
       
   710                 } catch (ClassNotFoundException cannotHappen) {
       
   711                 }
       
   712                 returnValue.add(toAdd);
       
   713             }
       
   714         }
       
   715         return returnValue;
       
   716     }
       
   717 
       
   718     private static final String [] htmlDocumentTypes =
       
   719             new String [] {"all", "selection", "fragment"};
       
   720 
       
   721     private static LinkedHashSet<String> handleHtmlMimeTypes(String baseType,
       
   722                                                              String mimeType) {
       
   723 
       
   724         LinkedHashSet<String> returnValues = new LinkedHashSet<>();
       
   725 
       
   726         if (HTML_TEXT_BASE_TYPE.equals(baseType)) {
       
   727             for (String documentType : htmlDocumentTypes) {
       
   728                 returnValues.add(mimeType + ";document=" + documentType);
       
   729             }
       
   730         } else {
       
   731             returnValues.add(mimeType);
       
   732         }
       
   733 
       
   734         return returnValues;
       
   735     }
       
   736 
       
   737     /**
       
   738      * Returns a <code>Map</code> of the specified <code>DataFlavor</code>s to
       
   739      * their most preferred <code>String</code> native. Each native value will
       
   740      * be the same as the first native in the List returned by
       
   741      * <code>getNativesForFlavor</code> for the specified flavor.
       
   742      * <p>
       
   743      * If a specified <code>DataFlavor</code> is previously unknown to the
       
   744      * data transfer subsystem, then invoking this method will establish a
       
   745      * mapping in both directions between the specified <code>DataFlavor</code>
       
   746      * and an encoded version of its MIME type as its native.
       
   747      *
       
   748      * @param flavors an array of <code>DataFlavor</code>s which will be the
       
   749      *        key set of the returned <code>Map</code>. If <code>null</code> is
       
   750      *        specified, a mapping of all <code>DataFlavor</code>s known to the
       
   751      *        data transfer subsystem to their most preferred
       
   752      *        <code>String</code> natives will be returned.
       
   753      * @return a <code>java.util.Map</code> of <code>DataFlavor</code>s to
       
   754      *         <code>String</code> natives
       
   755      *
       
   756      * @see #getNativesForFlavor
       
   757      * @see #encodeDataFlavor
       
   758      */
       
   759     @Override
       
   760     public synchronized Map<DataFlavor,String> getNativesForFlavors(DataFlavor[] flavors)
       
   761     {
       
   762         // Use getNativesForFlavor to generate extra natives for text flavors
       
   763         // and stringFlavor
       
   764 
       
   765         if (flavors == null) {
       
   766             List<DataFlavor> flavor_list = getFlavorsForNative(null);
       
   767             flavors = new DataFlavor[flavor_list.size()];
       
   768             flavor_list.toArray(flavors);
       
   769         }
       
   770 
       
   771         Map<DataFlavor, String> retval = new HashMap<>(flavors.length, 1.0f);
       
   772         for (DataFlavor flavor : flavors) {
       
   773             List<String> natives = getNativesForFlavor(flavor);
       
   774             String nat = (natives.isEmpty()) ? null : natives.get(0);
       
   775             retval.put(flavor, nat);
       
   776         }
       
   777 
       
   778         return retval;
       
   779     }
       
   780 
       
   781     /**
       
   782      * Returns a <code>Map</code> of the specified <code>String</code> natives
       
   783      * to their most preferred <code>DataFlavor</code>. Each
       
   784      * <code>DataFlavor</code> value will be the same as the first
       
   785      * <code>DataFlavor</code> in the List returned by
       
   786      * <code>getFlavorsForNative</code> for the specified native.
       
   787      * <p>
       
   788      * If a specified native is previously unknown to the data transfer
       
   789      * subsystem, and that native has been properly encoded, then invoking this
       
   790      * method will establish a mapping in both directions between the specified
       
   791      * native and a <code>DataFlavor</code> whose MIME type is a decoded
       
   792      * version of the native.
       
   793      *
       
   794      * @param natives an array of <code>String</code>s which will be the
       
   795      *        key set of the returned <code>Map</code>. If <code>null</code> is
       
   796      *        specified, a mapping of all supported <code>String</code> natives
       
   797      *        to their most preferred <code>DataFlavor</code>s will be
       
   798      *        returned.
       
   799      * @return a <code>java.util.Map</code> of <code>String</code> natives to
       
   800      *         <code>DataFlavor</code>s
       
   801      *
       
   802      * @see #getFlavorsForNative
       
   803      * @see #encodeJavaMIMEType
       
   804      */
       
   805     @Override
       
   806     public synchronized Map<String,DataFlavor> getFlavorsForNatives(String[] natives)
       
   807     {
       
   808         // Use getFlavorsForNative to generate extra flavors for text natives
       
   809         if (natives == null) {
       
   810             List<String> nativesList = getNativesForFlavor(null);
       
   811             natives = new String[nativesList.size()];
       
   812             nativesList.toArray(natives);
       
   813         }
       
   814 
       
   815         Map<String, DataFlavor> retval = new HashMap<>(natives.length, 1.0f);
       
   816         for (String aNative : natives) {
       
   817             List<DataFlavor> flavors = getFlavorsForNative(aNative);
       
   818             DataFlavor flav = (flavors.isEmpty())? null : flavors.get(0);
       
   819             retval.put(aNative, flav);
       
   820         }
       
   821         return retval;
       
   822     }
       
   823 
       
   824     /**
       
   825      * Adds a mapping from the specified <code>DataFlavor</code> (and all
       
   826      * <code>DataFlavor</code>s equal to the specified <code>DataFlavor</code>)
       
   827      * to the specified <code>String</code> native.
       
   828      * Unlike <code>getNativesForFlavor</code>, the mapping will only be
       
   829      * established in one direction, and the native will not be encoded. To
       
   830      * establish a two-way mapping, call
       
   831      * <code>addFlavorForUnencodedNative</code> as well. The new mapping will
       
   832      * be of lower priority than any existing mapping.
       
   833      * This method has no effect if a mapping from the specified or equal
       
   834      * <code>DataFlavor</code> to the specified <code>String</code> native
       
   835      * already exists.
       
   836      *
       
   837      * @param flav the <code>DataFlavor</code> key for the mapping
       
   838      * @param nat the <code>String</code> native value for the mapping
       
   839      * @throws NullPointerException if flav or nat is <code>null</code>
       
   840      *
       
   841      * @see #addFlavorForUnencodedNative
       
   842      * @since 1.4
       
   843      */
       
   844     public synchronized void addUnencodedNativeForFlavor(DataFlavor flav,
       
   845                                                          String nat) {
       
   846         Objects.requireNonNull(nat, "Null native not permitted");
       
   847         Objects.requireNonNull(flav, "Null flavor not permitted");
       
   848 
       
   849         LinkedHashSet<String> natives = getFlavorToNative().get(flav);
       
   850         if (natives == null) {
       
   851             natives = new LinkedHashSet<>(1);
       
   852             getFlavorToNative().put(flav, natives);
       
   853         }
       
   854         natives.add(nat);
       
   855         nativesForFlavorCache.remove(flav);
       
   856     }
       
   857 
       
   858     /**
       
   859      * Discards the current mappings for the specified <code>DataFlavor</code>
       
   860      * and all <code>DataFlavor</code>s equal to the specified
       
   861      * <code>DataFlavor</code>, and creates new mappings to the
       
   862      * specified <code>String</code> natives.
       
   863      * Unlike <code>getNativesForFlavor</code>, the mappings will only be
       
   864      * established in one direction, and the natives will not be encoded. To
       
   865      * establish two-way mappings, call <code>setFlavorsForNative</code>
       
   866      * as well. The first native in the array will represent the highest
       
   867      * priority mapping. Subsequent natives will represent mappings of
       
   868      * decreasing priority.
       
   869      * <p>
       
   870      * If the array contains several elements that reference equal
       
   871      * <code>String</code> natives, this method will establish new mappings
       
   872      * for the first of those elements and ignore the rest of them.
       
   873      * <p>
       
   874      * It is recommended that client code not reset mappings established by the
       
   875      * data transfer subsystem. This method should only be used for
       
   876      * application-level mappings.
       
   877      *
       
   878      * @param flav the <code>DataFlavor</code> key for the mappings
       
   879      * @param natives the <code>String</code> native values for the mappings
       
   880      * @throws NullPointerException if flav or natives is <code>null</code>
       
   881      *         or if natives contains <code>null</code> elements
       
   882      *
       
   883      * @see #setFlavorsForNative
       
   884      * @since 1.4
       
   885      */
       
   886     public synchronized void setNativesForFlavor(DataFlavor flav,
       
   887                                                  String[] natives) {
       
   888         Objects.requireNonNull(natives, "Null natives not permitted");
       
   889         Objects.requireNonNull(flav, "Null flavors not permitted");
       
   890 
       
   891         getFlavorToNative().remove(flav);
       
   892         for (String aNative : natives) {
       
   893             addUnencodedNativeForFlavor(flav, aNative);
       
   894         }
       
   895         disabledMappingGenerationKeys.add(flav);
       
   896         nativesForFlavorCache.remove(flav);
       
   897     }
       
   898 
       
   899     /**
       
   900      * Adds a mapping from a single <code>String</code> native to a single
       
   901      * <code>DataFlavor</code>. Unlike <code>getFlavorsForNative</code>, the
       
   902      * mapping will only be established in one direction, and the native will
       
   903      * not be encoded. To establish a two-way mapping, call
       
   904      * <code>addUnencodedNativeForFlavor</code> as well. The new mapping will
       
   905      * be of lower priority than any existing mapping.
       
   906      * This method has no effect if a mapping from the specified
       
   907      * <code>String</code> native to the specified or equal
       
   908      * <code>DataFlavor</code> already exists.
       
   909      *
       
   910      * @param nat the <code>String</code> native key for the mapping
       
   911      * @param flav the <code>DataFlavor</code> value for the mapping
       
   912      * @throws NullPointerException if nat or flav is <code>null</code>
       
   913      *
       
   914      * @see #addUnencodedNativeForFlavor
       
   915      * @since 1.4
       
   916      */
       
   917     public synchronized void addFlavorForUnencodedNative(String nat,
       
   918                                                          DataFlavor flav) {
       
   919         Objects.requireNonNull(nat, "Null native not permitted");
       
   920         Objects.requireNonNull(flav, "Null flavor not permitted");
       
   921 
       
   922         LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat);
       
   923         if (flavors == null) {
       
   924             flavors = new LinkedHashSet<>(1);
       
   925             getNativeToFlavor().put(nat, flavors);
       
   926         }
       
   927         flavors.add(flav);
       
   928         flavorsForNativeCache.remove(nat);
       
   929     }
       
   930 
       
   931     /**
       
   932      * Discards the current mappings for the specified <code>String</code>
       
   933      * native, and creates new mappings to the specified
       
   934      * <code>DataFlavor</code>s. Unlike <code>getFlavorsForNative</code>, the
       
   935      * mappings will only be established in one direction, and the natives need
       
   936      * not be encoded. To establish two-way mappings, call
       
   937      * <code>setNativesForFlavor</code> as well. The first
       
   938      * <code>DataFlavor</code> in the array will represent the highest priority
       
   939      * mapping. Subsequent <code>DataFlavor</code>s will represent mappings of
       
   940      * decreasing priority.
       
   941      * <p>
       
   942      * If the array contains several elements that reference equal
       
   943      * <code>DataFlavor</code>s, this method will establish new mappings
       
   944      * for the first of those elements and ignore the rest of them.
       
   945      * <p>
       
   946      * It is recommended that client code not reset mappings established by the
       
   947      * data transfer subsystem. This method should only be used for
       
   948      * application-level mappings.
       
   949      *
       
   950      * @param nat the <code>String</code> native key for the mappings
       
   951      * @param flavors the <code>DataFlavor</code> values for the mappings
       
   952      * @throws NullPointerException if nat or flavors is <code>null</code>
       
   953      *         or if flavors contains <code>null</code> elements
       
   954      *
       
   955      * @see #setNativesForFlavor
       
   956      * @since 1.4
       
   957      */
       
   958     public synchronized void setFlavorsForNative(String nat,
       
   959                                                  DataFlavor[] flavors) {
       
   960         Objects.requireNonNull(nat, "Null native not permitted");
       
   961         Objects.requireNonNull(flavors, "Null flavors not permitted");
       
   962 
       
   963         getNativeToFlavor().remove(nat);
       
   964         for (DataFlavor flavor : flavors) {
       
   965             addFlavorForUnencodedNative(nat, flavor);
       
   966         }
       
   967         disabledMappingGenerationKeys.add(nat);
       
   968         flavorsForNativeCache.remove(nat);
       
   969     }
       
   970 
       
   971     /**
       
   972      * Encodes a MIME type for use as a <code>String</code> native. The format
       
   973      * of an encoded representation of a MIME type is implementation-dependent.
       
   974      * The only restrictions are:
       
   975      * <ul>
       
   976      * <li>The encoded representation is <code>null</code> if and only if the
       
   977      * MIME type <code>String</code> is <code>null</code>.</li>
       
   978      * <li>The encoded representations for two non-<code>null</code> MIME type
       
   979      * <code>String</code>s are equal if and only if these <code>String</code>s
       
   980      * are equal according to <code>String.equals(Object)</code>.</li>
       
   981      * </ul>
       
   982      * <p>
       
   983      * The reference implementation of this method returns the specified MIME
       
   984      * type <code>String</code> prefixed with <code>JAVA_DATAFLAVOR:</code>.
       
   985      *
       
   986      * @param mimeType the MIME type to encode
       
   987      * @return the encoded <code>String</code>, or <code>null</code> if
       
   988      *         mimeType is <code>null</code>
       
   989      */
       
   990     public static String encodeJavaMIMEType(String mimeType) {
       
   991         return (mimeType != null)
       
   992             ? JavaMIME + mimeType
       
   993             : null;
       
   994     }
       
   995 
       
   996     /**
       
   997      * Encodes a <code>DataFlavor</code> for use as a <code>String</code>
       
   998      * native. The format of an encoded <code>DataFlavor</code> is
       
   999      * implementation-dependent. The only restrictions are:
       
  1000      * <ul>
       
  1001      * <li>The encoded representation is <code>null</code> if and only if the
       
  1002      * specified <code>DataFlavor</code> is <code>null</code> or its MIME type
       
  1003      * <code>String</code> is <code>null</code>.</li>
       
  1004      * <li>The encoded representations for two non-<code>null</code>
       
  1005      * <code>DataFlavor</code>s with non-<code>null</code> MIME type
       
  1006      * <code>String</code>s are equal if and only if the MIME type
       
  1007      * <code>String</code>s of these <code>DataFlavor</code>s are equal
       
  1008      * according to <code>String.equals(Object)</code>.</li>
       
  1009      * </ul>
       
  1010      * <p>
       
  1011      * The reference implementation of this method returns the MIME type
       
  1012      * <code>String</code> of the specified <code>DataFlavor</code> prefixed
       
  1013      * with <code>JAVA_DATAFLAVOR:</code>.
       
  1014      *
       
  1015      * @param flav the <code>DataFlavor</code> to encode
       
  1016      * @return the encoded <code>String</code>, or <code>null</code> if
       
  1017      *         flav is <code>null</code> or has a <code>null</code> MIME type
       
  1018      */
       
  1019     public static String encodeDataFlavor(DataFlavor flav) {
       
  1020         return (flav != null)
       
  1021             ? SystemFlavorMap.encodeJavaMIMEType(flav.getMimeType())
       
  1022             : null;
       
  1023     }
       
  1024 
       
  1025     /**
       
  1026      * Returns whether the specified <code>String</code> is an encoded Java
       
  1027      * MIME type.
       
  1028      *
       
  1029      * @param str the <code>String</code> to test
       
  1030      * @return <code>true</code> if the <code>String</code> is encoded;
       
  1031      *         <code>false</code> otherwise
       
  1032      */
       
  1033     public static boolean isJavaMIMEType(String str) {
       
  1034         return (str != null && str.startsWith(JavaMIME, 0));
       
  1035     }
       
  1036 
       
  1037     /**
       
  1038      * Decodes a <code>String</code> native for use as a Java MIME type.
       
  1039      *
       
  1040      * @param nat the <code>String</code> to decode
       
  1041      * @return the decoded Java MIME type, or <code>null</code> if nat is not
       
  1042      *         an encoded <code>String</code> native
       
  1043      */
       
  1044     public static String decodeJavaMIMEType(String nat) {
       
  1045         return (isJavaMIMEType(nat))
       
  1046             ? nat.substring(JavaMIME.length(), nat.length()).trim()
       
  1047             : null;
       
  1048     }
       
  1049 
       
  1050     /**
       
  1051      * Decodes a <code>String</code> native for use as a
       
  1052      * <code>DataFlavor</code>.
       
  1053      *
       
  1054      * @param nat the <code>String</code> to decode
       
  1055      * @return the decoded <code>DataFlavor</code>, or <code>null</code> if
       
  1056      *         nat is not an encoded <code>String</code> native
       
  1057      * @throws ClassNotFoundException if the class of the data flavor
       
  1058      * is not loaded
       
  1059      */
       
  1060     public static DataFlavor decodeDataFlavor(String nat)
       
  1061         throws ClassNotFoundException
       
  1062     {
       
  1063         String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat);
       
  1064         return (retval_str != null)
       
  1065             ? new DataFlavor(retval_str)
       
  1066             : null;
       
  1067     }
       
  1068 
       
  1069     private static final class SoftCache<K, V> {
       
  1070         Map<K, SoftReference<LinkedHashSet<V>>> cache;
       
  1071 
       
  1072         public void put(K key, LinkedHashSet<V> value) {
       
  1073             if (cache == null) {
       
  1074                 cache = new HashMap<>(1);
       
  1075             }
       
  1076             cache.put(key, new SoftReference<>(value));
       
  1077         }
       
  1078 
       
  1079         public void remove(K key) {
       
  1080             if (cache == null) return;
       
  1081             cache.remove(null);
       
  1082             cache.remove(key);
       
  1083         }
       
  1084 
       
  1085         public LinkedHashSet<V> check(K key) {
       
  1086             if (cache == null) return null;
       
  1087             SoftReference<LinkedHashSet<V>> ref = cache.get(key);
       
  1088             if (ref != null) {
       
  1089                 return ref.get();
       
  1090             }
       
  1091             return null;
       
  1092         }
       
  1093     }
       
  1094 }