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