8027148: SystemFlavorMap.getNativesForFlavor returns list of native formats in incorrect order
authorpchelko
Wed, 23 Apr 2014 17:56:05 +0400
changeset 24529 c580bcb3aabc
parent 24528 21c5bb3d76cc
child 24530 479661529a54
8027148: SystemFlavorMap.getNativesForFlavor returns list of native formats in incorrect order Reviewed-by: anthony, serb
jdk/src/share/classes/java/awt/datatransfer/SystemFlavorMap.java
jdk/src/share/classes/sun/awt/datatransfer/DataTransferer.java
jdk/src/solaris/classes/sun/awt/X11/XDataTransferer.java
jdk/test/java/awt/datatransfer/MappingGenerationTest/MappingGenerationTest.java
--- a/jdk/src/share/classes/java/awt/datatransfer/SystemFlavorMap.java	Tue Apr 22 17:55:25 2014 -0700
+++ b/jdk/src/share/classes/java/awt/datatransfer/SystemFlavorMap.java	Wed Apr 23 17:56:05 2014 +0400
@@ -38,13 +38,14 @@
 import java.net.MalformedURLException;
 
 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;
-import java.util.WeakHashMap;
 
 import sun.awt.AppContext;
 import sun.awt.datatransfer.DataTransferer;
@@ -102,19 +103,11 @@
     private static final String HTML_TEXT_BASE_TYPE = "text/html";
 
     /**
-     * This constant is passed to flavorToNativeLookup() to indicate that a
-     * a native should be synthesized, stored, and returned by encoding the
-     * DataFlavor's MIME type in case if the DataFlavor is not found in
-     * 'flavorToNative' map.
-     */
-    private static final boolean SYNTHESIZE_IF_NOT_FOUND = true;
-
-    /**
      * Maps native Strings to Lists of DataFlavors (or base type Strings for
      * text DataFlavors).
      * Do not use the field directly, use getNativeToFlavor() instead.
      */
-    private final Map<String, List<DataFlavor>> nativeToFlavor = new HashMap<>();
+    private final Map<String, LinkedHashSet<DataFlavor>> nativeToFlavor = new HashMap<>();
 
     /**
      * Accessor to nativeToFlavor map.  Since we use lazy initialization we must
@@ -123,7 +116,7 @@
      *
      * @return nativeToFlavor
      */
