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