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.LinkedList;
  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      * This constant is passed to flavorToNativeLookup() to indicate that a
 107      * a native should be synthesized, stored, and returned by encoding the
 108      * DataFlavor's MIME type in case if the DataFlavor is not found in
 109      * 'flavorToNative' map.
 110      */
 111     private static final boolean SYNTHESIZE_IF_NOT_FOUND = true;
 112 
 113     /**
 114      * Maps native Strings to Lists of DataFlavors (or base type Strings for
 115      * text DataFlavors).
 116      * Do not use the field directly, use getNativeToFlavor() instead.
 117      */
 118     private Map nativeToFlavor = new HashMap();
 119 
 120     /**
 121      * Accessor to nativeToFlavor map.  Since we use lazy initialization we must
 122      * use this accessor instead of direct access to the field which may not be
 123      * initialized yet.  This method will initialize the field if needed.
 124      *
 125      * @return nativeToFlavor
 126      */
 127     private Map getNativeToFlavor() {
 128         if (!isMapInitialized) {
 129             initSystemFlavorMap();
 130         }
 131         return nativeToFlavor;
 132     }
 133 
 134     /**
 135      * Maps DataFlavors (or base type Strings for text DataFlavors) to Lists of
 136      * native Strings.
 137      * Do not use the field directly, use getFlavorToNative() instead.
 138      */
 139     private Map flavorToNative = new HashMap();
 140 
 141     /**
 142      * Accessor to flavorToNative map.  Since we use lazy initialization we must
 143      * use this accessor instead of direct access to the field which may not be
 144      * initialized yet.  This method will initialize the field if needed.
 145      *
 146      * @return flavorToNative
 147      */
 148     private synchronized Map getFlavorToNative() {
 149         if (!isMapInitialized) {
 150             initSystemFlavorMap();
 151         }
 152         return flavorToNative;
 153     }
 154 
 155     /**
 156      * Shows if the object has been initialized.
 157      */
 158     private boolean isMapInitialized = false;
 159 
 160     /**
 161      * Caches the result of getNativesForFlavor(). Maps DataFlavors to
 162      * SoftReferences which reference Lists of String natives.
 163      */
 164     private Map getNativesForFlavorCache = new HashMap();
 165 
 166     /**
 167      * Caches the result getFlavorsForNative(). Maps String natives to
 168      * SoftReferences which reference Lists of DataFlavors.
 169      */
 170     private Map getFlavorsForNativeCache = new HashMap();
 171 
 172     /**
 173      * Dynamic mapping generation used for text mappings should not be applied
 174      * to the DataFlavors and String natives for which the mappings have been
 175      * explicitly specified with setFlavorsForNative() or
 176      * setNativesForFlavor(). This keeps all such keys.
 177      */
 178     private Set disabledMappingGenerationKeys = new HashSet();
 179 
 180     /**
 181      * Returns 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                     // For text/* flavors, store mappings in separate maps to
 415                     // enable dynamic mapping generation at a run-time.
 416                     if ("text".equals(flavor.getPrimaryType())) {
 417                         store(value, key, getFlavorToNative());
 418                         store(key, value, getNativeToFlavor());
 419                     } else {
 420                         store(flavor, key, getFlavorToNative());
 421                         store(key, flavor, getNativeToFlavor());
 422                     }
 423                 }
 424             }
 425         }
 426     }
 427 
 428     /**
 429      * Copied from java.util.Properties.
 430      */
 431     private boolean continueLine (String line) {
 432         int slashCount = 0;
 433         int index = line.length() - 1;
 434         while((index >= 0) && (line.charAt(index--) == '\\')) {
 435             slashCount++;
 436         }
 437         return (slashCount % 2 == 1);
 438     }
 439 
 440     /**
 441      * Copied from java.util.Properties.
 442      */
 443     private String loadConvert(String theString) {
 444         char aChar;
 445         int len = theString.length();
 446         StringBuilder outBuffer = new StringBuilder(len);
 447 
 448         for (int x = 0; x < len; ) {
 449             aChar = theString.charAt(x++);
 450             if (aChar == '\\') {
 451                 aChar = theString.charAt(x++);
 452                 if (aChar == 'u') {
 453                     // Read the xxxx
 454                     int value = 0;
 455                     for (int i = 0; i < 4; i++) {
 456                         aChar = theString.charAt(x++);
 457                         switch (aChar) {
 458                           case '0': case '1': case '2': case '3': case '4':
 459                           case '5': case '6': case '7': case '8': case '9': {
 460                              value = (value << 4) + aChar - '0';
 461                              break;
 462                           }
 463                           case 'a': case 'b': case 'c':
 464                           case 'd': case 'e': case 'f': {
 465                              value = (value << 4) + 10 + aChar - 'a';
 466                              break;
 467                           }
 468                           case 'A': case 'B': case 'C':
 469                           case 'D': case 'E': case 'F': {
 470                              value = (value << 4) + 10 + aChar - 'A';
 471                              break;
 472                           }
 473                           default: {
 474                               throw new IllegalArgumentException(
 475                                            "Malformed \\uxxxx encoding.");
 476                           }
 477                         }
 478                     }
 479                     outBuffer.append((char)value);
 480                 } else {
 481                     if (aChar == 't') {
 482                         aChar = '\t';
 483                     } else if (aChar == 'r') {
 484                         aChar = '\r';
 485                     } else if (aChar == 'n') {
 486                         aChar = '\n';
 487                     } else if (aChar == 'f') {
 488                         aChar = '\f';
 489                     }
 490                     outBuffer.append(aChar);
 491                 }
 492             } else {
 493                 outBuffer.append(aChar);
 494             }
 495         }
 496         return outBuffer.toString();
 497     }
 498 
 499     /**
 500      * Stores the listed object under the specified hash key in map. Unlike a
 501      * standard map, the listed object will not replace any object already at
 502      * the appropriate Map location, but rather will be appended to a List
 503      * stored in that location.
 504      */
 505     private void store(Object hashed, Object listed, Map map) {
 506         List list = (List)map.get(hashed);
 507         if (list == null) {
 508             list = new ArrayList(1);
 509             map.put(hashed, list);
 510         }
 511         if (!list.contains(listed)) {
 512             list.add(listed);
 513         }
 514     }
 515 
 516     /**
 517      * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method
 518      * handles the case where 'nat' is not found in 'nativeToFlavor'. In that
 519      * case, a new DataFlavor is synthesized, stored, and returned, if and
 520      * only if the specified native is encoded as a Java MIME type.
 521      */
 522     private List nativeToFlavorLookup(String nat) {
 523         List flavors = (List)getNativeToFlavor().get(nat);
 524 
 525         if (nat != null && !disabledMappingGenerationKeys.contains(nat)) {
 526             DataTransferer transferer = DataTransferer.getInstance();
 527             if (transferer != null) {
 528                 List platformFlavors =
 529                     transferer.getPlatformMappingsForNative(nat);
 530                 if (!platformFlavors.isEmpty()) {
 531                     if (flavors != null) {
 532                         platformFlavors.removeAll(new HashSet(flavors));
 533                         // Prepending the platform-specific mappings ensures
 534                         // that the flavors added with
 535                         // addFlavorForUnencodedNative() are at the end of
 536                         // list.
 537                         platformFlavors.addAll(flavors);
 538                     }
 539                     flavors = platformFlavors;
 540                 }
 541             }
 542         }
 543 
 544         if (flavors == null && isJavaMIMEType(nat)) {
 545             String decoded = decodeJavaMIMEType(nat);
 546             DataFlavor flavor = null;
 547 
 548             try {
 549                 flavor = new DataFlavor(decoded);
 550             } catch (Exception e) {
 551                 System.err.println("Exception \"" + e.getClass().getName() +
 552                                    ": " + e.getMessage()  +
 553                                    "\"while constructing DataFlavor for: " +
 554                                    decoded);
 555             }
 556 
 557             if (flavor != null) {
 558                 flavors = new ArrayList(1);
 559                 getNativeToFlavor().put(nat, flavors);
 560                 flavors.add(flavor);
 561                 getFlavorsForNativeCache.remove(nat);
 562                 getFlavorsForNativeCache.remove(null);
 563 
 564                 List natives = (List)getFlavorToNative().get(flavor);
 565                 if (natives == null) {
 566                     natives = new ArrayList(1);
 567                     getFlavorToNative().put(flavor, natives);
 568                 }
 569                 natives.add(nat);
 570                 getNativesForFlavorCache.remove(flavor);
 571                 getNativesForFlavorCache.remove(null);
 572             }
 573         }
 574 
 575         return (flavors != null) ? flavors : new ArrayList(0);
 576     }
 577 
 578     /**
 579      * Semantically equivalent to 'flavorToNative.get(flav)'. This method
 580      * handles the case where 'flav' is not found in 'flavorToNative' depending
 581      * on the value of passes 'synthesize' parameter. If 'synthesize' is
 582      * SYNTHESIZE_IF_NOT_FOUND a native is synthesized, stored, and returned by
 583      * encoding the DataFlavor's MIME type. Otherwise an empty List is returned
 584      * and 'flavorToNative' remains unaffected.
 585      */
 586     private List flavorToNativeLookup(final DataFlavor flav,
 587                                       final boolean synthesize) {
 588         List natives = (List)getFlavorToNative().get(flav);
 589 
 590         if (flav != null && !disabledMappingGenerationKeys.contains(flav)) {
 591             DataTransferer transferer = DataTransferer.getInstance();
 592             if (transferer != null) {
 593                 List platformNatives =
 594                     transferer.getPlatformMappingsForFlavor(flav);
 595                 if (!platformNatives.isEmpty()) {
 596                     if (natives != null) {
 597                         platformNatives.removeAll(new HashSet(natives));
 598                         // Prepend the platform-specific mappings to ensure
 599                         // that the natives added with
 600                         // addUnencodedNativeForFlavor() are at the end of
 601                         // list.
 602                         platformNatives.addAll(natives);
 603                     }
 604                     natives = platformNatives;
 605                 }
 606             }
 607         }
 608 
 609         if (natives == null) {
 610             if (synthesize) {
 611                 String encoded = encodeDataFlavor(flav);
 612                 natives = new ArrayList(1);
 613                 getFlavorToNative().put(flav, natives);
 614                 natives.add(encoded);
 615                 getNativesForFlavorCache.remove(flav);
 616                 getNativesForFlavorCache.remove(null);
 617 
 618                 List flavors = (List)getNativeToFlavor().get(encoded);
 619                 if (flavors == null) {
 620                     flavors = new ArrayList(1);
 621                     getNativeToFlavor().put(encoded, flavors);
 622                 }
 623                 flavors.add(flav);
 624                 getFlavorsForNativeCache.remove(encoded);
 625                 getFlavorsForNativeCache.remove(null);
 626             } else {
 627                 natives = new ArrayList(0);
 628             }
 629         }
 630 
 631         return natives;
 632     }
 633 
 634     /**
 635      * Returns a <code>List</code> of <code>String</code> natives to which the
 636      * specified <code>DataFlavor</code> can be translated by the data transfer
 637      * subsystem. The <code>List</code> will be sorted from best native to
 638      * worst. That is, the first native will best reflect data in the specified
 639      * flavor to the underlying native platform.
 640      * <p>
 641      * If the specified <code>DataFlavor</code> is previously unknown to the
 642      * data transfer subsystem and the data transfer subsystem is unable to
 643      * translate this <code>DataFlavor</code> to any existing native, then
 644      * invoking this method will establish a
 645      * mapping in both directions between the specified <code>DataFlavor</code>
 646      * and an encoded version of its MIME type as its native.
 647      *
 648      * @param flav the <code>DataFlavor</code> whose corresponding natives
 649      *        should be returned. If <code>null</code> is specified, all
 650      *        natives currently known to the data transfer subsystem are
 651      *        returned in a non-deterministic order.
 652      * @return a <code>java.util.List</code> of <code>java.lang.String</code>
 653      *         objects which are platform-specific representations of platform-
 654      *         specific data formats
 655      *
 656      * @see #encodeDataFlavor
 657      * @since 1.4
 658      */
 659     public synchronized List<String> getNativesForFlavor(DataFlavor flav) {
 660         List retval = null;
 661 
 662         // Check cache, even for null flav
 663         SoftReference ref = (SoftReference)getNativesForFlavorCache.get(flav);
 664         if (ref != null) {
 665             retval = (List)ref.get();
 666             if (retval != null) {
 667                 // Create a copy, because client code can modify the returned
 668                 // list.
 669                 return new ArrayList(retval);
 670             }
 671         }
 672 
 673         if (flav == null) {
 674             retval = new ArrayList(getNativeToFlavor().keySet());
 675         } else if (disabledMappingGenerationKeys.contains(flav)) {
 676             // In this case we shouldn't synthesize a native for this flavor,
 677             // since its mappings were explicitly specified.
 678             retval = flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
 679         } else if (DataTransferer.isFlavorCharsetTextType(flav)) {
 680 
 681             // For text/* flavors, flavor-to-native mappings specified in
 682             // flavormap.properties are stored per flavor's base type.
 683             if ("text".equals(flav.getPrimaryType())) {
 684                 retval = (List)getFlavorToNative().get(flav.mimeType.getBaseType());
 685                 if (retval != null) {
 686                     // To prevent the List stored in the map from modification.
 687                     retval = new ArrayList(retval);
 688                 }
 689             }
 690 
 691             // Also include text/plain natives, but don't duplicate Strings
 692             List textPlainList = (List)getFlavorToNative().get(TEXT_PLAIN_BASE_TYPE);
 693 
 694             if (textPlainList != null && !textPlainList.isEmpty()) {
 695                 // To prevent the List stored in the map from modification.
 696                 // This also guarantees that removeAll() is supported.
 697                 textPlainList = new ArrayList(textPlainList);
 698                 if (retval != null && !retval.isEmpty()) {
 699                     // Use HashSet to get constant-time performance for search.
 700                     textPlainList.removeAll(new HashSet(retval));
 701                     retval.addAll(textPlainList);
 702                 } else {
 703                     retval = textPlainList;
 704                 }
 705             }
 706 
 707             if (retval == null || retval.isEmpty()) {
 708                 retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
 709             } else {
 710                 // In this branch it is guaranteed that natives explicitly
 711                 // listed for flav's MIME type were added with
 712                 // addUnencodedNativeForFlavor(), so they have lower priority.
 713                 List explicitList =
 714                     flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
 715 
 716                 // flavorToNativeLookup() never returns null.
 717                 // It can return an empty List, however.
 718                 if (!explicitList.isEmpty()) {
 719                     // To prevent the List stored in the map from modification.
 720                     // This also guarantees that removeAll() is supported.
 721                     explicitList = new ArrayList(explicitList);
 722                     // Use HashSet to get constant-time performance for search.
 723                     explicitList.removeAll(new HashSet(retval));
 724                     retval.addAll(explicitList);
 725                 }
 726             }
 727         } else if (DataTransferer.isFlavorNoncharsetTextType(flav)) {
 728             retval = (List)getFlavorToNative().get(flav.mimeType.getBaseType());
 729 
 730             if (retval == null || retval.isEmpty()) {
 731                 retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
 732             } else {
 733                 // In this branch it is guaranteed that natives explicitly
 734                 // listed for flav's MIME type were added with
 735                 // addUnencodedNativeForFlavor(), so they have lower priority.
 736                 List explicitList =
 737                     flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
 738 
 739                 // flavorToNativeLookup() never returns null.
 740                 // It can return an empty List, however.
 741                 if (!explicitList.isEmpty()) {
 742                     // To prevent the List stored in the map from modification.
 743                     // This also guarantees that add/removeAll() are supported.
 744                     retval = new ArrayList(retval);
 745                     explicitList = new ArrayList(explicitList);
 746                     // Use HashSet to get constant-time performance for search.
 747                     explicitList.removeAll(new HashSet(retval));
 748                     retval.addAll(explicitList);
 749                 }
 750             }
 751         } else {
 752             retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
 753         }
 754 
 755         getNativesForFlavorCache.put(flav, new SoftReference(retval));
 756         // Create a copy, because client code can modify the returned list.
 757         return new ArrayList(retval);
 758     }
 759 
 760     /**
 761      * Returns a <code>List</code> of <code>DataFlavor</code>s to which the
 762      * specified <code>String</code> native can be translated by the data
 763      * transfer subsystem. The <code>List</code> will be sorted from best
 764      * <code>DataFlavor</code> to worst. That is, the first
 765      * <code>DataFlavor</code> will best reflect data in the specified
 766      * native to a Java application.
 767      * <p>
 768      * If the specified native is previously unknown to the data transfer
 769      * subsystem, and that native has been properly encoded, then invoking this
 770      * method will establish a mapping in both directions between the specified
 771      * native and a <code>DataFlavor</code> whose MIME type is a decoded
 772      * version of the native.
 773      * <p>
 774      * If the specified native is not a properly encoded native and the
 775      * mappings for this native have not been altered with
 776      * <code>setFlavorsForNative</code>, then the contents of the
 777      * <code>List</code> is platform dependent, but <code>null</code>
 778      * cannot be returned.
 779      *
 780      * @param nat the native whose corresponding <code>DataFlavor</code>s
 781      *        should be returned. If <code>null</code> is specified, all
 782      *        <code>DataFlavor</code>s currently known to the data transfer
 783      *        subsystem are returned in a non-deterministic order.
 784      * @return a <code>java.util.List</code> of <code>DataFlavor</code>
 785      *         objects into which platform-specific data in the specified,
 786      *         platform-specific native can be translated
 787      *
 788      * @see #encodeJavaMIMEType
 789      * @since 1.4
 790      */
 791     public synchronized List<DataFlavor> getFlavorsForNative(String nat) {
 792 
 793         // Check cache, even for null nat
 794         SoftReference ref = (SoftReference)getFlavorsForNativeCache.get(nat);
 795         if (ref != null) {
 796             ArrayList retval = (ArrayList)ref.get();
 797             if (retval != null) {
 798                 return (List)retval.clone();
 799             }
 800         }
 801 
 802         LinkedList retval = new LinkedList();
 803 
 804         if (nat == null) {
 805             List natives = getNativesForFlavor(null);
 806             HashSet dups = new HashSet(natives.size());
 807 
 808             for (Iterator natives_iter = natives.iterator();
 809                  natives_iter.hasNext(); )
 810             {
 811                 List flavors =
 812                     getFlavorsForNative((String)natives_iter.next());
 813                 for (Iterator flavors_iter = flavors.iterator();
 814                      flavors_iter.hasNext(); )
 815                 {
 816                     Object flavor = flavors_iter.next();
 817                     if (dups.add(flavor)) {
 818                         retval.add(flavor);
 819                     }
 820                 }
 821             }
 822         } else {
 823             List flavors = nativeToFlavorLookup(nat);
 824 
 825             if (disabledMappingGenerationKeys.contains(nat)) {
 826                 return flavors;
 827             }
 828 
 829             HashSet dups = new HashSet(flavors.size());
 830 
 831             List flavorsAndbaseTypes = nativeToFlavorLookup(nat);
 832 
 833             for (Iterator flavorsAndbaseTypes_iter =
 834                      flavorsAndbaseTypes.iterator();
 835                  flavorsAndbaseTypes_iter.hasNext(); )
 836             {
 837                 Object value = flavorsAndbaseTypes_iter.next();
 838                 if (value instanceof String) {
 839                     String baseType = (String)value;
 840                     String subType = null;
 841                     try {
 842                         MimeType mimeType = new MimeType(baseType);
 843                         subType = mimeType.getSubType();
 844                     } catch (MimeTypeParseException mtpe) {
 845                         // Cannot happen, since we checked all mappings
 846                         // on load from flavormap.properties.
 847                         assert(false);
 848                     }
 849                     if (DataTransferer.doesSubtypeSupportCharset(subType,
 850                                                                  null)) {
 851                         if (TEXT_PLAIN_BASE_TYPE.equals(baseType) &&
 852                             dups.add(DataFlavor.stringFlavor))
 853                         {
 854                             retval.add(DataFlavor.stringFlavor);
 855                         }
 856 
 857                         for (int i = 0; i < UNICODE_TEXT_CLASSES.length; i++) {
 858                             DataFlavor toAdd = null;
 859                             try {
 860                                 toAdd = new DataFlavor
 861                                     (baseType + ";charset=Unicode;class=" +
 862                                      UNICODE_TEXT_CLASSES[i]);
 863                             } catch (ClassNotFoundException cannotHappen) {
 864                             }
 865                             if (dups.add(toAdd)) {
 866                                 retval.add(toAdd);
 867                             }
 868                         }
 869 
 870                         for (Iterator charset_iter =
 871                                  DataTransferer.standardEncodings();
 872                              charset_iter.hasNext(); )
 873                         {
 874                             String charset = (String)charset_iter.next();
 875 
 876                             for (int i = 0; i < ENCODED_TEXT_CLASSES.length;
 877                                  i++)
 878                             {
 879                                 DataFlavor toAdd = null;
 880                                 try {
 881                                     toAdd = new DataFlavor
 882                                         (baseType + ";charset=" + charset +
 883                                          ";class=" + ENCODED_TEXT_CLASSES[i]);
 884                                 } catch (ClassNotFoundException cannotHappen) {
 885                                 }
 886 
 887                                 // Check for equality to plainTextFlavor so
 888                                 // that we can ensure that the exact charset of
 889                                 // plainTextFlavor, not the canonical charset
 890                                 // or another equivalent charset with a
 891                                 // different name, is used.
 892                                 if (toAdd.equals(DataFlavor.plainTextFlavor)) {
 893                                     toAdd = DataFlavor.plainTextFlavor;
 894                                 }
 895 
 896                                 if (dups.add(toAdd)) {
 897                                     retval.add(toAdd);
 898                                 }
 899                             }
 900                         }
 901 
 902                         if (TEXT_PLAIN_BASE_TYPE.equals(baseType) &&
 903                             dups.add(DataFlavor.plainTextFlavor))
 904                         {
 905                             retval.add(DataFlavor.plainTextFlavor);
 906                         }
 907                     } else {
 908                         // Non-charset text natives should be treated as
 909                         // opaque, 8-bit data in any of its various
 910                         // representations.
 911                         for (int i = 0; i < ENCODED_TEXT_CLASSES.length; i++) {
 912                             DataFlavor toAdd = null;
 913                             try {
 914                                 toAdd = new DataFlavor(baseType +
 915                                      ";class=" + ENCODED_TEXT_CLASSES[i]);
 916                             } catch (ClassNotFoundException cannotHappen) {
 917                             }
 918 
 919                             if (dups.add(toAdd)) {
 920                                 retval.add(toAdd);
 921                             }
 922                         }
 923                     }
 924                 } else {
 925                     DataFlavor flavor = (DataFlavor)value;
 926                     if (dups.add(flavor)) {
 927                         retval.add(flavor);
 928                     }
 929                 }
 930             }
 931         }
 932 
 933         ArrayList arrayList = new ArrayList(retval);
 934         getFlavorsForNativeCache.put(nat, new SoftReference(arrayList));
 935         return (List)arrayList.clone();
 936     }
 937 
 938     /**
 939      * Returns a <code>Map</code> of the specified <code>DataFlavor</code>s to
 940      * their most preferred <code>String</code> native. Each native value will
 941      * be the same as the first native in the List returned by
 942      * <code>getNativesForFlavor</code> for the specified flavor.
 943      * <p>
 944      * If a specified <code>DataFlavor</code> is previously unknown to the
 945      * data transfer subsystem, then invoking this method will establish a
 946      * mapping in both directions between the specified <code>DataFlavor</code>
 947      * and an encoded version of its MIME type as its native.
 948      *
 949      * @param flavors an array of <code>DataFlavor</code>s which will be the
 950      *        key set of the returned <code>Map</code>. If <code>null</code> is
 951      *        specified, a mapping of all <code>DataFlavor</code>s known to the
 952      *        data transfer subsystem to their most preferred
 953      *        <code>String</code> natives will be returned.
 954      * @return a <code>java.util.Map</code> of <code>DataFlavor</code>s to
 955      *         <code>String</code> natives
 956      *
 957      * @see #getNativesForFlavor
 958      * @see #encodeDataFlavor
 959      */
 960     public synchronized Map<DataFlavor,String>
 961         getNativesForFlavors(DataFlavor[] flavors)
 962     {
 963         // Use getNativesForFlavor to generate extra natives for text flavors
 964         // and stringFlavor
 965 
 966         if (flavors == null) {
 967             List flavor_list = getFlavorsForNative(null);
 968             flavors = new DataFlavor[flavor_list.size()];
 969             flavor_list.toArray(flavors);
 970         }
 971 
 972         HashMap retval = new HashMap(flavors.length, 1.0f);
 973         for (int i = 0; i < flavors.length; i++) {
 974             List natives = getNativesForFlavor(flavors[i]);
 975             String nat = (natives.isEmpty()) ? null : (String)natives.get(0);
 976             retval.put(flavors[i], nat);
 977         }
 978 
 979         return retval;
 980     }
 981 
 982     /**
 983      * Returns a <code>Map</code> of the specified <code>String</code> natives
 984      * to their most preferred <code>DataFlavor</code>. Each
 985      * <code>DataFlavor</code> value will be the same as the first
 986      * <code>DataFlavor</code> in the List returned by
 987      * <code>getFlavorsForNative</code> for the specified native.
 988      * <p>
 989      * If a specified native is previously unknown to the data transfer
 990      * subsystem, and that native has been properly encoded, then invoking this
 991      * method will establish a mapping in both directions between the specified
 992      * native and a <code>DataFlavor</code> whose MIME type is a decoded
 993      * version of the native.
 994      *
 995      * @param natives an array of <code>String</code>s which will be the
 996      *        key set of the returned <code>Map</code>. If <code>null</code> is
 997      *        specified, a mapping of all supported <code>String</code> natives
 998      *        to their most preferred <code>DataFlavor</code>s will be
 999      *        returned.
1000      * @return a <code>java.util.Map</code> of <code>String</code> natives to
1001      *         <code>DataFlavor</code>s
1002      *
1003      * @see #getFlavorsForNative
1004      * @see #encodeJavaMIMEType
1005      */
1006     public synchronized Map<String,DataFlavor>
1007         getFlavorsForNatives(String[] natives)
1008     {
1009         // Use getFlavorsForNative to generate extra flavors for text natives
1010 
1011         if (natives == null) {
1012             List native_list = getNativesForFlavor(null);
1013             natives = new String[native_list.size()];
1014             native_list.toArray(natives);
1015         }
1016 
1017         HashMap retval = new HashMap(natives.length, 1.0f);
1018         for (int i = 0; i < natives.length; i++) {
1019             List flavors = getFlavorsForNative(natives[i]);
1020             DataFlavor flav = (flavors.isEmpty())
1021                 ? null : (DataFlavor)flavors.get(0);
1022             retval.put(natives[i], flav);
1023         }
1024 
1025         return retval;
1026     }
1027 
1028     /**
1029      * Adds a mapping from the specified <code>DataFlavor</code> (and all
1030      * <code>DataFlavor</code>s equal to the specified <code>DataFlavor</code>)
1031      * to the specified <code>String</code> native.
1032      * Unlike <code>getNativesForFlavor</code>, the mapping will only be
1033      * established in one direction, and the native will not be encoded. To
1034      * establish a two-way mapping, call
1035      * <code>addFlavorForUnencodedNative</code> as well. The new mapping will
1036      * be of lower priority than any existing mapping.
1037      * This method has no effect if a mapping from the specified or equal
1038      * <code>DataFlavor</code> to the specified <code>String</code> native
1039      * already exists.
1040      *
1041      * @param flav the <code>DataFlavor</code> key for the mapping
1042      * @param nat the <code>String</code> native value for the mapping
1043      * @throws NullPointerException if flav or nat is <code>null</code>
1044      *
1045      * @see #addFlavorForUnencodedNative
1046      * @since 1.4
1047      */
1048     public synchronized void addUnencodedNativeForFlavor(DataFlavor flav,
1049                                                          String nat) {
1050         if (flav == null || nat == null) {
1051             throw new NullPointerException("null arguments not permitted");
1052         }
1053 
1054         List natives = (List)getFlavorToNative().get(flav);
1055         if (natives == null) {
1056             natives = new ArrayList(1);
1057             getFlavorToNative().put(flav, natives);
1058         } else if (natives.contains(nat)) {
1059             return;
1060         }
1061         natives.add(nat);
1062         getNativesForFlavorCache.remove(flav);
1063         getNativesForFlavorCache.remove(null);
1064     }
1065 
1066     /**
1067      * Discards the current mappings for the specified <code>DataFlavor</code>
1068      * and all <code>DataFlavor</code>s equal to the specified
1069      * <code>DataFlavor</code>, and creates new mappings to the
1070      * specified <code>String</code> natives.
1071      * Unlike <code>getNativesForFlavor</code>, the mappings will only be
1072      * established in one direction, and the natives will not be encoded. To
1073      * establish two-way mappings, call <code>setFlavorsForNative</code>
1074      * as well. The first native in the array will represent the highest
1075      * priority mapping. Subsequent natives will represent mappings of
1076      * decreasing priority.
1077      * <p>
1078      * If the array contains several elements that reference equal
1079      * <code>String</code> natives, this method will establish new mappings
1080      * for the first of those elements and ignore the rest of them.
1081      * <p>
1082      * It is recommended that client code not reset mappings established by the
1083      * data transfer subsystem. This method should only be used for
1084      * application-level mappings.
1085      *
1086      * @param flav the <code>DataFlavor</code> key for the mappings
1087      * @param natives the <code>String</code> native values for the mappings
1088      * @throws NullPointerException if flav or natives is <code>null</code>
1089      *         or if natives contains <code>null</code> elements
1090      *
1091      * @see #setFlavorsForNative
1092      * @since 1.4
1093      */
1094     public synchronized void setNativesForFlavor(DataFlavor flav,
1095                                                  String[] natives) {
1096         if (flav == null || natives == null) {
1097             throw new NullPointerException("null arguments not permitted");
1098         }
1099 
1100         getFlavorToNative().remove(flav);
1101         for (int i = 0; i < natives.length; i++) {
1102             addUnencodedNativeForFlavor(flav, natives[i]);
1103         }
1104         disabledMappingGenerationKeys.add(flav);
1105         // Clear the cache to handle the case of empty natives.
1106         getNativesForFlavorCache.remove(flav);
1107         getNativesForFlavorCache.remove(null);
1108     }
1109 
1110     /**
1111      * Adds a mapping from a single <code>String</code> native to a single
1112      * <code>DataFlavor</code>. Unlike <code>getFlavorsForNative</code>, the
1113      * mapping will only be established in one direction, and the native will
1114      * not be encoded. To establish a two-way mapping, call
1115      * <code>addUnencodedNativeForFlavor</code> as well. The new mapping will
1116      * be of lower priority than any existing mapping.
1117      * This method has no effect if a mapping from the specified
1118      * <code>String</code> native to the specified or equal
1119      * <code>DataFlavor</code> already exists.
1120      *
1121      * @param nat the <code>String</code> native key for the mapping
1122      * @param flav the <code>DataFlavor</code> value for the mapping
1123      * @throws NullPointerException if nat or flav is <code>null</code>
1124      *
1125      * @see #addUnencodedNativeForFlavor
1126      * @since 1.4
1127      */
1128     public synchronized void addFlavorForUnencodedNative(String nat,
1129                                                          DataFlavor flav) {
1130         if (nat == null || flav == null) {
1131             throw new NullPointerException("null arguments not permitted");
1132         }
1133 
1134         List flavors = (List)getNativeToFlavor().get(nat);
1135         if (flavors == null) {
1136             flavors = new ArrayList(1);
1137             getNativeToFlavor().put(nat, flavors);
1138         } else if (flavors.contains(flav)) {
1139             return;
1140         }
1141         flavors.add(flav);
1142         getFlavorsForNativeCache.remove(nat);
1143         getFlavorsForNativeCache.remove(null);
1144     }
1145 
1146     /**
1147      * Discards the current mappings for the specified <code>String</code>
1148      * native, and creates new mappings to the specified
1149      * <code>DataFlavor</code>s. Unlike <code>getFlavorsForNative</code>, the
1150      * mappings will only be established in one direction, and the natives need
1151      * not be encoded. To establish two-way mappings, call
1152      * <code>setNativesForFlavor</code> as well. The first
1153      * <code>DataFlavor</code> in the array will represent the highest priority
1154      * mapping. Subsequent <code>DataFlavor</code>s will represent mappings of
1155      * decreasing priority.
1156      * <p>
1157      * If the array contains several elements that reference equal
1158      * <code>DataFlavor</code>s, this method will establish new mappings
1159      * for the first of those elements and ignore the rest of them.
1160      * <p>
1161      * It is recommended that client code not reset mappings established by the
1162      * data transfer subsystem. This method should only be used for
1163      * application-level mappings.
1164      *
1165      * @param nat the <code>String</code> native key for the mappings
1166      * @param flavors the <code>DataFlavor</code> values for the mappings
1167      * @throws NullPointerException if nat or flavors is <code>null</code>
1168      *         or if flavors contains <code>null</code> elements
1169      *
1170      * @see #setNativesForFlavor
1171      * @since 1.4
1172      */
1173     public synchronized void setFlavorsForNative(String nat,
1174                                                  DataFlavor[] flavors) {
1175         if (nat == null || flavors == null) {
1176             throw new NullPointerException("null arguments not permitted");
1177         }
1178 
1179         getNativeToFlavor().remove(nat);
1180         for (int i = 0; i < flavors.length; i++) {
1181             addFlavorForUnencodedNative(nat, flavors[i]);
1182         }
1183         disabledMappingGenerationKeys.add(nat);
1184         // Clear the cache to handle the case of empty flavors.
1185         getFlavorsForNativeCache.remove(nat);
1186         getFlavorsForNativeCache.remove(null);
1187     }
1188 
1189     /**
1190      * Encodes a MIME type for use as a <code>String</code> native. The format
1191      * of an encoded representation of a MIME type is implementation-dependent.
1192      * The only restrictions are:
1193      * <ul>
1194      * <li>The encoded representation is <code>null</code> if and only if the
1195      * MIME type <code>String</code> is <code>null</code>.</li>
1196      * <li>The encoded representations for two non-<code>null</code> MIME type
1197      * <code>String</code>s are equal if and only if these <code>String</code>s
1198      * are equal according to <code>String.equals(Object)</code>.</li>
1199      * </ul>
1200      * <p>
1201      * Sun's reference implementation of this method returns the specified MIME
1202      * type <code>String</code> prefixed with <code>JAVA_DATAFLAVOR:</code>.
1203      *
1204      * @param mimeType the MIME type to encode
1205      * @return the encoded <code>String</code>, or <code>null</code> if
1206      *         mimeType is <code>null</code>
1207      */
1208     public static String encodeJavaMIMEType(String mimeType) {
1209         return (mimeType != null)
1210             ? JavaMIME + mimeType
1211             : null;
1212     }
1213 
1214     /**
1215      * Encodes a <code>DataFlavor</code> for use as a <code>String</code>
1216      * native. The format of an encoded <code>DataFlavor</code> is
1217      * implementation-dependent. The only restrictions are:
1218      * <ul>
1219      * <li>The encoded representation is <code>null</code> if and only if the
1220      * specified <code>DataFlavor</code> is <code>null</code> or its MIME type
1221      * <code>String</code> is <code>null</code>.</li>
1222      * <li>The encoded representations for two non-<code>null</code>
1223      * <code>DataFlavor</code>s with non-<code>null</code> MIME type
1224      * <code>String</code>s are equal if and only if the MIME type
1225      * <code>String</code>s of these <code>DataFlavor</code>s are equal
1226      * according to <code>String.equals(Object)</code>.</li>
1227      * </ul>
1228      * <p>
1229      * Sun's reference implementation of this method returns the MIME type
1230      * <code>String</code> of the specified <code>DataFlavor</code> prefixed
1231      * with <code>JAVA_DATAFLAVOR:</code>.
1232      *
1233      * @param flav the <code>DataFlavor</code> to encode
1234      * @return the encoded <code>String</code>, or <code>null</code> if
1235      *         flav is <code>null</code> or has a <code>null</code> MIME type
1236      */
1237     public static String encodeDataFlavor(DataFlavor flav) {
1238         return (flav != null)
1239             ? SystemFlavorMap.encodeJavaMIMEType(flav.getMimeType())
1240             : null;
1241     }
1242 
1243     /**
1244      * Returns whether the specified <code>String</code> is an encoded Java
1245      * MIME type.
1246      *
1247      * @param str the <code>String</code> to test
1248      * @return <code>true</code> if the <code>String</code> is encoded;
1249      *         <code>false</code> otherwise
1250      */
1251     public static boolean isJavaMIMEType(String str) {
1252         return (str != null && str.startsWith(JavaMIME, 0));
1253     }
1254 
1255     /**
1256      * Decodes a <code>String</code> native for use as a Java MIME type.
1257      *
1258      * @param nat the <code>String</code> to decode
1259      * @return the decoded Java MIME type, or <code>null</code> if nat is not
1260      *         an encoded <code>String</code> native
1261      */
1262     public static String decodeJavaMIMEType(String nat) {
1263         return (isJavaMIMEType(nat))
1264             ? nat.substring(JavaMIME.length(), nat.length()).trim()
1265             : null;
1266     }
1267 
1268     /**
1269      * Decodes a <code>String</code> native for use as a
1270      * <code>DataFlavor</code>.
1271      *
1272      * @param nat the <code>String</code> to decode
1273      * @return the decoded <code>DataFlavor</code>, or <code>null</code> if
1274      *         nat is not an encoded <code>String</code> native
1275      */
1276     public static DataFlavor decodeDataFlavor(String nat)
1277         throws ClassNotFoundException
1278     {
1279         String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat);
1280         return (retval_str != null)
1281             ? new DataFlavor(retval_str)
1282             : null;
1283     }
1284 }