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