jdk/src/java.desktop/share/classes/sun/datatransfer/DataFlavorUtil.java
changeset 26037 508779ce6619
parent 26010 9a3cf8ee0776
child 27057 2202074399cf
equal deleted inserted replaced
25992:e9b05e933ddd 26037:508779ce6619
       
     1 /*
       
     2  * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package sun.datatransfer;
       
    27 
       
    28 import java.awt.datatransfer.DataFlavor;
       
    29 import java.awt.datatransfer.FlavorMap;
       
    30 import java.io.IOException;
       
    31 import java.io.InputStream;
       
    32 import java.io.Reader;
       
    33 import java.lang.reflect.Constructor;
       
    34 import java.lang.reflect.InvocationTargetException;
       
    35 import java.lang.reflect.Method;
       
    36 import java.nio.ByteBuffer;
       
    37 import java.nio.CharBuffer;
       
    38 import java.nio.charset.Charset;
       
    39 import java.nio.charset.IllegalCharsetNameException;
       
    40 import java.nio.charset.StandardCharsets;
       
    41 import java.nio.charset.UnsupportedCharsetException;
       
    42 import java.util.Collections;
       
    43 import java.util.Comparator;
       
    44 import java.util.HashMap;
       
    45 import java.util.Iterator;
       
    46 import java.util.LinkedHashSet;
       
    47 import java.util.Map;
       
    48 import java.util.ServiceLoader;
       
    49 import java.util.Set;
       
    50 import java.util.SortedSet;
       
    51 import java.util.TreeSet;
       
    52 import java.util.function.Supplier;
       
    53 
       
    54 
       
    55 /**
       
    56  * Utility class with different datatransfer helper functions
       
    57  *
       
    58  * @see 1.9
       
    59  */
       
    60 public class DataFlavorUtil {
       
    61 
       
    62     private DataFlavorUtil() {
       
    63         // Avoid instantiation
       
    64     }
       
    65 
       
    66     private static Comparator<String> getCharsetComparator() {
       
    67        return CharsetComparator.INSTANCE;
       
    68     }
       
    69 
       
    70     public static Comparator<DataFlavor> getDataFlavorComparator() {
       
    71        return DataFlavorComparator.INSTANCE;
       
    72     }
       
    73 
       
    74     public static Comparator<Long> getIndexOrderComparator(Map<Long, Integer> indexMap) {
       
    75         return new IndexOrderComparator(indexMap);
       
    76     }
       
    77 
       
    78     public static Comparator<DataFlavor> getTextFlavorComparator() {
       
    79         return TextFlavorComparator.INSTANCE;
       
    80     }
       
    81 
       
    82     /**
       
    83      * Tracks whether a particular text/* MIME type supports the charset
       
    84      * parameter. The Map is initialized with all of the standard MIME types
       
    85      * listed in the DataFlavor.selectBestTextFlavor method comment. Additional
       
    86      * entries may be added during the life of the JRE for text/<other> types.
       
    87      */
       
    88     private static final Map<String, Boolean> textMIMESubtypeCharsetSupport;
       
    89 
       
    90     static {
       
    91         Map<String, Boolean> tempMap = new HashMap<>(17);
       
    92         tempMap.put("sgml", Boolean.TRUE);
       
    93         tempMap.put("xml", Boolean.TRUE);
       
    94         tempMap.put("html", Boolean.TRUE);
       
    95         tempMap.put("enriched", Boolean.TRUE);
       
    96         tempMap.put("richtext", Boolean.TRUE);
       
    97         tempMap.put("uri-list", Boolean.TRUE);
       
    98         tempMap.put("directory", Boolean.TRUE);
       
    99         tempMap.put("css", Boolean.TRUE);
       
   100         tempMap.put("calendar", Boolean.TRUE);
       
   101         tempMap.put("plain", Boolean.TRUE);
       
   102         tempMap.put("rtf", Boolean.FALSE);
       
   103         tempMap.put("tab-separated-values", Boolean.FALSE);
       
   104         tempMap.put("t140", Boolean.FALSE);
       
   105         tempMap.put("rfc822-headers", Boolean.FALSE);
       
   106         tempMap.put("parityfec", Boolean.FALSE);
       
   107         textMIMESubtypeCharsetSupport = Collections.synchronizedMap(tempMap);
       
   108     }
       
   109 
       
   110     /**
       
   111      * Lazy initialization of Standard Encodings.
       
   112      */
       
   113     private static class StandardEncodingsHolder {
       
   114         private static final SortedSet<String> standardEncodings = load();
       
   115 
       
   116         private static SortedSet<String> load() {
       
   117             final SortedSet<String> tempSet = new TreeSet<>(getCharsetComparator().reversed());
       
   118             tempSet.add("US-ASCII");
       
   119             tempSet.add("ISO-8859-1");
       
   120             tempSet.add("UTF-8");
       
   121             tempSet.add("UTF-16BE");
       
   122             tempSet.add("UTF-16LE");
       
   123             tempSet.add("UTF-16");
       
   124             tempSet.add(Charset.defaultCharset().name());
       
   125             return Collections.unmodifiableSortedSet(tempSet);
       
   126         }
       
   127     }
       
   128 
       
   129     /**
       
   130      * Returns a {@code SortedSet} of Strings which are a total order of the standard
       
   131      * character sets supported by the JRE. The ordering follows the same principles as
       
   132      * {@link java.awt.datatransfer.DataFlavor#selectBestTextFlavor(java.awt.datatransfer.DataFlavor[])}.
       
   133      * So as to avoid loading all available character converters, optional, non-standard,
       
   134      * character sets are not included.
       
   135      */
       
   136     public static Set<String> standardEncodings() {
       
   137         return StandardEncodingsHolder.standardEncodings;
       
   138     }
       
   139 
       
   140     /**
       
   141      * Converts an arbitrary text encoding to its canonical name.
       
   142      */
       
   143     public static String canonicalName(String encoding) {
       
   144         if (encoding == null) {
       
   145             return null;
       
   146         }
       
   147         try {
       
   148             return Charset.forName(encoding).name();
       
   149         } catch (IllegalCharsetNameException icne) {
       
   150             return encoding;
       
   151         } catch (UnsupportedCharsetException uce) {
       
   152             return encoding;
       
   153         }
       
   154     }
       
   155 
       
   156     /**
       
   157      * Tests only whether the flavor's MIME type supports the charset
       
   158      * parameter. Must only be called for flavors with a primary type of
       
   159      * "text".
       
   160      */
       
   161     public static boolean doesSubtypeSupportCharset(DataFlavor flavor) {
       
   162         String subType = flavor.getSubType();
       
   163         if (subType == null) {
       
   164             return false;
       
   165         }
       
   166 
       
   167         Boolean support = textMIMESubtypeCharsetSupport.get(subType);
       
   168 
       
   169         if (support != null) {
       
   170             return support;
       
   171         }
       
   172 
       
   173         boolean ret_val = (flavor.getParameter("charset") != null);
       
   174         textMIMESubtypeCharsetSupport.put(subType, ret_val);
       
   175         return ret_val;
       
   176     }
       
   177     public static boolean doesSubtypeSupportCharset(String subType,
       
   178                                                     String charset)
       
   179     {
       
   180         Boolean support = textMIMESubtypeCharsetSupport.get(subType);
       
   181 
       
   182         if (support != null) {
       
   183             return support;
       
   184         }
       
   185 
       
   186         boolean ret_val = (charset != null);
       
   187         textMIMESubtypeCharsetSupport.put(subType, ret_val);
       
   188         return ret_val;
       
   189     }
       
   190 
       
   191 
       
   192     /**
       
   193      * Returns whether this flavor is a text type which supports the
       
   194      * 'charset' parameter.
       
   195      */
       
   196     public static boolean isFlavorCharsetTextType(DataFlavor flavor) {
       
   197         // Although stringFlavor doesn't actually support the charset
       
   198         // parameter (because its primary MIME type is not "text"), it should
       
   199         // be treated as though it does. stringFlavor is semantically
       
   200         // equivalent to "text/plain" data.
       
   201         if (DataFlavor.stringFlavor.equals(flavor)) {
       
   202             return true;
       
   203         }
       
   204 
       
   205         if (!"text".equals(flavor.getPrimaryType()) ||
       
   206                 !doesSubtypeSupportCharset(flavor))
       
   207         {
       
   208             return false;
       
   209         }
       
   210 
       
   211         Class<?> rep_class = flavor.getRepresentationClass();
       
   212 
       
   213         if (flavor.isRepresentationClassReader() ||
       
   214                 String.class.equals(rep_class) ||
       
   215                 flavor.isRepresentationClassCharBuffer() ||
       
   216                 char[].class.equals(rep_class))
       
   217         {
       
   218             return true;
       
   219         }
       
   220 
       
   221         if (!(flavor.isRepresentationClassInputStream() ||
       
   222                 flavor.isRepresentationClassByteBuffer() ||
       
   223                 byte[].class.equals(rep_class))) {
       
   224             return false;
       
   225         }
       
   226 
       
   227         String charset = flavor.getParameter("charset");
       
   228 
       
   229         // null equals default encoding which is always supported
       
   230         return (charset == null) || isEncodingSupported(charset);
       
   231     }
       
   232 
       
   233     /**
       
   234      * Returns whether this flavor is a text type which does not support the
       
   235      * 'charset' parameter.
       
   236      */
       
   237     public static boolean isFlavorNoncharsetTextType(DataFlavor flavor) {
       
   238         if (!"text".equals(flavor.getPrimaryType()) || doesSubtypeSupportCharset(flavor)) {
       
   239             return false;
       
   240         }
       
   241 
       
   242         return (flavor.isRepresentationClassInputStream() ||
       
   243                 flavor.isRepresentationClassByteBuffer() ||
       
   244                 byte[].class.equals(flavor.getRepresentationClass()));
       
   245     }
       
   246 
       
   247     /**
       
   248      * If the specified flavor is a text flavor which supports the "charset"
       
   249      * parameter, then this method returns that parameter, or the default
       
   250      * charset if no such parameter was specified at construction. For non-
       
   251      * text DataFlavors, and for non-charset text flavors, this method returns
       
   252      * null.
       
   253      */
       
   254     public static String getTextCharset(DataFlavor flavor) {
       
   255         if (!isFlavorCharsetTextType(flavor)) {
       
   256             return null;
       
   257         }
       
   258 
       
   259         String encoding = flavor.getParameter("charset");
       
   260 
       
   261         return (encoding != null) ? encoding : Charset.defaultCharset().name();
       
   262     }
       
   263 
       
   264     /**
       
   265      * Determines whether this JRE can both encode and decode text in the
       
   266      * specified encoding.
       
   267      */
       
   268     private static boolean isEncodingSupported(String encoding) {
       
   269         if (encoding == null) {
       
   270             return false;
       
   271         }
       
   272         try {
       
   273             return Charset.isSupported(encoding);
       
   274         } catch (IllegalCharsetNameException icne) {
       
   275             return false;
       
   276         }
       
   277     }
       
   278 
       
   279     /**
       
   280      * Helper method to compare two objects by their Integer indices in the
       
   281      * given map. If the map doesn't contain an entry for either of the
       
   282      * objects, the fallback index will be used for the object instead.
       
   283      *
       
   284      * @param indexMap the map which maps objects into Integer indexes.
       
   285      * @param obj1 the first object to be compared.
       
   286      * @param obj2 the second object to be compared.
       
   287      * @param fallbackIndex the Integer to be used as a fallback index.
       
   288      * @return a negative integer, zero, or a positive integer as the
       
   289      *             first object is mapped to a less, equal to, or greater
       
   290      *             index than the second.
       
   291      */
       
   292     static <T> int compareIndices(Map<T, Integer> indexMap,
       
   293                                   T obj1, T obj2,
       
   294                                   Integer fallbackIndex) {
       
   295         Integer index1 = indexMap.getOrDefault(obj1, fallbackIndex);
       
   296         Integer index2 = indexMap.getOrDefault(obj2, fallbackIndex);
       
   297         return index1.compareTo(index2);
       
   298     }
       
   299 
       
   300     /**
       
   301      * An IndexedComparator which compares two String charsets. The comparison
       
   302      * follows the rules outlined in DataFlavor.selectBestTextFlavor. In order
       
   303      * to ensure that non-Unicode, non-ASCII, non-default charsets are sorted
       
   304      * in alphabetical order, charsets are not automatically converted to their
       
   305      * canonical forms.
       
   306      */
       
   307     private static class CharsetComparator implements Comparator<String> {
       
   308         static final CharsetComparator INSTANCE = new CharsetComparator();
       
   309 
       
   310         private static final Map<String, Integer> charsets;
       
   311 
       
   312         private static final Integer DEFAULT_CHARSET_INDEX = 2;
       
   313         private static final Integer OTHER_CHARSET_INDEX = 1;
       
   314         private static final Integer WORST_CHARSET_INDEX = 0;
       
   315         private static final Integer UNSUPPORTED_CHARSET_INDEX = Integer.MIN_VALUE;
       
   316 
       
   317         private static final String UNSUPPORTED_CHARSET = "UNSUPPORTED";
       
   318 
       
   319         static {
       
   320             Map<String, Integer> charsetsMap = new HashMap<>(8, 1.0f);
       
   321 
       
   322             // we prefer Unicode charsets
       
   323             charsetsMap.put(canonicalName("UTF-16LE"), 4);
       
   324             charsetsMap.put(canonicalName("UTF-16BE"), 5);
       
   325             charsetsMap.put(canonicalName("UTF-8"), 6);
       
   326             charsetsMap.put(canonicalName("UTF-16"), 7);
       
   327 
       
   328             // US-ASCII is the worst charset supported
       
   329             charsetsMap.put(canonicalName("US-ASCII"), WORST_CHARSET_INDEX);
       
   330 
       
   331             charsetsMap.putIfAbsent(Charset.defaultCharset().name(), DEFAULT_CHARSET_INDEX);
       
   332 
       
   333             charsetsMap.put(UNSUPPORTED_CHARSET, UNSUPPORTED_CHARSET_INDEX);
       
   334 
       
   335             charsets = Collections.unmodifiableMap(charsetsMap);
       
   336         }
       
   337 
       
   338         /**
       
   339          * Compares charsets. Returns a negative integer, zero, or a positive
       
   340          * integer as the first charset is worse than, equal to, or better than
       
   341          * the second.
       
   342          * <p>
       
   343          * Charsets are ordered according to the following rules:
       
   344          * <ul>
       
   345          * <li>All unsupported charsets are equal.
       
   346          * <li>Any unsupported charset is worse than any supported charset.
       
   347          * <li>Unicode charsets, such as "UTF-16", "UTF-8", "UTF-16BE" and
       
   348          *     "UTF-16LE", are considered best.
       
   349          * <li>After them, platform default charset is selected.
       
   350          * <li>"US-ASCII" is the worst of supported charsets.
       
   351          * <li>For all other supported charsets, the lexicographically less
       
   352          *     one is considered the better.
       
   353          * </ul>
       
   354          *
       
   355          * @param charset1 the first charset to be compared
       
   356          * @param charset2 the second charset to be compared.
       
   357          * @return a negative integer, zero, or a positive integer as the
       
   358          *             first argument is worse, equal to, or better than the
       
   359          *             second.
       
   360          */
       
   361         public int compare(String charset1, String charset2) {
       
   362             charset1 = getEncoding(charset1);
       
   363             charset2 = getEncoding(charset2);
       
   364 
       
   365             int comp = compareIndices(charsets, charset1, charset2, OTHER_CHARSET_INDEX);
       
   366 
       
   367             if (comp == 0) {
       
   368                 return charset2.compareTo(charset1);
       
   369             }
       
   370 
       
   371             return comp;
       
   372         }
       
   373 
       
   374         /**
       
   375          * Returns encoding for the specified charset according to the
       
   376          * following rules:
       
   377          * <ul>
       
   378          * <li>If the charset is <code>null</code>, then <code>null</code> will
       
   379          *     be returned.
       
   380          * <li>Iff the charset specifies an encoding unsupported by this JRE,
       
   381          *     <code>UNSUPPORTED_CHARSET</code> will be returned.
       
   382          * <li>If the charset specifies an alias name, the corresponding
       
   383          *     canonical name will be returned iff the charset is a known
       
   384          *     Unicode, ASCII, or default charset.
       
   385          * </ul>
       
   386          *
       
   387          * @param charset the charset.
       
   388          * @return an encoding for this charset.
       
   389          */
       
   390         static String getEncoding(String charset) {
       
   391             if (charset == null) {
       
   392                 return null;
       
   393             } else if (!isEncodingSupported(charset)) {
       
   394                 return UNSUPPORTED_CHARSET;
       
   395             } else {
       
   396                 // Only convert to canonical form if the charset is one
       
   397                 // of the charsets explicitly listed in the known charsets
       
   398                 // map. This will happen only for Unicode, ASCII, or default
       
   399                 // charsets.
       
   400                 String canonicalName = canonicalName(charset);
       
   401                 return (charsets.containsKey(canonicalName))
       
   402                         ? canonicalName
       
   403                         : charset;
       
   404             }
       
   405         }
       
   406     }
       
   407 
       
   408     /**
       
   409      * An IndexedComparator which compares two DataFlavors. For text flavors,
       
   410      * the comparison follows the rules outlined in
       
   411      * DataFlavor.selectBestTextFlavor. For non-text flavors, unknown
       
   412      * application MIME types are preferred, followed by known
       
   413      * application/x-java-* MIME types. Unknown application types are preferred
       
   414      * because if the user provides his own data flavor, it will likely be the
       
   415      * most descriptive one. For flavors which are otherwise equal, the
       
   416      * flavors' string representation are compared in the alphabetical order.
       
   417      */
       
   418     private static class DataFlavorComparator implements Comparator<DataFlavor> {
       
   419 
       
   420         static final DataFlavorComparator INSTANCE = new DataFlavorComparator();
       
   421 
       
   422         private static final Map<String, Integer> exactTypes;
       
   423         private static final Map<String, Integer> primaryTypes;
       
   424         private static final Map<Class<?>, Integer> nonTextRepresentations;
       
   425         private static final Map<String, Integer> textTypes;
       
   426         private static final Map<Class<?>, Integer> decodedTextRepresentations;
       
   427         private static final Map<Class<?>, Integer> encodedTextRepresentations;
       
   428 
       
   429         private static final Integer UNKNOWN_OBJECT_LOSES = Integer.MIN_VALUE;
       
   430         private static final Integer UNKNOWN_OBJECT_WINS = Integer.MAX_VALUE;
       
   431 
       
   432         static {
       
   433             {
       
   434                 Map<String, Integer> exactTypesMap = new HashMap<>(4, 1.0f);
       
   435 
       
   436                 // application/x-java-* MIME types
       
   437                 exactTypesMap.put("application/x-java-file-list", 0);
       
   438                 exactTypesMap.put("application/x-java-serialized-object", 1);
       
   439                 exactTypesMap.put("application/x-java-jvm-local-objectref", 2);
       
   440                 exactTypesMap.put("application/x-java-remote-object", 3);
       
   441 
       
   442                 exactTypes = Collections.unmodifiableMap(exactTypesMap);
       
   443             }
       
   444 
       
   445             {
       
   446                 Map<String, Integer> primaryTypesMap = new HashMap<>(1, 1.0f);
       
   447 
       
   448                 primaryTypesMap.put("application", 0);
       
   449 
       
   450                 primaryTypes = Collections.unmodifiableMap(primaryTypesMap);
       
   451             }
       
   452 
       
   453             {
       
   454                 Map<Class<?>, Integer> nonTextRepresentationsMap = new HashMap<>(3, 1.0f);
       
   455 
       
   456                 nonTextRepresentationsMap.put(java.io.InputStream.class, 0);
       
   457                 nonTextRepresentationsMap.put(java.io.Serializable.class, 1);
       
   458 
       
   459                 nonTextRepresentationsMap.put(RMI.remoteClass(), 2);
       
   460 
       
   461                 nonTextRepresentations = Collections.unmodifiableMap(nonTextRepresentationsMap);
       
   462             }
       
   463 
       
   464             {
       
   465                 Map<String, Integer> textTypesMap = new HashMap<>(16, 1.0f);
       
   466 
       
   467                 // plain text
       
   468                 textTypesMap.put("text/plain", 0);
       
   469 
       
   470                 // stringFlavor
       
   471                 textTypesMap.put("application/x-java-serialized-object", 1);
       
   472 
       
   473                 // misc
       
   474                 textTypesMap.put("text/calendar", 2);
       
   475                 textTypesMap.put("text/css", 3);
       
   476                 textTypesMap.put("text/directory", 4);
       
   477                 textTypesMap.put("text/parityfec", 5);
       
   478                 textTypesMap.put("text/rfc822-headers", 6);
       
   479                 textTypesMap.put("text/t140", 7);
       
   480                 textTypesMap.put("text/tab-separated-values", 8);
       
   481                 textTypesMap.put("text/uri-list", 9);
       
   482 
       
   483                 // enriched
       
   484                 textTypesMap.put("text/richtext", 10);
       
   485                 textTypesMap.put("text/enriched", 11);
       
   486                 textTypesMap.put("text/rtf", 12);
       
   487 
       
   488                 // markup
       
   489                 textTypesMap.put("text/html", 13);
       
   490                 textTypesMap.put("text/xml", 14);
       
   491                 textTypesMap.put("text/sgml", 15);
       
   492 
       
   493                 textTypes = Collections.unmodifiableMap(textTypesMap);
       
   494             }
       
   495 
       
   496             {
       
   497                 Map<Class<?>, Integer> decodedTextRepresentationsMap = new HashMap<>(4, 1.0f);
       
   498 
       
   499                 decodedTextRepresentationsMap.put(char[].class, 0);
       
   500                 decodedTextRepresentationsMap.put(CharBuffer.class, 1);
       
   501                 decodedTextRepresentationsMap.put(String.class, 2);
       
   502                 decodedTextRepresentationsMap.put(Reader.class, 3);
       
   503 
       
   504                 decodedTextRepresentations =
       
   505                         Collections.unmodifiableMap(decodedTextRepresentationsMap);
       
   506             }
       
   507 
       
   508             {
       
   509                 Map<Class<?>, Integer> encodedTextRepresentationsMap = new HashMap<>(3, 1.0f);
       
   510 
       
   511                 encodedTextRepresentationsMap.put(byte[].class, 0);
       
   512                 encodedTextRepresentationsMap.put(ByteBuffer.class, 1);
       
   513                 encodedTextRepresentationsMap.put(InputStream.class, 2);
       
   514 
       
   515                 encodedTextRepresentations =
       
   516                         Collections.unmodifiableMap(encodedTextRepresentationsMap);
       
   517             }
       
   518         }
       
   519 
       
   520 
       
   521         public int compare(DataFlavor flavor1, DataFlavor flavor2) {
       
   522             if (flavor1.equals(flavor2)) {
       
   523                 return 0;
       
   524             }
       
   525 
       
   526             int comp;
       
   527 
       
   528             String primaryType1 = flavor1.getPrimaryType();
       
   529             String subType1 = flavor1.getSubType();
       
   530             String mimeType1 = primaryType1 + "/" + subType1;
       
   531             Class<?> class1 = flavor1.getRepresentationClass();
       
   532 
       
   533             String primaryType2 = flavor2.getPrimaryType();
       
   534             String subType2 = flavor2.getSubType();
       
   535             String mimeType2 = primaryType2 + "/" + subType2;
       
   536             Class<?> class2 = flavor2.getRepresentationClass();
       
   537 
       
   538             if (flavor1.isFlavorTextType() && flavor2.isFlavorTextType()) {
       
   539                 // First, compare MIME types
       
   540                 comp = compareIndices(textTypes, mimeType1, mimeType2, UNKNOWN_OBJECT_LOSES);
       
   541                 if (comp != 0) {
       
   542                     return comp;
       
   543                 }
       
   544 
       
   545                 // Only need to test one flavor because they both have the
       
   546                 // same MIME type. Also don't need to worry about accidentally
       
   547                 // passing stringFlavor because either
       
   548                 //   1. Both flavors are stringFlavor, in which case the
       
   549                 //      equality test at the top of the function succeeded.
       
   550                 //   2. Only one flavor is stringFlavor, in which case the MIME
       
   551                 //      type comparison returned a non-zero value.
       
   552                 if (doesSubtypeSupportCharset(flavor1)) {
       
   553                     // Next, prefer the decoded text representations of Reader,
       
   554                     // String, CharBuffer, and [C, in that order.
       
   555                     comp = compareIndices(decodedTextRepresentations, class1,
       
   556                             class2, UNKNOWN_OBJECT_LOSES);
       
   557                     if (comp != 0) {
       
   558                         return comp;
       
   559                     }
       
   560 
       
   561                     // Next, compare charsets
       
   562                     comp = CharsetComparator.INSTANCE.compare(getTextCharset(flavor1),
       
   563                             getTextCharset(flavor2));
       
   564                     if (comp != 0) {
       
   565                         return comp;
       
   566                     }
       
   567                 }
       
   568 
       
   569                 // Finally, prefer the encoded text representations of
       
   570                 // InputStream, ByteBuffer, and [B, in that order.
       
   571                 comp = compareIndices(encodedTextRepresentations, class1,
       
   572                         class2, UNKNOWN_OBJECT_LOSES);
       
   573                 if (comp != 0) {
       
   574                     return comp;
       
   575                 }
       
   576             } else {
       
   577                 // First, prefer application types.
       
   578                 comp = compareIndices(primaryTypes, primaryType1, primaryType2,
       
   579                         UNKNOWN_OBJECT_LOSES);
       
   580                 if (comp != 0) {
       
   581                     return comp;
       
   582                 }
       
   583 
       
   584                 // Next, look for application/x-java-* types. Prefer unknown
       
   585                 // MIME types because if the user provides his own data flavor,
       
   586                 // it will likely be the most descriptive one.
       
   587                 comp = compareIndices(exactTypes, mimeType1, mimeType2,
       
   588                         UNKNOWN_OBJECT_WINS);
       
   589                 if (comp != 0) {
       
   590                     return comp;
       
   591                 }
       
   592 
       
   593                 // Finally, prefer the representation classes of Remote,
       
   594                 // Serializable, and InputStream, in that order.
       
   595                 comp = compareIndices(nonTextRepresentations, class1, class2,
       
   596                         UNKNOWN_OBJECT_LOSES);
       
   597                 if (comp != 0) {
       
   598                     return comp;
       
   599                 }
       
   600             }
       
   601 
       
   602             // The flavours are not equal but still not distinguishable.
       
   603             // Compare String representations in alphabetical order
       
   604             return flavor1.getMimeType().compareTo(flavor2.getMimeType());
       
   605         }
       
   606     }
       
   607 
       
   608     /*
       
   609      * Given the Map that maps objects to Integer indices and a boolean value,
       
   610      * this Comparator imposes a direct or reverse order on set of objects.
       
   611      * <p>
       
   612      * If the specified boolean value is SELECT_BEST, the Comparator imposes the
       
   613      * direct index-based order: an object A is greater than an object B if and
       
   614      * only if the index of A is greater than the index of B. An object that
       
   615      * doesn't have an associated index is less or equal than any other object.
       
   616      * <p>
       
   617      * If the specified boolean value is SELECT_WORST, the Comparator imposes the
       
   618      * reverse index-based order: an object A is greater than an object B if and
       
   619      * only if A is less than B with the direct index-based order.
       
   620      */
       
   621     private static class IndexOrderComparator implements Comparator<Long> {
       
   622         private final Map<Long, Integer> indexMap;
       
   623         private static final Integer FALLBACK_INDEX = Integer.MIN_VALUE;
       
   624 
       
   625         public IndexOrderComparator(Map<Long, Integer> indexMap) {
       
   626             this.indexMap = indexMap;
       
   627         }
       
   628 
       
   629         public int compare(Long obj1, Long obj2) {
       
   630             return compareIndices(indexMap, obj1, obj2, FALLBACK_INDEX);
       
   631         }
       
   632     }
       
   633 
       
   634     private static class TextFlavorComparator extends DataFlavorComparator {
       
   635 
       
   636         static final TextFlavorComparator INSTANCE = new TextFlavorComparator();
       
   637         /**
       
   638          * Compares two <code>DataFlavor</code> objects. Returns a negative
       
   639          * integer, zero, or a positive integer as the first
       
   640          * <code>DataFlavor</code> is worse than, equal to, or better than the
       
   641          * second.
       
   642          * <p>
       
   643          * <code>DataFlavor</code>s are ordered according to the rules outlined
       
   644          * for <code>selectBestTextFlavor</code>.
       
   645          *
       
   646          * @param flavor1 the first <code>DataFlavor</code> to be compared
       
   647          * @param flavor2 the second <code>DataFlavor</code> to be compared
       
   648          * @return a negative integer, zero, or a positive integer as the first
       
   649          *         argument is worse, equal to, or better than the second
       
   650          * @throws ClassCastException if either of the arguments is not an
       
   651          *         instance of <code>DataFlavor</code>
       
   652          * @throws NullPointerException if either of the arguments is
       
   653          *         <code>null</code>
       
   654          *
       
   655          * @see java.awt.datatransfer.DataFlavor#selectBestTextFlavor
       
   656          */
       
   657         public int compare(DataFlavor flavor1, DataFlavor flavor2) {
       
   658             if (flavor1.isFlavorTextType()) {
       
   659                 if (flavor2.isFlavorTextType()) {
       
   660                     return super.compare(flavor1, flavor2);
       
   661                 } else {
       
   662                     return 1;
       
   663                 }
       
   664             } else if (flavor2.isFlavorTextType()) {
       
   665                 return -1;
       
   666             } else {
       
   667                 return 0;
       
   668             }
       
   669         }
       
   670     }
       
   671 
       
   672     /**
       
   673      * A fallback implementation of {@link sun.datatransfer.DesktopDatatransferService}
       
   674      * used if there is no desktop.
       
   675      */
       
   676     private static final class DefaultDesktopDatatransferService implements DesktopDatatransferService {
       
   677         static final DesktopDatatransferService INSTANCE = getDesktopService();
       
   678 
       
   679         private static DesktopDatatransferService getDesktopService() {
       
   680             ServiceLoader<DesktopDatatransferService> loader =
       
   681                     ServiceLoader.load(DesktopDatatransferService.class, null);
       
   682             Iterator<DesktopDatatransferService> iterator = loader.iterator();
       
   683             if (iterator.hasNext()) {
       
   684                 return iterator.next();
       
   685             } else {
       
   686                 return new DefaultDesktopDatatransferService();
       
   687             }
       
   688         }
       
   689 
       
   690         /**
       
   691          * System singleton FlavorTable.
       
   692          * Only used if there is no desktop
       
   693          * to provide an appropriate FlavorMap.
       
   694          */
       
   695         private volatile FlavorMap flavorMap;
       
   696 
       
   697         @Override
       
   698         public void invokeOnEventThread(Runnable r) {
       
   699             r.run();
       
   700         }
       
   701 
       
   702         @Override
       
   703         public String getDefaultUnicodeEncoding() {
       
   704             return StandardCharsets.UTF_8.name();
       
   705         }
       
   706 
       
   707         @Override
       
   708         public FlavorMap getFlavorMap(Supplier<FlavorMap> supplier) {
       
   709             FlavorMap map = flavorMap;
       
   710             if (map == null) {
       
   711                 synchronized (this) {
       
   712                     map = flavorMap;
       
   713                     if (map == null) {
       
   714                         flavorMap = map = supplier.get();
       
   715                     }
       
   716                 }
       
   717             }
       
   718             return map;
       
   719         }
       
   720 
       
   721         @Override
       
   722         public boolean isDesktopPresent() {
       
   723             return false;
       
   724         }
       
   725 
       
   726         @Override
       
   727         public LinkedHashSet<DataFlavor> getPlatformMappingsForNative(String nat) {
       
   728             return new LinkedHashSet<>();
       
   729         }
       
   730 
       
   731         @Override
       
   732         public LinkedHashSet<String> getPlatformMappingsForFlavor(DataFlavor df) {
       
   733             return new LinkedHashSet<>();
       
   734         }
       
   735 
       
   736         @Override
       
   737         public void registerTextFlavorProperties(String nat, String charset,
       
   738                                                  String eoln, String terminators) {
       
   739             // Not needed if desktop module is absent
       
   740         }
       
   741     }
       
   742 
       
   743     public static DesktopDatatransferService getDesktopService() {
       
   744         return DefaultDesktopDatatransferService.INSTANCE;
       
   745     }
       
   746 
       
   747     /**
       
   748      * A class that provides access to java.rmi.Remote and java.rmi.MarshalledObject
       
   749      * without creating a static dependency.
       
   750      */
       
   751     public static class RMI {
       
   752         private static final Class<?> remoteClass = getClass("java.rmi.Remote");
       
   753         private static final Class<?> marshallObjectClass = getClass("java.rmi.MarshalledObject");
       
   754         private static final Constructor<?> marshallCtor = getConstructor(marshallObjectClass, Object.class);
       
   755         private static final Method marshallGet = getMethod(marshallObjectClass, "get");
       
   756 
       
   757         private static Class<?> getClass(String name) {
       
   758             try {
       
   759                 return Class.forName(name, true, null);
       
   760             } catch (ClassNotFoundException e) {
       
   761                 return null;
       
   762             }
       
   763         }
       
   764 
       
   765         private static Constructor<?> getConstructor(Class<?> c, Class<?>... types) {
       
   766             try {
       
   767                 return (c == null) ? null : c.getDeclaredConstructor(types);
       
   768             } catch (NoSuchMethodException x) {
       
   769                 throw new AssertionError(x);
       
   770             }
       
   771         }
       
   772 
       
   773         private static Method getMethod(Class<?> c, String name, Class<?>... types) {
       
   774             try {
       
   775                 return (c == null) ? null : c.getMethod(name, types);
       
   776             } catch (NoSuchMethodException e) {
       
   777                 throw new AssertionError(e);
       
   778             }
       
   779         }
       
   780 
       
   781         /**
       
   782          * Returns java.rmi.Remote.class if RMI is present; otherwise {@code null}.
       
   783          */
       
   784         static Class<?> remoteClass() {
       
   785             return remoteClass;
       
   786         }
       
   787 
       
   788         /**
       
   789          * Returns {@code true} if the given class is java.rmi.Remote.
       
   790          */
       
   791         public static boolean isRemote(Class<?> c) {
       
   792             return (remoteClass != null) && remoteClass.isAssignableFrom(c);
       
   793         }
       
   794 
       
   795         /**
       
   796          * Returns a new MarshalledObject containing the serialized representation
       
   797          * of the given object.
       
   798          */
       
   799         public static Object newMarshalledObject(Object obj) throws IOException {
       
   800             try {
       
   801                 return marshallCtor == null ? null : marshallCtor.newInstance(obj);
       
   802             } catch (InstantiationException | IllegalAccessException x) {
       
   803                 throw new AssertionError(x);
       
   804             } catch (InvocationTargetException x) {
       
   805                 Throwable cause = x.getCause();
       
   806                 if (cause instanceof IOException)
       
   807                     throw (IOException) cause;
       
   808                 throw new AssertionError(x);
       
   809             }
       
   810         }
       
   811 
       
   812         /**
       
   813          * Returns a new copy of the contained marshalled object.
       
   814          */
       
   815         public static Object getMarshalledObject(Object obj)
       
   816                 throws IOException, ClassNotFoundException {
       
   817             try {
       
   818                 return marshallGet == null ? null : marshallGet.invoke(obj);
       
   819             } catch (IllegalAccessException x) {
       
   820                 throw new AssertionError(x);
       
   821             } catch (InvocationTargetException x) {
       
   822                 Throwable cause = x.getCause();
       
   823                 if (cause instanceof IOException)
       
   824                     throw (IOException) cause;
       
   825                 if (cause instanceof ClassNotFoundException)
       
   826                     throw (ClassNotFoundException) cause;
       
   827                 throw new AssertionError(x);
       
   828             }
       
   829         }
       
   830 
       
   831     }
       
   832 }