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.lang.ref.SoftReference;
  31 
  32 import java.io.BufferedReader;
  33 import java.io.File;
  34 import java.io.InputStreamReader;
  35 import java.io.IOException;
  36 
  37 import java.net.URL;
  38 import java.net.MalformedURLException;
  39 
  40 import java.util.ArrayList;
  41 import java.util.HashMap;
  42 import java.util.HashSet;
  43 import java.util.LinkedHashSet;
  44 import java.util.List;
  45 import java.util.Map;
  46 import java.util.Set;
  47 import java.util.WeakHashMap;
  48 
  49 import sun.awt.datatransfer.DataTransferer;
  50 
  51 /**
  52  * The SystemFlavorMap is a configurable map between "natives" (Strings), which
  53  * correspond to platform-specific data formats, and "flavors" (DataFlavors),
  54  * which correspond to platform-independent MIME types. This mapping is used
  55  * by the data transfer subsystem to transfer data between Java and native
  56  * applications, and between Java applications in separate VMs.
  57  *
  58  * @since 1.2
  59  */
  60 public final class SystemFlavorMap implements FlavorMap, FlavorTable {
  61 
  62     /**
  63      * Constant prefix used to tag Java types converted to native platform
  64      * type.
  65      */
  66     private static String JavaMIME = "JAVA_DATAFLAVOR:";
  67 
  68     /**
  69      * System singleton which maps a thread's ClassLoader to a SystemFlavorMap.
  70      */
  71     private static final WeakHashMap<ClassLoader, FlavorMap> flavorMaps = new WeakHashMap<>();
  72 
  73     /**
  74      * Copied from java.util.Properties.
  75      */
  76     private static final String keyValueSeparators = "=: \t\r\n\f";
  77     private static final String strictKeyValueSeparators = "=:";
  78     private static final String whiteSpaceChars = " \t\r\n\f";
  79 
  80     /**
  81      * The list of valid, decoded text flavor representation classes, in order
  82      * from best to worst.
  83      */
  84     private static final String[] UNICODE_TEXT_CLASSES = {
  85         "java.io.Reader", "java.lang.String", "java.nio.CharBuffer", "\"[C\""
  86     };
  87 
  88     /**
  89      * The list of valid, encoded text flavor representation classes, in order
  90      * from best to worst.
  91      */
  92     private static final String[] ENCODED_TEXT_CLASSES = {
  93         "java.io.InputStream", "java.nio.ByteBuffer", "\"[B\""
  94     };
  95 
  96     /**
  97      * A String representing text/plain MIME type.
  98      */
  99     private static final String TEXT_PLAIN_BASE_TYPE = "text/plain";
 100 
 101     /**
 102      * A String representing text/html MIME type.
 103      */
 104     private static final String HTML_TEXT_BASE_TYPE = "text/html";
 105 
 106     /**
 107      * This constant is passed to flavorToNativeLookup() to indicate that a
 108      * a native should be synthesized, stored, and returned by encoding the
 109      * DataFlavor's MIME type in case if the DataFlavor is not found in
 110      * 'flavorToNative' map.
 111      */
 112     private static final boolean SYNTHESIZE_IF_NOT_FOUND = true;
 113 
 114     /**
 115      * Maps native Strings to Lists of DataFlavors (or base type Strings for
 116      * text DataFlavors).
 117      * Do not use the field directly, use getNativeToFlavor() instead.
 118      */
 119     private final Map<String, List<DataFlavor>> nativeToFlavor = new HashMap<>();
 120 
 121     /**
 122      * Accessor to nativeToFlavor map.  Since we use lazy initialization we must
 123      * use this accessor instead of direct access to the field which may not be
 124      * initialized yet.  This method will initialize the field if needed.
 125      *
 126      * @return nativeToFlavor
 127      */
 128     private Map<String, List<DataFlavor>> getNativeToFlavor() {
 129         if (!isMapInitialized) {
 130             initSystemFlavorMap();
 131         }
 132         return nativeToFlavor;
 133     }
 134 
 135     /**
 136      * Maps DataFlavors (or base type Strings for text DataFlavors) to Lists of
 137      * native Strings.
 138      * Do not use the field directly, use getFlavorToNative() instead.
 139      */
 140     private final Map<DataFlavor, List<String>> flavorToNative = new HashMap<>();
 141 
 142     /**
 143      * Accessor to flavorToNative map.  Since we use lazy initialization we must
 144      * use this accessor instead of direct access to the field which may not be
 145      * initialized yet.  This method will initialize the field if needed.
 146      *
 147      * @return flavorToNative
 148      */
 149     private synchronized Map<DataFlavor, List<String>> getFlavorToNative() {
 150         if (!isMapInitialized) {
 151             initSystemFlavorMap();
 152         }
 153         return flavorToNative;
 154     }
 155 
 156     /**
 157      * Shows if the object has been initialized.
 158      */
 159     private boolean isMapInitialized = false;
 160 
 161     /**
 162      * Caches the result of getNativesForFlavor(). Maps DataFlavors to
 163      * SoftReferences which reference Lists of String natives.
 164      */
 165     private Map<DataFlavor, SoftReference<List<String>>> getNativesForFlavorCache = new HashMap<>();
 166 
 167     /**
 168      * Caches the result getFlavorsForNative(). Maps String natives to
 169      * SoftReferences which reference Lists of DataFlavors.
 170      */
 171     private Map<String, SoftReference<List<DataFlavor>>> getFlavorsForNativeCache = new HashMap<>();
 172 
 173     /**
 174      * Dynamic mapping generation used for text mappings should not be applied
 175      * to the DataFlavors and String natives for which the mappings have been
 176      * explicitly specified with setFlavorsForNative() or
 177      * setNativesForFlavor(). This keeps all such keys.
 178      */
 179     private Set disabledMappingGenerationKeys = new HashSet();
 180 
 181     /**
 182      * Returns the default FlavorMap for this thread's ClassLoader.
 183      * @return the default FlavorMap for this thread's ClassLoader
 184      */
 185     public static FlavorMap getDefaultFlavorMap() {
 186         ClassLoader contextClassLoader =
 187             Thread.currentThread().getContextClassLoader();
 188         if (contextClassLoader == null) {
 189             contextClassLoader = ClassLoader.getSystemClassLoader();
 190         }
 191 
 192         FlavorMap fm;
 193 
 194         synchronized(flavorMaps) {
 195             fm = flavorMaps.get(contextClassLoader);
 196             if (fm == null) {
 197                 fm = new SystemFlavorMap();
 198                 flavorMaps.put(contextClassLoader, fm);
 199             }
 200         }
 201 
 202         return fm;
 203     }
 204 
 205     private SystemFlavorMap() {
 206     }
 207 
 208     /**
 209      * Initializes a SystemFlavorMap by reading flavormap.properties and
 210      * AWT.DnD.flavorMapFileURL.
 211      * For thread-safety must be called under lock on this.
 212      */
 213     private void initSystemFlavorMap() {
 214         if (isMapInitialized) {
 215             return;
 216         }
 217 
 218         isMapInitialized = true;
 219         BufferedReader flavormapDotProperties =
 220             java.security.AccessController.doPrivileged(
 221                 new java.security.PrivilegedAction<BufferedReader>() {
 222                     public BufferedReader run() {
 223                         String fileName =
 224                             System.getProperty("java.home") +
 225                             File.separator +
 226                             "lib" +
 227                             File.separator +
 228                             "flavormap.properties";
 229                         try {
 230                             return new BufferedReader
 231                                 (new InputStreamReader
 232                                     (new File(fileName).toURI().toURL().openStream(), "ISO-8859-1"));
 233                         } catch (MalformedURLException e) {
 234                             System.err.println("MalformedURLException:" + e + " while loading default flavormap.properties file:" + fileName);
 235                         } catch (IOException e) {
 236                             System.err.println("IOException:" + e + " while loading default flavormap.properties file:" + fileName);
 237                         }
 238                         return null;
 239                     }
 240                 });
 241 
 242         BufferedReader flavormapURL =
 243             java.security.AccessController.doPrivileged(
 244                 new java.security.PrivilegedAction<BufferedReader>() {
 245                     public BufferedReader run() {
 246                         String url = Toolkit.getProperty("AWT.DnD.flavorMapFileURL", null);
 247 
 248                         if (url == null) {
 249                             return null;
 250                         }
 251 
 252                         try {
 253                             return new BufferedReader
 254                                 (new InputStreamReader
 255                                     (new URL(url).openStream(), "ISO-8859-1"));
 256                         } catch (MalformedURLException e) {
 257                             System.err.println("MalformedURLException:" + e + " while reading AWT.DnD.flavorMapFileURL:" + url);
 258                         } catch (IOException e) {
 259                             System.err.println("IOException:" + e + " while reading AWT.DnD.flavorMapFileURL:" + url);
 260                         }
 261                         return null;
 262                     }
 263                 });
 264 
 265         if (flavormapDotProperties != null) {
 266             try {
 267                 parseAndStoreReader(flavormapDotProperties);
 268             } catch (IOException e) {
 269                 System.err.println("IOException:" + e + " while parsing default flavormap.properties file");
 270             }
 271         }
 272 
 273         if (flavormapURL != null) {
 274             try {
 275                 parseAndStoreReader(flavormapURL);
 276             } catch (IOException e) {
 277                 System.err.println("IOException:" + e + " while parsing AWT.DnD.flavorMapFileURL");
 278             }
 279         }
 280     }
 281     /**
 282      * Copied code from java.util.Properties. Parsing the data ourselves is the
 283      * only way to handle duplicate keys and values.
 284      */
 285     private void parseAndStoreReader(BufferedReader in) throws IOException {
 286         while (true) {
 287             // Get next line
 288             String line = in.readLine();
 289             if (line == null) {
 290                 return;
 291             }
 292 
 293             if (line.length() > 0) {
 294                 // Continue lines that end in slashes if they are not comments
 295                 char firstChar = line.charAt(0);
 296                 if (firstChar != '#' && firstChar != '!') {
 297                     while (continueLine(line)) {
 298                         String nextLine = in.readLine();
 299                         if (nextLine == null) {
 300                             nextLine = "";
 301                         }
 302                         String loppedLine =
 303                             line.substring(0, line.length() - 1);
 304                         // Advance beyond whitespace on new line
 305                         int startIndex = 0;
 306                         for(; startIndex < nextLine.length(); startIndex++) {
 307                             if (whiteSpaceChars.
 308                                     indexOf(nextLine.charAt(startIndex)) == -1)
 309                             {
 310                                 break;
 311                             }
 312                         }
 313                         nextLine = nextLine.substring(startIndex,
 314                                                       nextLine.length());
 315                         line = loppedLine+nextLine;
 316                     }
 317 
 318                     // Find start of key
 319                     int len = line.length();
 320                     int keyStart = 0;
 321                     for(; keyStart < len; keyStart++) {
 322                         if(whiteSpaceChars.
 323                                indexOf(line.charAt(keyStart)) == -1) {
 324                             break;
 325                         }
 326                     }
 327 
 328                     // Blank lines are ignored
 329                     if (keyStart == len) {
 330                         continue;
 331                     }
 332 
 333                     // Find separation between key and value
 334                     int separatorIndex = keyStart;
 335                     for(; separatorIndex < len; separatorIndex++) {
 336                         char currentChar = line.charAt(separatorIndex);
 337                         if (currentChar == '\\') {
 338                             separatorIndex++;
 339                         } else if (keyValueSeparators.
 340                                        indexOf(currentChar) != -1) {
 341                             break;
 342                         }
 343                     }
 344 
 345                     // Skip over whitespace after key if any
 346                     int valueIndex = separatorIndex;
 347                     for (; valueIndex < len; valueIndex++) {
 348                         if (whiteSpaceChars.
 349                                 indexOf(line.charAt(valueIndex)) == -1) {
 350                             break;
 351                         }
 352                     }
 353 
 354                     // Skip over one non whitespace key value separators if any
 355                     if (valueIndex < len) {
 356                         if (strictKeyValueSeparators.
 357                                 indexOf(line.charAt(valueIndex)) != -1) {
 358                             valueIndex++;
 359                         }
 360                     }
 361 
 362                     // Skip over white space after other separators if any
 363                     while (valueIndex < len) {
 364                         if (whiteSpaceChars.
 365                                 indexOf(line.charAt(valueIndex)) == -1) {
 366                             break;
 367                         }
 368                         valueIndex++;
 369                     }
 370 
 371                     String key = line.substring(keyStart, separatorIndex);
 372                     String value = (separatorIndex < len)
 373                         ? line.substring(valueIndex, len)
 374                         : "";
 375 
 376                     // Convert then store key and value
 377                     key = loadConvert(key);
 378                     value = loadConvert(value);
 379 
 380                     try {
 381                         MimeType mime = new MimeType(value);
 382                         if ("text".equals(mime.getPrimaryType())) {
 383                             String charset = mime.getParameter("charset");
 384                             if (DataTransferer.doesSubtypeSupportCharset
 385                                     (mime.getSubType(), charset))
 386                             {
 387                                 // We need to store the charset and eoln
 388                                 // parameters, if any, so that the
 389                                 // DataTransferer will have this information
 390                                 // for conversion into the native format.
 391                                 DataTransferer transferer =
 392                                     DataTransferer.getInstance();
 393                                 if (transferer != null) {
 394                                     transferer.registerTextFlavorProperties
 395                                         (key, charset,
 396                                          mime.getParameter("eoln"),
 397                                          mime.getParameter("terminators"));
 398                                 }
 399                             }
 400 
 401                             // But don't store any of these parameters in the
 402                             // DataFlavor itself for any text natives (even
 403                             // non-charset ones). The SystemFlavorMap will
 404                             // synthesize the appropriate mappings later.
 405                             mime.removeParameter("charset");
 406                             mime.removeParameter("class");
 407                             mime.removeParameter("eoln");
 408                             mime.removeParameter("terminators");
 409                             value = mime.toString();
 410                         }
 411                     } catch (MimeTypeParseException e) {
 412                         e.printStackTrace();
 413                         continue;
 414                     }
 415 
 416                     DataFlavor flavor;
 417                     try {
 418                         flavor = new DataFlavor(value);
 419                     } catch (Exception e) {
 420                         try {
 421                             flavor = new DataFlavor(value, (String)null);
 422                         } catch (Exception ee) {
 423                             ee.printStackTrace();
 424                             continue;
 425                         }
 426                     }
 427 
 428                     final LinkedHashSet<DataFlavor> dfs = new LinkedHashSet<>();
 429 
 430                     dfs.add(flavor);
 431 
 432                     if ("text".equals(flavor.getPrimaryType())) {
 433                         dfs.addAll(convertMimeTypeToDataFlavors(value));
 434                     }
 435 
 436                     for (DataFlavor df : dfs) {
 437                         store(df, key, getFlavorToNative());
 438                         store(key, df, getNativeToFlavor());
 439                     }
 440                 }
 441             }
 442         }
 443     }
 444 
 445     /**
 446      * Copied from java.util.Properties.
 447      */
 448     private boolean continueLine (String line) {
 449         int slashCount = 0;
 450         int index = line.length() - 1;
 451         while((index >= 0) && (line.charAt(index--) == '\\')) {
 452             slashCount++;
 453         }
 454         return (slashCount % 2 == 1);
 455     }
 456 
 457     /**
 458      * Copied from java.util.Properties.
 459      */
 460     private String loadConvert(String theString) {
 461         char aChar;
 462         int len = theString.length();
 463         StringBuilder outBuffer = new StringBuilder(len);
 464 
 465         for (int x = 0; x < len; ) {
 466             aChar = theString.charAt(x++);
 467             if (aChar == '\\') {
 468                 aChar = theString.charAt(x++);
 469                 if (aChar == 'u') {
 470                     // Read the xxxx
 471                     int value = 0;
 472                     for (int i = 0; i < 4; i++) {
 473                         aChar = theString.charAt(x++);
 474                         switch (aChar) {
 475                           case '0': case '1': case '2': case '3': case '4':
 476                           case '5': case '6': case '7': case '8': case '9': {
 477                              value = (value << 4) + aChar - '0';
 478                              break;
 479                           }
 480                           case 'a': case 'b': case 'c':
 481                           case 'd': case 'e': case 'f': {
 482                              value = (value << 4) + 10 + aChar - 'a';
 483                              break;
 484                           }
 485                           case 'A': case 'B': case 'C':
 486                           case 'D': case 'E': case 'F': {
 487                              value = (value << 4) + 10 + aChar - 'A';
 488                              break;
 489                           }
 490                           default: {
 491                               throw new IllegalArgumentException(
 492                                            "Malformed \\uxxxx encoding.");
 493                           }
 494                         }
 495                     }
 496                     outBuffer.append((char)value);
 497                 } else {
 498                     if (aChar == 't') {
 499                         aChar = '\t';
 500                     } else if (aChar == 'r') {
 501                         aChar = '\r';
 502                     } else if (aChar == 'n') {
 503                         aChar = '\n';
 504                     } else if (aChar == 'f') {
 505                         aChar = '\f';
 506                     }
 507                     outBuffer.append(aChar);
 508                 }
 509             } else {
 510                 outBuffer.append(aChar);
 511             }
 512         }
 513         return outBuffer.toString();
 514     }
 515 
 516     /**
 517      * Stores the listed object under the specified hash key in map. Unlike a
 518      * standard map, the listed object will not replace any object already at
 519      * the appropriate Map location, but rather will be appended to a List
 520      * stored in that location.
 521      */
 522     private <H, L> void store(H hashed, L listed, Map<H, List<L>> map) {
 523         List<L> list = map.get(hashed);
 524         if (list == null) {
 525             list = new ArrayList<>(1);
 526             map.put(hashed, list);
 527         }
 528         if (!list.contains(listed)) {
 529             list.add(listed);
 530         }
 531     }
 532 
 533     /**
 534      * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method
 535      * handles the case where 'nat' is not found in 'nativeToFlavor'. In that
 536      * case, a new DataFlavor is synthesized, stored, and returned, if and
 537      * only if the specified native is encoded as a Java MIME type.
 538      */
 539     private List<DataFlavor> nativeToFlavorLookup(String nat) {
 540         List<DataFlavor> flavors = getNativeToFlavor().get(nat);
 541 
 542         if (nat != null && !disabledMappingGenerationKeys.contains(nat)) {
 543             DataTransferer transferer = DataTransferer.getInstance();
 544             if (transferer != null) {
 545                 List<DataFlavor> platformFlavors =
 546                         transferer.getPlatformMappingsForNative(nat);
 547                 if (!platformFlavors.isEmpty()) {
 548                     if (flavors != null) {
 549                         platformFlavors.removeAll(new HashSet<>(flavors));
 550                         // Prepending the platform-specific mappings ensures
 551                         // that the flavors added with
 552                         // addFlavorForUnencodedNative() are at the end of
 553                         // list.
 554                         platformFlavors.addAll(flavors);
 555                     }
 556                     flavors = platformFlavors;
 557                 }
 558             }
 559         }
 560 
 561         if (flavors == null && isJavaMIMEType(nat)) {
 562             String decoded = decodeJavaMIMEType(nat);
 563             DataFlavor flavor = null;
 564 
 565             try {
 566                 flavor = new DataFlavor(decoded);
 567             } catch (Exception e) {
 568                 System.err.println("Exception \"" + e.getClass().getName() +
 569                                    ": " + e.getMessage()  +
 570                                    "\"while constructing DataFlavor for: " +
 571                                    decoded);
 572             }
 573 
 574             if (flavor != null) {
 575                 flavors = new ArrayList<>(1);
 576                 getNativeToFlavor().put(nat, flavors);
 577                 flavors.add(flavor);
 578                 getFlavorsForNativeCache.remove(nat);
 579                 getFlavorsForNativeCache.remove(null);
 580 
 581                 List<String> natives = getFlavorToNative().get(flavor);
 582                 if (natives == null) {
 583                     natives = new ArrayList<>(1);
 584                     getFlavorToNative().put(flavor, natives);
 585                 }
 586                 natives.add(nat);
 587                 getNativesForFlavorCache.remove(flavor);
 588                 getNativesForFlavorCache.remove(null);
 589             }
 590         }
 591 
 592         return (flavors != null) ? flavors : new ArrayList<>(0);
 593     }
 594 
 595     /**
 596      * Semantically equivalent to 'flavorToNative.get(flav)'. This method
 597      * handles the case where 'flav' is not found in 'flavorToNative' depending
 598      * on the value of passes 'synthesize' parameter. If 'synthesize' is
 599      * SYNTHESIZE_IF_NOT_FOUND a native is synthesized, stored, and returned by
 600      * encoding the DataFlavor's MIME type. Otherwise an empty List is returned
 601      * and 'flavorToNative' remains unaffected.
 602      */
 603     private List<String> flavorToNativeLookup(final DataFlavor flav,
 604                                               final boolean synthesize) {
 605         List<String> natives = getFlavorToNative().get(flav);
 606 
 607         if (flav != null && !disabledMappingGenerationKeys.contains(flav)) {
 608             DataTransferer transferer = DataTransferer.getInstance();
 609             if (transferer != null) {
 610                 List<String> platformNatives =
 611                     transferer.getPlatformMappingsForFlavor(flav);
 612                 if (!platformNatives.isEmpty()) {
 613                     if (natives != null) {
 614                         platformNatives.removeAll(new HashSet<>(natives));
 615                         // Prepend the platform-specific mappings to ensure
 616                         // that the natives added with
 617                         // addUnencodedNativeForFlavor() are at the end of
 618                         // list.
 619                         platformNatives.addAll(natives);
 620                     }
 621                     natives = platformNatives;
 622                 }
 623             }
 624         }
 625 
 626         if (natives == null) {
 627             if (synthesize) {
 628                 String encoded = encodeDataFlavor(flav);
 629                 natives = new ArrayList<>(1);
 630                 getFlavorToNative().put(flav, natives);
 631                 natives.add(encoded);
 632                 getNativesForFlavorCache.remove(flav);
 633                 getNativesForFlavorCache.remove(null);
 634 
 635                 List<DataFlavor> flavors = getNativeToFlavor().get(encoded);
 636                 if (flavors == null) {
 637                     flavors = new ArrayList<>(1);
 638                     getNativeToFlavor().put(encoded, flavors);
 639                 }
 640                 flavors.add(flav);
 641                 getFlavorsForNativeCache.remove(encoded);
 642                 getFlavorsForNativeCache.remove(null);
 643             } else {
 644                 natives = new ArrayList<>(0);
 645             }
 646         }
 647 
 648         return natives;
 649     }
 650 
 651     /**
 652      * Returns a <code>List</code> of <code>String</code> natives to which the
 653      * specified <code>DataFlavor</code> can be translated by the data transfer
 654      * subsystem. The <code>List</code> will be sorted from best native to
 655      * worst. That is, the first native will best reflect data in the specified
 656      * flavor to the underlying native platform.
 657      * <p>
 658      * If the specified <code>DataFlavor</code> is previously unknown to the
 659      * data transfer subsystem and the data transfer subsystem is unable to
 660      * translate this <code>DataFlavor</code> to any existing native, then
 661      * invoking this method will establish a
 662      * mapping in both directions between the specified <code>DataFlavor</code>
 663      * and an encoded version of its MIME type as its native.
 664      *
 665      * @param flav the <code>DataFlavor</code> whose corresponding natives
 666      *        should be returned. If <code>null</code> is specified, all
 667      *        natives currently known to the data transfer subsystem are
 668      *        returned in a non-deterministic order.
 669      * @return a <code>java.util.List</code> of <code>java.lang.String</code>
 670      *         objects which are platform-specific representations of platform-
 671      *         specific data formats
 672      *
 673      * @see #encodeDataFlavor
 674      * @since 1.4
 675      */
 676     public synchronized List<String> getNativesForFlavor(DataFlavor flav) {
 677         List<String> retval = null;
 678 
 679         // Check cache, even for null flav
 680         SoftReference<List<String>> ref = getNativesForFlavorCache.get(flav);
 681         if (ref != null) {
 682             retval = ref.get();
 683             if (retval != null) {
 684                 // Create a copy, because client code can modify the returned
 685                 // list.
 686                 return new ArrayList<>(retval);
 687             }
 688         }
 689 
 690         if (flav == null) {
 691             retval = new ArrayList<>(getNativeToFlavor().keySet());
 692         } else if (disabledMappingGenerationKeys.contains(flav)) {
 693             // In this case we shouldn't synthesize a native for this flavor,
 694             // since its mappings were explicitly specified.
 695             retval = flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
 696         } else if (DataTransferer.isFlavorCharsetTextType(flav)) {
 697 
 698             // For text/* flavors, flavor-to-native mappings specified in
 699             // flavormap.properties are stored per flavor's base type.
 700             if ("text".equals(flav.getPrimaryType())) {
 701                 retval = getAllNativesForType(flav.mimeType.getBaseType());
 702                 if (retval != null) {
 703                     // To prevent the List stored in the map from modification.
 704                     retval = new ArrayList(retval);
 705                 }
 706             }
 707 
 708             // Also include text/plain natives, but don't duplicate Strings
 709             List<String> textPlainList = getAllNativesForType(TEXT_PLAIN_BASE_TYPE);
 710 
 711             if (textPlainList != null && !textPlainList.isEmpty()) {
 712                 // To prevent the List stored in the map from modification.
 713                 // This also guarantees that removeAll() is supported.
 714                 textPlainList = new ArrayList<>(textPlainList);
 715                 if (retval != null && !retval.isEmpty()) {
 716                     // Use HashSet to get constant-time performance for search.
 717                     textPlainList.removeAll(new HashSet<>(retval));
 718                     retval.addAll(textPlainList);
 719                 } else {
 720                     retval = textPlainList;
 721                 }
 722             }
 723 
 724             if (retval == null || retval.isEmpty()) {
 725                 retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
 726             } else {
 727                 // In this branch it is guaranteed that natives explicitly
 728                 // listed for flav's MIME type were added with
 729                 // addUnencodedNativeForFlavor(), so they have lower priority.
 730                 List<String> explicitList =
 731                     flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
 732 
 733                 // flavorToNativeLookup() never returns null.
 734                 // It can return an empty List, however.
 735                 if (!explicitList.isEmpty()) {
 736                     // To prevent the List stored in the map from modification.
 737                     // This also guarantees that removeAll() is supported.
 738                     explicitList = new ArrayList<>(explicitList);
 739                     // Use HashSet to get constant-time performance for search.
 740                     explicitList.removeAll(new HashSet<>(retval));
 741                     retval.addAll(explicitList);
 742                 }
 743             }
 744         } else if (DataTransferer.isFlavorNoncharsetTextType(flav)) {
 745             retval = getAllNativesForType(flav.mimeType.getBaseType());
 746 
 747             if (retval == null || retval.isEmpty()) {
 748                 retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
 749             } else {
 750                 // In this branch it is guaranteed that natives explicitly
 751                 // listed for flav's MIME type were added with
 752                 // addUnencodedNativeForFlavor(), so they have lower priority.
 753                 List<String> explicitList =
 754                     flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
 755 
 756                 // flavorToNativeLookup() never returns null.
 757                 // It can return an empty List, however.
 758                 if (!explicitList.isEmpty()) {
 759                     // To prevent the List stored in the map from modification.
 760                     // This also guarantees that add/removeAll() are supported.
 761                     retval = new ArrayList<>(retval);
 762                     explicitList = new ArrayList<>(explicitList);
 763                     // Use HashSet to get constant-time performance for search.
 764                     explicitList.removeAll(new HashSet<>(retval));
 765                     retval.addAll(explicitList);
 766                 }
 767             }
 768         } else {
 769             retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
 770         }
 771 
 772         getNativesForFlavorCache.put(flav, new SoftReference<>(retval));
 773         // Create a copy, because client code can modify the returned list.
 774         return new ArrayList<>(retval);
 775     }
 776 
 777     /**
 778      * Returns a <code>List</code> of <code>DataFlavor</code>s to which the
 779      * specified <code>String</code> native can be translated by the data
 780      * transfer subsystem. The <code>List</code> will be sorted from best
 781      * <code>DataFlavor</code> to worst. That is, the first
 782      * <code>DataFlavor</code> will best reflect data in the specified
 783      * native to a Java application.
 784      * <p>
 785      * If the specified native is previously unknown to the data transfer
 786      * subsystem, and that native has been properly encoded, then invoking this
 787      * method will establish a mapping in both directions between the specified
 788      * native and a <code>DataFlavor</code> whose MIME type is a decoded
 789      * version of the native.
 790      * <p>
 791      * If the specified native is not a properly encoded native and the
 792      * mappings for this native have not been altered with
 793      * <code>setFlavorsForNative</code>, then the contents of the
 794      * <code>List</code> is platform dependent, but <code>null</code>
 795      * cannot be returned.
 796      *
 797      * @param nat the native whose corresponding <code>DataFlavor</code>s
 798      *        should be returned. If <code>null</code> is specified, all
 799      *        <code>DataFlavor</code>s currently known to the data transfer
 800      *        subsystem are returned in a non-deterministic order.
 801      * @return a <code>java.util.List</code> of <code>DataFlavor</code>
 802      *         objects into which platform-specific data in the specified,
 803      *         platform-specific native can be translated
 804      *
 805      * @see #encodeJavaMIMEType
 806      * @since 1.4
 807      */
 808     public synchronized List<DataFlavor> getFlavorsForNative(String nat) {
 809 
 810         // Check cache, even for null nat
 811         SoftReference<List<DataFlavor>> ref = getFlavorsForNativeCache.get(nat);
 812         if (ref != null) {
 813             List<DataFlavor> retval = ref.get();
 814             if (retval != null) {
 815                 return new ArrayList<>(retval);
 816             }
 817         }
 818 
 819         final LinkedHashSet <DataFlavor> returnValue =
 820             new LinkedHashSet<>();
 821 
 822         if (nat == null) {
 823             final List<String> natives = getNativesForFlavor(null);
 824 
 825             for (String n : natives)
 826             {
 827                 final List<DataFlavor> flavors = getFlavorsForNative(n);
 828 
 829                 for (DataFlavor df : flavors)
 830                 {
 831                     returnValue.add(df);
 832                 }
 833             }
 834         } else {
 835 
 836             final List<DataFlavor> flavors = nativeToFlavorLookup(nat);
 837 
 838             if (disabledMappingGenerationKeys.contains(nat)) {
 839                 return flavors;
 840             }
 841 
 842             final List<DataFlavor> flavorsAndBaseTypes =
 843                 nativeToFlavorLookup(nat);
 844 
 845             for (DataFlavor df : flavorsAndBaseTypes) {
 846                 returnValue.add(df);
 847                 if ("text".equals(df.getPrimaryType())) {
 848                     try {
 849                         returnValue.addAll(
 850                                 convertMimeTypeToDataFlavors(
 851                                         new MimeType(df.getMimeType()
 852                                         ).getBaseType()));
 853                     } catch (MimeTypeParseException e) {
 854                         e.printStackTrace();
 855                     }
 856                 }
 857             }
 858 
 859         }
 860 
 861         final List<DataFlavor> arrayList = new ArrayList<>(returnValue);
 862         getFlavorsForNativeCache.put(nat, new SoftReference<>(arrayList));
 863         return new ArrayList<>(arrayList);
 864     }
 865 
 866     private static Set<DataFlavor> convertMimeTypeToDataFlavors(
 867         final String baseType) {
 868 
 869         final Set<DataFlavor> returnValue = new LinkedHashSet<>();
 870 
 871         String subType = null;
 872 
 873         try {
 874             final MimeType mimeType = new MimeType(baseType);
 875             subType = mimeType.getSubType();
 876         } catch (MimeTypeParseException mtpe) {
 877             // Cannot happen, since we checked all mappings
 878             // on load from flavormap.properties.
 879             assert(false);
 880         }
 881 
 882         if (DataTransferer.doesSubtypeSupportCharset(subType, null)) {
 883             if (TEXT_PLAIN_BASE_TYPE.equals(baseType))
 884             {
 885                 returnValue.add(DataFlavor.stringFlavor);
 886             }
 887 
 888             for (String unicodeClassName : UNICODE_TEXT_CLASSES) {
 889                 final String mimeType = baseType + ";charset=Unicode;class=" +
 890                                             unicodeClassName;
 891 
 892                 final LinkedHashSet<String> mimeTypes =
 893                     handleHtmlMimeTypes(baseType, mimeType);
 894                 for (String mt : mimeTypes) {
 895                     DataFlavor toAdd = null;
 896                     try {
 897                         toAdd = new DataFlavor(mt);
 898                     } catch (ClassNotFoundException cannotHappen) {
 899                     }
 900                     returnValue.add(toAdd);
 901                 }
 902             }
 903 
 904             for (String charset : DataTransferer.standardEncodings()) {
 905 
 906                 for (String encodedTextClass : ENCODED_TEXT_CLASSES) {
 907                     final String mimeType =
 908                             baseType + ";charset=" + charset +
 909                             ";class=" + encodedTextClass;
 910 
 911                     final LinkedHashSet<String> mimeTypes =
 912                         handleHtmlMimeTypes(baseType, mimeType);
 913 
 914                     for (String mt : mimeTypes) {
 915 
 916                         DataFlavor df = null;
 917 
 918                         try {
 919                             df = new DataFlavor(mt);
 920                             // Check for equality to plainTextFlavor so
 921                             // that we can ensure that the exact charset of
 922                             // plainTextFlavor, not the canonical charset
 923                             // or another equivalent charset with a
 924                             // different name, is used.
 925                             if (df.equals(DataFlavor.plainTextFlavor)) {
 926                                 df = DataFlavor.plainTextFlavor;
 927                             }
 928                         } catch (ClassNotFoundException cannotHappen) {
 929                         }
 930 
 931                         returnValue.add(df);
 932                     }
 933                 }
 934             }
 935 
 936             if (TEXT_PLAIN_BASE_TYPE.equals(baseType))
 937             {
 938                 returnValue.add(DataFlavor.plainTextFlavor);
 939             }
 940         } else {
 941             // Non-charset text natives should be treated as
 942             // opaque, 8-bit data in any of its various
 943             // representations.
 944             for (String encodedTextClassName : ENCODED_TEXT_CLASSES) {
 945                 DataFlavor toAdd = null;
 946                 try {
 947                     toAdd = new DataFlavor(baseType +
 948                          ";class=" + encodedTextClassName);
 949                 } catch (ClassNotFoundException cannotHappen) {
 950                 }
 951                 returnValue.add(toAdd);
 952             }
 953         }
 954         return returnValue;
 955     }
 956 
 957     private static final String [] htmlDocumntTypes =
 958         new String [] {"all", "selection", "fragment"};
 959 
 960     private static LinkedHashSet<String> handleHtmlMimeTypes(
 961         String baseType, String mimeType) {
 962 
 963         LinkedHashSet<String> returnValues = new LinkedHashSet<>();
 964 
 965         if (HTML_TEXT_BASE_TYPE.equals(baseType)) {
 966             for (String documentType : htmlDocumntTypes) {
 967                 returnValues.add(mimeType + ";document=" + documentType);
 968             }
 969         } else {
 970             returnValues.add(mimeType);
 971         }
 972 
 973         return returnValues;
 974     }
 975 
 976     /**
 977      * Returns a <code>Map</code> of the specified <code>DataFlavor</code>s to
 978      * their most preferred <code>String</code> native. Each native value will
 979      * be the same as the first native in the List returned by
 980      * <code>getNativesForFlavor</code> for the specified flavor.
 981      * <p>
 982      * If a specified <code>DataFlavor</code> is previously unknown to the
 983      * data transfer subsystem, then invoking this method will establish a
 984      * mapping in both directions between the specified <code>DataFlavor</code>
 985      * and an encoded version of its MIME type as its native.
 986      *
 987      * @param flavors an array of <code>DataFlavor</code>s which will be the
 988      *        key set of the returned <code>Map</code>. If <code>null</code> is
 989      *        specified, a mapping of all <code>DataFlavor</code>s known to the
 990      *        data transfer subsystem to their most preferred
 991      *        <code>String</code> natives will be returned.
 992      * @return a <code>java.util.Map</code> of <code>DataFlavor</code>s to
 993      *         <code>String</code> natives
 994      *
 995      * @see #getNativesForFlavor
 996      * @see #encodeDataFlavor
 997      */
 998     public synchronized Map<DataFlavor,String>
 999         getNativesForFlavors(DataFlavor[] flavors)
1000     {
1001         // Use getNativesForFlavor to generate extra natives for text flavors
1002         // and stringFlavor
1003 
1004         if (flavors == null) {
1005             List flavor_list = getFlavorsForNative(null);
1006             flavors = new DataFlavor[flavor_list.size()];
1007             flavor_list.toArray(flavors);
1008         }
1009 
1010         Map<DataFlavor, String> retval = new HashMap<>(flavors.length, 1.0f);
1011         for (DataFlavor flavor : flavors) {
1012             List<String> natives = getNativesForFlavor(flavor);
1013             String nat = (natives.isEmpty()) ? null : natives.get(0);
1014             retval.put(flavor, nat);
1015         }
1016 
1017         return retval;
1018     }
1019 
1020     /**
1021      * Returns a <code>Map</code> of the specified <code>String</code> natives
1022      * to their most preferred <code>DataFlavor</code>. Each
1023      * <code>DataFlavor</code> value will be the same as the first
1024      * <code>DataFlavor</code> in the List returned by
1025      * <code>getFlavorsForNative</code> for the specified native.
1026      * <p>
1027      * If a specified native is previously unknown to the data transfer
1028      * subsystem, and that native has been properly encoded, then invoking this
1029      * method will establish a mapping in both directions between the specified
1030      * native and a <code>DataFlavor</code> whose MIME type is a decoded
1031      * version of the native.
1032      *
1033      * @param natives an array of <code>String</code>s which will be the
1034      *        key set of the returned <code>Map</code>. If <code>null</code> is
1035      *        specified, a mapping of all supported <code>String</code> natives
1036      *        to their most preferred <code>DataFlavor</code>s will be
1037      *        returned.
1038      * @return a <code>java.util.Map</code> of <code>String</code> natives to
1039      *         <code>DataFlavor</code>s
1040      *
1041      * @see #getFlavorsForNative
1042      * @see #encodeJavaMIMEType
1043      */
1044     public synchronized Map<String,DataFlavor>
1045         getFlavorsForNatives(String[] natives)
1046     {
1047         // Use getFlavorsForNative to generate extra flavors for text natives
1048 
1049         if (natives == null) {
1050             List native_list = getNativesForFlavor(null);
1051             natives = new String[native_list.size()];
1052             native_list.toArray(natives);
1053         }
1054 
1055         Map<String, DataFlavor> retval = new HashMap<>(natives.length, 1.0f);
1056         for (String aNative : natives) {
1057             List<DataFlavor> flavors = getFlavorsForNative(aNative);
1058             DataFlavor flav = (flavors.isEmpty())? null : flavors.get(0);
1059             retval.put(aNative, flav);
1060         }
1061 
1062         return retval;
1063     }
1064 
1065     /**
1066      * Adds a mapping from the specified <code>DataFlavor</code> (and all
1067      * <code>DataFlavor</code>s equal to the specified <code>DataFlavor</code>)
1068      * to the specified <code>String</code> native.
1069      * Unlike <code>getNativesForFlavor</code>, the mapping will only be
1070      * established in one direction, and the native will not be encoded. To
1071      * establish a two-way mapping, call
1072      * <code>addFlavorForUnencodedNative</code> as well. The new mapping will
1073      * be of lower priority than any existing mapping.
1074      * This method has no effect if a mapping from the specified or equal
1075      * <code>DataFlavor</code> to the specified <code>String</code> native
1076      * already exists.
1077      *
1078      * @param flav the <code>DataFlavor</code> key for the mapping
1079      * @param nat the <code>String</code> native value for the mapping
1080      * @throws NullPointerException if flav or nat is <code>null</code>
1081      *
1082      * @see #addFlavorForUnencodedNative
1083      * @since 1.4
1084      */
1085     public synchronized void addUnencodedNativeForFlavor(DataFlavor flav,
1086                                                          String nat) {
1087         if (flav == null || nat == null) {
1088             throw new NullPointerException("null arguments not permitted");
1089         }
1090 
1091         List<String> natives = getFlavorToNative().get(flav);
1092         if (natives == null) {
1093             natives = new ArrayList<>(1);
1094             getFlavorToNative().put(flav, natives);
1095         } else if (natives.contains(nat)) {
1096             return;
1097         }
1098         natives.add(nat);
1099         getNativesForFlavorCache.remove(flav);
1100         getNativesForFlavorCache.remove(null);
1101     }
1102 
1103     /**
1104      * Discards the current mappings for the specified <code>DataFlavor</code>
1105      * and all <code>DataFlavor</code>s equal to the specified
1106      * <code>DataFlavor</code>, and creates new mappings to the
1107      * specified <code>String</code> natives.
1108      * Unlike <code>getNativesForFlavor</code>, the mappings will only be
1109      * established in one direction, and the natives will not be encoded. To
1110      * establish two-way mappings, call <code>setFlavorsForNative</code>
1111      * as well. The first native in the array will represent the highest
1112      * priority mapping. Subsequent natives will represent mappings of
1113      * decreasing priority.
1114      * <p>
1115      * If the array contains several elements that reference equal
1116      * <code>String</code> natives, this method will establish new mappings
1117      * for the first of those elements and ignore the rest of them.
1118      * <p>
1119      * It is recommended that client code not reset mappings established by the
1120      * data transfer subsystem. This method should only be used for
1121      * application-level mappings.
1122      *
1123      * @param flav the <code>DataFlavor</code> key for the mappings
1124      * @param natives the <code>String</code> native values for the mappings
1125      * @throws NullPointerException if flav or natives is <code>null</code>
1126      *         or if natives contains <code>null</code> elements
1127      *
1128      * @see #setFlavorsForNative
1129      * @since 1.4
1130      */
1131     public synchronized void setNativesForFlavor(DataFlavor flav,
1132                                                  String[] natives) {
1133         if (flav == null || natives == null) {
1134             throw new NullPointerException("null arguments not permitted");
1135         }
1136 
1137         getFlavorToNative().remove(flav);
1138         for (String aNative : natives) {
1139             addUnencodedNativeForFlavor(flav, aNative);
1140         }
1141         disabledMappingGenerationKeys.add(flav);
1142         // Clear the cache to handle the case of empty natives.
1143         getNativesForFlavorCache.remove(flav);
1144         getNativesForFlavorCache.remove(null);
1145     }
1146 
1147     /**
1148      * Adds a mapping from a single <code>String</code> native to a single
1149      * <code>DataFlavor</code>. Unlike <code>getFlavorsForNative</code>, the
1150      * mapping will only be established in one direction, and the native will
1151      * not be encoded. To establish a two-way mapping, call
1152      * <code>addUnencodedNativeForFlavor</code> as well. The new mapping will
1153      * be of lower priority than any existing mapping.
1154      * This method has no effect if a mapping from the specified
1155      * <code>String</code> native to the specified or equal
1156      * <code>DataFlavor</code> already exists.
1157      *
1158      * @param nat the <code>String</code> native key for the mapping
1159      * @param flav the <code>DataFlavor</code> value for the mapping
1160      * @throws NullPointerException if nat or flav is <code>null</code>
1161      *
1162      * @see #addUnencodedNativeForFlavor
1163      * @since 1.4
1164      */
1165     public synchronized void addFlavorForUnencodedNative(String nat,
1166                                                          DataFlavor flav) {
1167         if (nat == null || flav == null) {
1168             throw new NullPointerException("null arguments not permitted");
1169         }
1170 
1171         List<DataFlavor> flavors = getNativeToFlavor().get(nat);
1172         if (flavors == null) {
1173             flavors = new ArrayList<>(1);
1174             getNativeToFlavor().put(nat, flavors);
1175         } else if (flavors.contains(flav)) {
1176             return;
1177         }
1178         flavors.add(flav);
1179         getFlavorsForNativeCache.remove(nat);
1180         getFlavorsForNativeCache.remove(null);
1181     }
1182 
1183     /**
1184      * Discards the current mappings for the specified <code>String</code>
1185      * native, and creates new mappings to the specified
1186      * <code>DataFlavor</code>s. Unlike <code>getFlavorsForNative</code>, the
1187      * mappings will only be established in one direction, and the natives need
1188      * not be encoded. To establish two-way mappings, call
1189      * <code>setNativesForFlavor</code> as well. The first
1190      * <code>DataFlavor</code> in the array will represent the highest priority
1191      * mapping. Subsequent <code>DataFlavor</code>s will represent mappings of
1192      * decreasing priority.
1193      * <p>
1194      * If the array contains several elements that reference equal
1195      * <code>DataFlavor</code>s, this method will establish new mappings
1196      * for the first of those elements and ignore the rest of them.
1197      * <p>
1198      * It is recommended that client code not reset mappings established by the
1199      * data transfer subsystem. This method should only be used for
1200      * application-level mappings.
1201      *
1202      * @param nat the <code>String</code> native key for the mappings
1203      * @param flavors the <code>DataFlavor</code> values for the mappings
1204      * @throws NullPointerException if nat or flavors is <code>null</code>
1205      *         or if flavors contains <code>null</code> elements
1206      *
1207      * @see #setNativesForFlavor
1208      * @since 1.4
1209      */
1210     public synchronized void setFlavorsForNative(String nat,
1211                                                  DataFlavor[] flavors) {
1212         if (nat == null || flavors == null) {
1213             throw new NullPointerException("null arguments not permitted");
1214         }
1215 
1216         getNativeToFlavor().remove(nat);
1217         for (DataFlavor flavor : flavors) {
1218             addFlavorForUnencodedNative(nat, flavor);
1219         }
1220         disabledMappingGenerationKeys.add(nat);
1221         // Clear the cache to handle the case of empty flavors.
1222         getFlavorsForNativeCache.remove(nat);
1223         getFlavorsForNativeCache.remove(null);
1224     }
1225 
1226     /**
1227      * Encodes a MIME type for use as a <code>String</code> native. The format
1228      * of an encoded representation of a MIME type is implementation-dependent.
1229      * The only restrictions are:
1230      * <ul>
1231      * <li>The encoded representation is <code>null</code> if and only if the
1232      * MIME type <code>String</code> is <code>null</code>.</li>
1233      * <li>The encoded representations for two non-<code>null</code> MIME type
1234      * <code>String</code>s are equal if and only if these <code>String</code>s
1235      * are equal according to <code>String.equals(Object)</code>.</li>
1236      * </ul>
1237      * <p>
1238      * The reference implementation of this method returns the specified MIME
1239      * type <code>String</code> prefixed with <code>JAVA_DATAFLAVOR:</code>.
1240      *
1241      * @param mimeType the MIME type to encode
1242      * @return the encoded <code>String</code>, or <code>null</code> if
1243      *         mimeType is <code>null</code>
1244      */
1245     public static String encodeJavaMIMEType(String mimeType) {
1246         return (mimeType != null)
1247             ? JavaMIME + mimeType
1248             : null;
1249     }
1250 
1251     /**
1252      * Encodes a <code>DataFlavor</code> for use as a <code>String</code>
1253      * native. The format of an encoded <code>DataFlavor</code> is
1254      * implementation-dependent. The only restrictions are:
1255      * <ul>
1256      * <li>The encoded representation is <code>null</code> if and only if the
1257      * specified <code>DataFlavor</code> is <code>null</code> or its MIME type
1258      * <code>String</code> is <code>null</code>.</li>
1259      * <li>The encoded representations for two non-<code>null</code>
1260      * <code>DataFlavor</code>s with non-<code>null</code> MIME type
1261      * <code>String</code>s are equal if and only if the MIME type
1262      * <code>String</code>s of these <code>DataFlavor</code>s are equal
1263      * according to <code>String.equals(Object)</code>.</li>
1264      * </ul>
1265      * <p>
1266      * The reference implementation of this method returns the MIME type
1267      * <code>String</code> of the specified <code>DataFlavor</code> prefixed
1268      * with <code>JAVA_DATAFLAVOR:</code>.
1269      *
1270      * @param flav the <code>DataFlavor</code> to encode
1271      * @return the encoded <code>String</code>, or <code>null</code> if
1272      *         flav is <code>null</code> or has a <code>null</code> MIME type
1273      */
1274     public static String encodeDataFlavor(DataFlavor flav) {
1275         return (flav != null)
1276             ? SystemFlavorMap.encodeJavaMIMEType(flav.getMimeType())
1277             : null;
1278     }
1279 
1280     /**
1281      * Returns whether the specified <code>String</code> is an encoded Java
1282      * MIME type.
1283      *
1284      * @param str the <code>String</code> to test
1285      * @return <code>true</code> if the <code>String</code> is encoded;
1286      *         <code>false</code> otherwise
1287      */
1288     public static boolean isJavaMIMEType(String str) {
1289         return (str != null && str.startsWith(JavaMIME, 0));
1290     }
1291 
1292     /**
1293      * Decodes a <code>String</code> native for use as a Java MIME type.
1294      *
1295      * @param nat the <code>String</code> to decode
1296      * @return the decoded Java MIME type, or <code>null</code> if nat is not
1297      *         an encoded <code>String</code> native
1298      */
1299     public static String decodeJavaMIMEType(String nat) {
1300         return (isJavaMIMEType(nat))
1301             ? nat.substring(JavaMIME.length(), nat.length()).trim()
1302             : null;
1303     }
1304 
1305     /**
1306      * Decodes a <code>String</code> native for use as a
1307      * <code>DataFlavor</code>.
1308      *
1309      * @param nat the <code>String</code> to decode
1310      * @return the decoded <code>DataFlavor</code>, or <code>null</code> if
1311      *         nat is not an encoded <code>String</code> native
1312      * @throws ClassNotFoundException if the class of the data flavor
1313      * is not loaded
1314      */
1315     public static DataFlavor decodeDataFlavor(String nat)
1316         throws ClassNotFoundException
1317     {
1318         String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat);
1319         return (retval_str != null)
1320             ? new DataFlavor(retval_str)
1321             : null;
1322     }
1323 
1324     private List<String> getAllNativesForType(String type) {
1325         Set<String> retval = null;
1326         for (DataFlavor dataFlavor : convertMimeTypeToDataFlavors(type)) {
1327             List<String> natives = getFlavorToNative().get(dataFlavor);
1328             if (natives != null && !natives.isEmpty()) {
1329                 if (retval == null) {
1330                     retval = new LinkedHashSet<>();
1331                 }
1332                 retval.addAll(natives);
1333             }
1334         }
1335         return retval == null ? null : new ArrayList<>(retval);
1336     }
1337 }