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