-    private Map<String, List<DataFlavor>> getNativeToFlavor() {
+    private Map<String, LinkedHashSet<DataFlavor>> getNativeToFlavor() {
         if (!isMapInitialized) {
             initSystemFlavorMap();
         }
@@ -135,7 +128,7 @@
      * native Strings.
      * Do not use the field directly, use getFlavorToNative() instead.
      */
-    private final Map<DataFlavor, List<String>> flavorToNative = new HashMap<>();
+    private final Map<DataFlavor, LinkedHashSet<String>> flavorToNative = new HashMap<>();
 
     /**
      * Accessor to flavorToNative map.  Since we use lazy initialization we must
@@ -144,7 +137,7 @@
      *
      * @return flavorToNative
      */
-    private synchronized Map<DataFlavor, List<String>> getFlavorToNative() {
+    private synchronized Map<DataFlavor, LinkedHashSet<String>> getFlavorToNative() {
         if (!isMapInitialized) {
             initSystemFlavorMap();
         }
@@ -152,21 +145,44 @@
     }
 
     /**
+     * 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<String, LinkedHashSet<String>> 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<String, LinkedHashSet<String>> 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 Lists of String natives.
+     * SoftReferences which reference LinkedHashSet of String natives.
      */
-    private Map<DataFlavor, SoftReference<List<String>>> getNativesForFlavorCache = new HashMap<>();
+    private final SoftCache<DataFlavor, String> nativesForFlavorCache = new SoftCache<>();
 
     /**
      * Caches the result getFlavorsForNative(). Maps String natives to
-     * SoftReferences which reference Lists of DataFlavors.
+     * SoftReferences which reference LinkedHashSet of DataFlavors.
      */
-    private Map<String, SoftReference<List<DataFlavor>>> getFlavorsForNativeCache = new HashMap<>();
+    private final SoftCache<String, DataFlavor> flavorsForNativeCache = new SoftCache<>();
 
     /**
      * Dynamic mapping generation used for text mappings should not be applied
@@ -174,7 +190,7 @@
      * explicitly specified with setFlavorsForNative() or
      * setNativesForFlavor(). This keeps all such keys.
      */
-    private Set disabledMappingGenerationKeys = new HashSet();
+    private Set<Object> disabledMappingGenerationKeys = new HashSet<>();
 
     /**
      * Returns the default FlavorMap for this thread's ClassLoader.
@@ -404,7 +420,7 @@
                         flavor = new DataFlavor(value);
                     } catch (Exception e) {
                         try {
-                            flavor = new DataFlavor(value, (String)null);
+                            flavor = new DataFlavor(value, null);
                         } catch (Exception ee) {
                             ee.printStackTrace();
                             continue;
@@ -412,11 +428,11 @@
                     }
 
                     final LinkedHashSet<DataFlavor> 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) {
@@ -505,10 +521,10 @@
      * the appropriate Map location, but rather will be appended to a List
      * stored in that location.
      */
-    private <H, L> void store(H hashed, L listed, Map<H, List<L>> map) {
-        List<L> list = map.get(hashed);
+    private <H, L> void store(H hashed, L listed, Map<H, LinkedHashSet<L>> map) {
+        LinkedHashSet<L> list = map.get(hashed);
         if (list == null) {
-            list = new ArrayList<>(1);
+            list = new LinkedHashSet<>(1);
             map.put(hashed, list);
         }
         if (!list.contains(listed)) {
@@ -522,17 +538,16 @@
      * case, a new DataFlavor is synthesized, stored, and returned, if and
      * only if the specified native is encoded as a Java MIME type.
      */
-    private List<DataFlavor> nativeToFlavorLookup(String nat) {
-        List<DataFlavor> flavors = getNativeToFlavor().get(nat);
+    private LinkedHashSet<DataFlavor> nativeToFlavorLookup(String nat) {
+        LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat);
 
         if (nat != null && !disabledMappingGenerationKeys.contains(nat)) {
             DataTransferer transferer = DataTransferer.getInstance();
             if (transferer != null) {
-                List<DataFlavor> platformFlavors =
+                LinkedHashSet<DataFlavor> platformFlavors =
                         transferer.getPlatformMappingsForNative(nat);
                 if (!platformFlavors.isEmpty()) {
                     if (flavors != null) {
-                        platformFlavors.removeAll(new HashSet<>(flavors));
                         // Prepending the platform-specific mappings ensures
                         // that the flavors added with
                         // addFlavorForUnencodedNative() are at the end of
@@ -558,24 +573,22 @@
             }
 
             if (flavor != null) {
-                flavors = new ArrayList<>(1);
+                flavors = new LinkedHashSet<>(1);
                 getNativeToFlavor().put(nat, flavors);
                 flavors.add(flavor);
-                getFlavorsForNativeCache.remove(nat);
-                getFlavorsForNativeCache.remove(null);
+                flavorsForNativeCache.remove(nat);
 
-                List<String> natives = getFlavorToNative().get(flavor);
+                LinkedHashSet<String> natives = getFlavorToNative().get(flavor);
                 if (natives == null) {
-                    natives = new ArrayList<>(1);
+                    natives = new LinkedHashSet<>(1);
                     getFlavorToNative().put(flavor, natives);
                 }
                 natives.add(nat);
-                getNativesForFlavorCache.remove(flavor);
-                getNativesForFlavorCache.remove(null);
+                nativesForFlavorCache.remove(flavor);
             }
         }
 
-        return (flavors != null) ? flavors : new ArrayList<>(0);
+        return (flavors != null) ? flavors : new LinkedHashSet<>(0);
     }
 
     /**
@@ -586,18 +599,18 @@
      * encoding the DataFlavor's MIME type. Otherwise an empty List is returned
      * and 'flavorToNative' remains unaffected.
      */
-    private List<String> flavorToNativeLookup(final DataFlavor flav,
-                                              final boolean synthesize) {
-        List<String> natives = getFlavorToNative().get(flav);
+    private LinkedHashSet<String> flavorToNativeLookup(final DataFlavor flav,
+                                                       final boolean synthesize) {
+
+        LinkedHashSet<String> natives = getFlavorToNative().get(flav);
 
         if (flav != null && !disabledMappingGenerationKeys.contains(flav)) {
             DataTransferer transferer = DataTransferer.getInstance();
             if (transferer != null) {
-                List<String> platformNatives =
+                LinkedHashSet<String> platformNatives =
                     transferer.getPlatformMappingsForFlavor(flav);
                 if (!platformNatives.isEmpty()) {
                     if (natives != null) {
-                        platformNatives.removeAll(new HashSet<>(natives));
                         // Prepend the platform-specific mappings to ensure
                         // that the natives added with
                         // addUnencodedNativeForFlavor() are at the end of
@@ -612,26 +625,25 @@
         if (natives == null) {
             if (synthesize) {
                 String encoded = encodeDataFlavor(flav);
-                natives = new ArrayList<>(1);
+                natives = new LinkedHashSet<>(1);
                 getFlavorToNative().put(flav, natives);
                 natives.add(encoded);
-                getNativesForFlavorCache.remove(flav);
-                getNativesForFlavorCache.remove(null);
 
-                List<DataFlavor> flavors = getNativeToFlavor().get(encoded);
+                LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(encoded);
                 if (flavors == null) {
-                    flavors = new ArrayList<>(1);
+                    flavors = new LinkedHashSet<>(1);
                     getNativeToFlavor().put(encoded, flavors);
                 }
                 flavors.add(flav);
-                getFlavorsForNativeCache.remove(encoded);
-                getFlavorsForNativeCache.remove(null);
+
+                nativesForFlavorCache.remove(flav);
+                flavorsForNativeCache.remove(encoded);
             } else {
-                natives = new ArrayList<>(0);
+                natives = new LinkedHashSet<>(0);
             }
         }
 
-        return natives;
+        return new LinkedHashSet<>(natives);
     }
 
     /**
@@ -659,103 +671,63 @@
      * @see #encodeDataFlavor
      * @since 1.4
      */
+    @Override
     public synchronized List<String> getNativesForFlavor(DataFlavor flav) {
-        List<String> retval = null;
-
-        // Check cache, even for null flav
-        SoftReference<List<String>> ref = getNativesForFlavorCache.get(flav);
-        if (ref != null) {
-            retval = ref.get();
-            if (retval != null) {
-                // Create a copy, because client code can modify the returned
-                // list.
-                return new ArrayList<>(retval);
-            }
+        LinkedHashSet<String> retval = nativesForFlavorCache.check(flav);
+        if (retval != null) {
+            return new ArrayList<>(retval);
         }
 
         if (flav == null) {
-            retval = new ArrayList<>(getNativeToFlavor().keySet());
+            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, !SYNTHESIZE_IF_NOT_FOUND);
+            retval = flavorToNativeLookup(flav, false);
         } else if (DataTransferer.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())) {
-                retval = getAllNativesForType(flav.mimeType.getBaseType());
-                if (retval != null) {
-                    // To prevent the List stored in the map from modification.
-                    retval = new ArrayList(retval);
+                LinkedHashSet<String> textTypeNatives =
+                        getTextTypeToNative().get(flav.mimeType.getBaseType());
+                if (textTypeNatives != null) {
+                    retval.addAll(textTypeNatives);
                 }
             }
 
             // Also include text/plain natives, but don't duplicate Strings
-            List<String> textPlainList = getAllNativesForType(TEXT_PLAIN_BASE_TYPE);
-
-            if (textPlainList != null && !textPlainList.isEmpty()) {
-                // To prevent the List stored in the map from modification.
-                // This also guarantees that removeAll() is supported.
-                textPlainList = new ArrayList<>(textPlainList);
-                if (retval != null && !retval.isEmpty()) {
-                    // Use HashSet to get constant-time performance for search.
-                    textPlainList.removeAll(new HashSet<>(retval));
-                    retval.addAll(textPlainList);
-                } else {
-                    retval = textPlainList;
-                }
+            LinkedHashSet<String> textTypeNatives =
+                    getTextTypeToNative().get(TEXT_PLAIN_BASE_TYPE);
+            if (textTypeNatives != null) {
+                retval.addAll(textTypeNatives);
             }
 
-            if (retval == null || retval.isEmpty()) {
-                retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
+            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.
-                List<String> explicitList =
-                    flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
-
-                // flavorToNativeLookup() never returns null.
-                // It can return an empty List, however.
-                if (!explicitList.isEmpty()) {
-                    // To prevent the List stored in the map from modification.
-                    // This also guarantees that removeAll() is supported.
-                    explicitList = new ArrayList<>(explicitList);
-                    // Use HashSet to get constant-time performance for search.
-                    explicitList.removeAll(new HashSet<>(retval));
-                    retval.addAll(explicitList);
-                }
+                retval.addAll(flavorToNativeLookup(flav, false));
             }
         } else if (DataTransferer.isFlavorNoncharsetTextType(flav)) {
-            retval = getAllNativesForType(flav.mimeType.getBaseType());
+            retval = getTextTypeToNative().get(flav.mimeType.getBaseType());
 
             if (retval == null || retval.isEmpty()) {
-                retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
+                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.
-                List<String> explicitList =
-                    flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
-
-                // flavorToNativeLookup() never returns null.
-                // It can return an empty List, however.
-                if (!explicitList.isEmpty()) {
-                    // To prevent the List stored in the map from modification.
-                    // This also guarantees that add/removeAll() are supported.
-                    retval = new ArrayList<>(retval);
-                    explicitList = new ArrayList<>(explicitList);
-                    // Use HashSet to get constant-time performance for search.
-                    explicitList.removeAll(new HashSet<>(retval));
-                    retval.addAll(explicitList);
-                }
+                retval.addAll(flavorToNativeLookup(flav, false));
             }
         } else {
-            retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
+            retval = flavorToNativeLookup(flav, true);
         }
 
-        getNativesForFlavorCache.put(flav, new SoftReference<>(retval));
+        nativesForFlavorCache.put(flav, retval);
         // Create a copy, because client code can modify the returned list.
         return new ArrayList<>(retval);
     }
@@ -791,62 +763,38 @@
      * @see #encodeJavaMIMEType
      * @since 1.4
      */
+    @Override
     public synchronized List<DataFlavor> getFlavorsForNative(String nat) {
-
-        // Check cache, even for null nat
-        SoftReference<List<DataFlavor>> ref = getFlavorsForNativeCache.get(nat);
-        if (ref != null) {
-            List<DataFlavor> retval = ref.get();
-            if (retval != null) {
-                return new ArrayList<>(retval);
-            }
+        LinkedHashSet<DataFlavor> returnValue = flavorsForNativeCache.check(nat);
+        if (returnValue != null) {
+            return new ArrayList<>(returnValue);
+        } else {
+            returnValue = new LinkedHashSet<>();
         }
 
-        final LinkedHashSet <DataFlavor> returnValue =
-            new LinkedHashSet<>();
-
         if (nat == null) {
-            final List<String> natives = getNativesForFlavor(null);
-
-            for (String n : natives)
-            {
-                final List<DataFlavor> flavors = getFlavorsForNative(n);
-
-                for (DataFlavor df : flavors)
-                {
-                    returnValue.add(df);
-                }
+            for (String n : getNativesForFlavor(null)) {
+                returnValue.addAll(getFlavorsForNative(n));
             }
         } else {
-
-            final List<DataFlavor> flavors = nativeToFlavorLookup(nat);
-
+            final LinkedHashSet<DataFlavor> flavors = nativeToFlavorLookup(nat);
             if (disabledMappingGenerationKeys.contains(nat)) {
-                return flavors;
+                return new ArrayList<>(flavors);
             }
 
-            final List<DataFlavor> flavorsAndBaseTypes =
-                nativeToFlavorLookup(nat);
+            final LinkedHashSet<DataFlavor> flavorsWithSynthesized =
+                    nativeToFlavorLookup(nat);
 
-            for (DataFlavor df : flavorsAndBaseTypes) {
+            for (DataFlavor df : flavorsWithSynthesized) {
                 returnValue.add(df);
                 if ("text".equals(df.getPrimaryType())) {
-                    try {
-                        returnValue.addAll(
-                                convertMimeTypeToDataFlavors(
-                                        new MimeType(df.getMimeType()
-                                        ).getBaseType()));
-                    } catch (MimeTypeParseException e) {
-                        e.printStackTrace();
-                    }
+                    String baseType = df.mimeType.getBaseType();
+                    returnValue.addAll(convertMimeTypeToDataFlavors(baseType));
                 }
             }
-
         }
-
-        final List<DataFlavor> arrayList = new ArrayList<>(returnValue);
-        getFlavorsForNativeCache.put(nat, new SoftReference<>(arrayList));
-        return new ArrayList<>(arrayList);
+        flavorsForNativeCache.put(nat, returnValue);
+        return new ArrayList<>(returnValue);
     }
 
     private static Set<DataFlavor> convertMimeTypeToDataFlavors(
@@ -862,7 +810,6 @@
         } catch (MimeTypeParseException mtpe) {
             // Cannot happen, since we checked all mappings
             // on load from flavormap.properties.
-            assert(false);
         }
 
         if (DataTransferer.doesSubtypeSupportCharset(subType, null)) {
@@ -941,10 +888,10 @@
     }
 
     private static final String [] htmlDocumntTypes =
-        new String [] {"all", "selection", "fragment"};
+            new String [] {"all", "selection", "fragment"};
 
-    private static LinkedHashSet<String> handleHtmlMimeTypes(
-        String baseType, String mimeType) {
+    private static LinkedHashSet<String> handleHtmlMimeTypes(String baseType,
+                                                             String mimeType) {
 
         LinkedHashSet<String> returnValues = new LinkedHashSet<>();
 
@@ -981,14 +928,14 @@
      * @see #getNativesForFlavor
      * @see #encodeDataFlavor
      */
-    public synchronized Map<DataFlavor,String>
-        getNativesForFlavors(DataFlavor[] flavors)
+    @Override
+    public synchronized Map<DataFlavor,String> getNativesForFlavors(DataFlavor[] flavors)
     {
         // Use getNativesForFlavor to generate extra natives for text flavors
         // and stringFlavor
 
         if (flavors == null) {
-            List flavor_list = getFlavorsForNative(null);
+            List<DataFlavor> flavor_list = getFlavorsForNative(null);
             flavors = new DataFlavor[flavor_list.size()];
             flavor_list.toArray(flavors);
         }
@@ -1027,15 +974,14 @@
      * @see #getFlavorsForNative
      * @see #encodeJavaMIMEType
      */
-    public synchronized Map<String,DataFlavor>
-        getFlavorsForNatives(String[] natives)
+    @Override
+    public synchronized Map<String,DataFlavor> getFlavorsForNatives(String[] natives)
     {
         // Use getFlavorsForNative to generate extra flavors for text natives
-
         if (natives == null) {
-            List native_list = getNativesForFlavor(null);
-            natives = new String[native_list.size()];
-            native_list.toArray(natives);
+            List<String> nativesList = getNativesForFlavor(null);
+            natives = new String[nativesList.size()];
+            nativesList.toArray(natives);
         }
 
         Map<String, DataFlavor> retval = new HashMap<>(natives.length, 1.0f);
@@ -1044,7 +990,6 @@
             DataFlavor flav = (flavors.isEmpty())? null : flavors.get(0);
             retval.put(aNative, flav);
         }
-
         return retval;
     }
 
@@ -1070,20 +1015,16 @@
      */
     public synchronized void addUnencodedNativeForFlavor(DataFlavor flav,
                                                          String nat) {
-        if (flav == null || nat == null) {
-            throw new NullPointerException("null arguments not permitted");
-        }
+        Objects.requireNonNull(nat, "Null native not permitted");
+        Objects.requireNonNull(flav, "Null flavor not permitted");
 
-        List<String> natives = getFlavorToNative().get(flav);
+        LinkedHashSet<String> natives = getFlavorToNative().get(flav);
         if (natives == null) {
-            natives = new ArrayList<>(1);
+            natives = new LinkedHashSet<>(1);
             getFlavorToNative().put(flav, natives);
-        } else if (natives.contains(nat)) {
-            return;
         }
         natives.add(nat);
-        getNativesForFlavorCache.remove(flav);
-        getNativesForFlavorCache.remove(null);
+        nativesForFlavorCache.remove(flav);
     }
 
     /**
@@ -1116,18 +1057,15 @@
      */
     public synchronized void setNativesForFlavor(DataFlavor flav,
                                                  String[] natives) {
-        if (flav == null || natives == null) {
-            throw new NullPointerException("null arguments not permitted");
-        }
+        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);
-        // Clear the cache to handle the case of empty natives.
-        getNativesForFlavorCache.remove(flav);
-        getNativesForFlavorCache.remove(null);
+        nativesForFlavorCache.remove(flav);
     }
 
     /**
@@ -1150,20 +1088,16 @@
      */
     public synchronized void addFlavorForUnencodedNative(String nat,
                                                          DataFlavor flav) {
-        if (nat == null || flav == null) {
-            throw new NullPointerException("null arguments not permitted");
-        }
+        Objects.requireNonNull(nat, "Null native not permitted");
+        Objects.requireNonNull(flav, "Null flavor not permitted");
 
-        List<DataFlavor> flavors = getNativeToFlavor().get(nat);
+        LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat);
         if (flavors == null) {
-            flavors = new ArrayList<>(1);
+            flavors = new LinkedHashSet<>(1);
             getNativeToFlavor().put(nat, flavors);
-        } else if (flavors.contains(flav)) {
-            return;
         }
         flavors.add(flav);
-        getFlavorsForNativeCache.remove(nat);
-        getFlavorsForNativeCache.remove(null);
+        flavorsForNativeCache.remove(nat);
     }
 
     /**
@@ -1195,18 +1129,15 @@
      */
     public synchronized void setFlavorsForNative(String nat,
                                                  DataFlavor[] flavors) {
-        if (nat == null || flavors == null) {
-            throw new NullPointerException("null arguments not permitted");
-        }
+        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);
-        // Clear the cache to handle the case of empty flavors.
-        getFlavorsForNativeCache.remove(nat);
-        getFlavorsForNativeCache.remove(null);
+        flavorsForNativeCache.remove(nat);
     }
 
     /**
@@ -1307,17 +1238,29 @@
             : null;
     }
 
-    private List<String> getAllNativesForType(String type) {
-        Set<String> retval = null;
-        for (DataFlavor dataFlavor : convertMimeTypeToDataFlavors(type)) {
-            List<String> natives = getFlavorToNative().get(dataFlavor);
-            if (natives != null && !natives.isEmpty()) {
-                if (retval == null) {
-                    retval = new LinkedHashSet<>();
-                }
-                retval.addAll(natives);
+    private static final class SoftCache<K, V> {
+        Map<K, SoftReference<LinkedHashSet<V>>> cache;
+
+        public void put(K key, LinkedHashSet<V> value) {
+            if (cache == null) {
+                cache = new HashMap<>(1);
             }
+            cache.put(key, new SoftReference<>(value));
         }
-        return retval == null ? null : new ArrayList<>(retval);
+
+        public void remove(K key) {
+            if (cache == null) return;
+            cache.remove(null);
+            cache.remove(key);
+        }
+
+        public LinkedHashSet<V> check(K key) {
+            if (cache == null) return null;
+            SoftReference<LinkedHashSet<V>> ref = cache.get(key);
+            if (ref != null) {
+                return ref.get();
+            }
+            return null;
+        }
     }
 }
--- a/jdk/src/share/classes/sun/awt/datatransfer/DataTransferer.java	Tue Apr 22 17:55:25 2014 -0700
+++ b/jdk/src/share/classes/sun/awt/datatransfer/DataTransferer.java	Wed Apr 23 17:56:05 2014 +0400
@@ -70,22 +70,7 @@
 import java.security.PrivilegedExceptionAction;
 import java.security.ProtectionDomain;
 
-import java.util.AbstractMap;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.SortedSet;
-import java.util.Set;
-import java.util.Stack;
-import java.util.TreeMap;
-import java.util.TreeSet;
+import java.util.*;
 
 import sun.util.logging.PlatformLogger;
 
@@ -2267,8 +2252,8 @@
      * If there are no platform-specific mappings for this native, the method
      * returns an empty <code>List</code>.
      */
-    public List<DataFlavor> getPlatformMappingsForNative(String nat) {
-        return new ArrayList<>();
+    public LinkedHashSet<DataFlavor> getPlatformMappingsForNative(String nat) {
+        return new LinkedHashSet<>();
     }
 
     /**
@@ -2276,8 +2261,8 @@
      * If there are no platform-specific mappings for this flavor, the method
      * returns an empty <code>List</code>.
      */
-    public List<String> getPlatformMappingsForFlavor(DataFlavor df) {
-        return new ArrayList<>();
+    public LinkedHashSet<String> getPlatformMappingsForFlavor(DataFlavor df) {
+        return new LinkedHashSet<>();
     }
 
     /**
--- a/jdk/src/solaris/classes/sun/awt/X11/XDataTransferer.java	Tue Apr 22 17:55:25 2014 -0700
+++ b/jdk/src/solaris/classes/sun/awt/X11/XDataTransferer.java	Wed Apr 23 17:56:05 2014 +0400
@@ -45,6 +45,7 @@
 
 import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 
 import javax.imageio.ImageIO;
@@ -330,8 +331,8 @@
      * type can be translated by the Data Transfer subsystem.
      */
     @Override
-    public List<DataFlavor> getPlatformMappingsForNative(String nat) {
-        List<DataFlavor> flavors = new ArrayList<>();
+    public LinkedHashSet<DataFlavor> getPlatformMappingsForNative(String nat) {
+        LinkedHashSet<DataFlavor> flavors = new LinkedHashSet<>();
 
         if (nat == null) {
             return flavors;
@@ -392,8 +393,8 @@
      * Transfer subsystem.
      */
     @Override
-    public List<String> getPlatformMappingsForFlavor(DataFlavor df) {
-        List<String> natives = new ArrayList<>(1);
+    public LinkedHashSet<String> getPlatformMappingsForFlavor(DataFlavor df) {
+        LinkedHashSet<String> natives = new LinkedHashSet<>(1);
 
         if (df == null) {
             return natives;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/datatransfer/MappingGenerationTest/MappingGenerationTest.java	Wed Apr 23 17:56:05 2014 +0400
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 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.
+ *
+ * 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.
+ */
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.SystemFlavorMap;
+import java.util.List;
+
+/*
+  @test
+  @bug 4512530 8027148
+  @summary tests that mappings for text flavors are generated properly
+  @author das@sparc.spb.su area=datatransfer
+*/
+
+public class MappingGenerationTest {
+
+    private static final SystemFlavorMap fm =
+        (SystemFlavorMap)SystemFlavorMap.getDefaultFlavorMap();
+
+    public static void main(String[] args)  {
+        test1();
+        test2();
+        test3();
+        test4();
+        test5();
+        test6();
+    }
+
+    /**
+     * Verifies that Lists returned from getNativesForFlavor() and
+     * getFlavorsForNative() are not modified with a subsequent call
+     * to addUnencodedNativeForFlavor() and addFlavorForUnencodedNative()
+     * respectively.
+     */
+    public static void test1() {
+        DataFlavor df = new DataFlavor("text/plain-test1", null);
+        String nat = "native1";
+
+        List<String> natives = fm.getNativesForFlavor(df);
+        fm.addUnencodedNativeForFlavor(df, nat);
+        List<String> nativesNew = fm.getNativesForFlavor(df);
+        if (natives.equals(nativesNew)) {
+            System.err.println("orig=" + natives);
+            System.err.println("new=" + nativesNew);
+            throw new RuntimeException("Test failed");
+        }
+
+        List<DataFlavor> flavors = fm.getFlavorsForNative(nat);
+        fm.addFlavorForUnencodedNative(nat, df);
+        List<DataFlavor> flavorsNew = fm.getFlavorsForNative(nat);
+        if (flavors.equals(flavorsNew)) {
+            System.err.println("orig=" + flavors);
+            System.err.println("new=" + flavorsNew);
+            throw new RuntimeException("Test failed");
+        }
+    }
+
+    /**
+     * Verifies that SystemFlavorMap is not affected by modification of
+     * the Lists returned from getNativesForFlavor() and
+     * getFlavorsForNative().
+     */
+    public static void test2() {
+        DataFlavor df = new DataFlavor("text/plain-test2", null);
+        String nat = "native2";
+        DataFlavor extraDf = new DataFlavor("text/test", null);
+
+        List<String> natives = fm.getNativesForFlavor(df);
+        natives.add("Should not be here");
+        java.util.List nativesNew = fm.getNativesForFlavor(df);
+        if (natives.equals(nativesNew)) {
+            System.err.println("orig=" + natives);
+            System.err.println("new=" + nativesNew);
+            throw new RuntimeException("Test failed");
+        }
+
+        List<DataFlavor> flavors = fm.getFlavorsForNative(nat);
+        flavors.add(extraDf);
+        java.util.List flavorsNew = fm.getFlavorsForNative(nat);
+        if (flavors.equals(flavorsNew)) {
+            System.err.println("orig=" + flavors);
+            System.err.println("new=" + flavorsNew);
+            throw new RuntimeException("Test failed");
+        }
+    }
+
+    /**
+     * Verifies that addUnencodedNativeForFlavor() for a particular text flavor
+     * doesn't affect mappings for other flavors.
+     */
+    public static void test3() {
+        DataFlavor df1 = new DataFlavor("text/plain-test3", null);
+        DataFlavor df2 = new DataFlavor("text/plain-test3; charset=Unicode; class=java.io.Reader", null);
+        String nat = "native3";
+        List<String> natives = fm.getNativesForFlavor(df2);
+        fm.addUnencodedNativeForFlavor(df1, nat);
+        List<String> nativesNew = fm.getNativesForFlavor(df2);
+        if (!natives.equals(nativesNew)) {
+            System.err.println("orig=" + natives);
+            System.err.println("new=" + nativesNew);
+            throw new RuntimeException("Test failed");
+        }
+    }
+
+    /**
+     * Verifies that addUnencodedNativeForFlavor() really adds the specified
+     * flavor-to-native mapping to the existing mappings.
+     */
+    public static void test4() {
+        DataFlavor df = new DataFlavor("text/plain-test4; charset=Unicode; class=java.io.Reader", null);
+        String nat = "native4";
+        List<String> natives = fm.getNativesForFlavor(df);
+        if (!natives.contains(nat)) {
+            fm.addUnencodedNativeForFlavor(df, nat);
+            List<String> nativesNew = fm.getNativesForFlavor(df);
+            natives.add(nat);
+            if (!natives.equals(nativesNew)) {
+                System.err.println("orig=" + natives);
+                System.err.println("new=" + nativesNew);
+                throw new RuntimeException("Test failed");
+            }
+        }
+    }
+
+    /**
+     * Verifies that a flavor doesn't have any flavor-to-native mappings after
+     * a call to setNativesForFlavor() with this flavor and an empty native
+     * array as arguments.
+     */
+    public static void test5() {
+        final DataFlavor flavor =
+            new DataFlavor("text/plain-TEST5; charset=Unicode", null);
+
+        fm.getNativesForFlavor(flavor);
+
+        fm.setNativesForFlavor(flavor, new String[0]);
+
+        List<String> natives = fm.getNativesForFlavor(flavor);
+
+        if (!natives.isEmpty()) {
+            System.err.println("natives=" + natives);
+            throw new RuntimeException("Test failed");
+        }
+    }
+
+    /**
+     * Verifies that a native doesn't have any native-to-flavor mappings after
+     * a call to setFlavorsForNative() with this native and an empty flavor
+     * array as arguments.
+     */
+    public static void test6() {
+        final String nat = "STRING";
+        fm.getFlavorsForNative(nat);
+        fm.setFlavorsForNative(nat, new DataFlavor[0]);
+
+        List<DataFlavor> flavors = fm.getFlavorsForNative(nat);
+
+        if (!flavors.isEmpty()) {
+            System.err.println("flavors=" + flavors);
+            throw new RuntimeException("Test failed");
+        }
+    }
+}