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