1 /*
   2  * Copyright (c) 2015, 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  * @since 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 text types
 578                 if (flavor1.isFlavorTextType()) {
 579                     return 1;
 580                 }
 581 
 582                 if (flavor2.isFlavorTextType()) {
 583                     return -1;
 584                 }
 585 
 586                 // Next, prefer application types.
 587                 comp = compareIndices(primaryTypes, primaryType1, primaryType2,
 588                         UNKNOWN_OBJECT_LOSES);
 589                 if (comp != 0) {
 590                     return comp;
 591                 }
 592 
 593                 // Next, look for application/x-java-* types. Prefer unknown
 594                 // MIME types because if the user provides his own data flavor,
 595                 // it will likely be the most descriptive one.
 596                 comp = compareIndices(exactTypes, mimeType1, mimeType2,
 597                         UNKNOWN_OBJECT_WINS);
 598                 if (comp != 0) {
 599                     return comp;
 600                 }
 601 
 602                 // Finally, prefer the representation classes of Remote,
 603                 // Serializable, and InputStream, in that order.
 604                 comp = compareIndices(nonTextRepresentations, class1, class2,
 605                         UNKNOWN_OBJECT_LOSES);
 606                 if (comp != 0) {
 607                     return comp;
 608                 }
 609             }
 610 
 611             // The flavours are not equal but still not distinguishable.
 612             // Compare String representations in alphabetical order
 613             return flavor1.getMimeType().compareTo(flavor2.getMimeType());
 614         }
 615     }
 616 
 617     /*
 618      * Given the Map that maps objects to Integer indices and a boolean value,
 619      * this Comparator imposes a direct or reverse order on set of objects.
 620      * <p>
 621      * If the specified boolean value is SELECT_BEST, the Comparator imposes the
 622      * direct index-based order: an object A is greater than an object B if and
 623      * only if the index of A is greater than the index of B. An object that
 624      * doesn't have an associated index is less or equal than any other object.
 625      * <p>
 626      * If the specified boolean value is SELECT_WORST, the Comparator imposes the
 627      * reverse index-based order: an object A is greater than an object B if and
 628      * only if A is less than B with the direct index-based order.
 629      */
 630     private static class IndexOrderComparator implements Comparator<Long> {
 631         private final Map<Long, Integer> indexMap;
 632         private static final Integer FALLBACK_INDEX = Integer.MIN_VALUE;
 633 
 634         public IndexOrderComparator(Map<Long, Integer> indexMap) {
 635             this.indexMap = indexMap;
 636         }
 637 
 638         public int compare(Long obj1, Long obj2) {
 639             return compareIndices(indexMap, obj1, obj2, FALLBACK_INDEX);
 640         }
 641     }
 642 
 643     private static class TextFlavorComparator extends DataFlavorComparator {
 644 
 645         static final TextFlavorComparator INSTANCE = new TextFlavorComparator();
 646         /**
 647          * Compares two <code>DataFlavor</code> objects. Returns a negative
 648          * integer, zero, or a positive integer as the first
 649          * <code>DataFlavor</code> is worse than, equal to, or better than the
 650          * second.
 651          * <p>
 652          * <code>DataFlavor</code>s are ordered according to the rules outlined
 653          * for <code>selectBestTextFlavor</code>.
 654          *
 655          * @param flavor1 the first <code>DataFlavor</code> to be compared
 656          * @param flavor2 the second <code>DataFlavor</code> to be compared
 657          * @return a negative integer, zero, or a positive integer as the first
 658          *         argument is worse, equal to, or better than the second
 659          * @throws ClassCastException if either of the arguments is not an
 660          *         instance of <code>DataFlavor</code>
 661          * @throws NullPointerException if either of the arguments is
 662          *         <code>null</code>
 663          *
 664          * @see java.awt.datatransfer.DataFlavor#selectBestTextFlavor
 665          */
 666         public int compare(DataFlavor flavor1, DataFlavor flavor2) {
 667             if (flavor1.isFlavorTextType()) {
 668                 if (flavor2.isFlavorTextType()) {
 669                     return super.compare(flavor1, flavor2);
 670                 } else {
 671                     return 1;
 672                 }
 673             } else if (flavor2.isFlavorTextType()) {
 674                 return -1;
 675             } else {
 676                 return 0;
 677             }
 678         }
 679     }
 680 
 681     /**
 682      * A fallback implementation of {@link sun.datatransfer.DesktopDatatransferService}
 683      * used if there is no desktop.
 684      */
 685     private static final class DefaultDesktopDatatransferService implements DesktopDatatransferService {
 686         static final DesktopDatatransferService INSTANCE = getDesktopService();
 687 
 688         private static DesktopDatatransferService getDesktopService() {
 689             ServiceLoader<DesktopDatatransferService> loader =
 690                     ServiceLoader.load(DesktopDatatransferService.class, null);
 691             Iterator<DesktopDatatransferService> iterator = loader.iterator();
 692             if (iterator.hasNext()) {
 693                 return iterator.next();
 694             } else {
 695                 return new DefaultDesktopDatatransferService();
 696             }
 697         }
 698 
 699         /**
 700          * System singleton FlavorTable.
 701          * Only used if there is no desktop
 702          * to provide an appropriate FlavorMap.
 703          */
 704         private volatile FlavorMap flavorMap;
 705 
 706         @Override
 707         public void invokeOnEventThread(Runnable r) {
 708             r.run();
 709         }
 710 
 711         @Override
 712         public String getDefaultUnicodeEncoding() {
 713             return StandardCharsets.UTF_8.name();
 714         }
 715 
 716         @Override
 717         public FlavorMap getFlavorMap(Supplier<FlavorMap> supplier) {
 718             FlavorMap map = flavorMap;
 719             if (map == null) {
 720                 synchronized (this) {
 721                     map = flavorMap;
 722                     if (map == null) {
 723                         flavorMap = map = supplier.get();
 724                     }
 725                 }
 726             }
 727             return map;
 728         }
 729 
 730         @Override
 731         public boolean isDesktopPresent() {
 732             return false;
 733         }
 734 
 735         @Override
 736         public LinkedHashSet<DataFlavor> getPlatformMappingsForNative(String nat) {
 737             return new LinkedHashSet<>();
 738         }
 739 
 740         @Override
 741         public LinkedHashSet<String> getPlatformMappingsForFlavor(DataFlavor df) {
 742             return new LinkedHashSet<>();
 743         }
 744 
 745         @Override
 746         public void registerTextFlavorProperties(String nat, String charset,
 747                                                  String eoln, String terminators) {
 748             // Not needed if desktop module is absent
 749         }
 750     }
 751 
 752     public static DesktopDatatransferService getDesktopService() {
 753         return DefaultDesktopDatatransferService.INSTANCE;
 754     }
 755 
 756     /**
 757      * A class that provides access to java.rmi.Remote and java.rmi.MarshalledObject
 758      * without creating a static dependency.
 759      */
 760     public static class RMI {
 761         private static final Class<?> remoteClass = getClass("java.rmi.Remote");
 762         private static final Class<?> marshallObjectClass = getClass("java.rmi.MarshalledObject");
 763         private static final Constructor<?> marshallCtor = getConstructor(marshallObjectClass, Object.class);
 764         private static final Method marshallGet = getMethod(marshallObjectClass, "get");
 765 
 766         private static Class<?> getClass(String name) {
 767             try {
 768                 return Class.forName(name, true, null);
 769             } catch (ClassNotFoundException e) {
 770                 return null;
 771             }
 772         }
 773 
 774         private static Constructor<?> getConstructor(Class<?> c, Class<?>... types) {
 775             try {
 776                 return (c == null) ? null : c.getDeclaredConstructor(types);
 777             } catch (NoSuchMethodException x) {
 778                 throw new AssertionError(x);
 779             }
 780         }
 781 
 782         private static Method getMethod(Class<?> c, String name, Class<?>... types) {
 783             try {
 784                 return (c == null) ? null : c.getMethod(name, types);
 785             } catch (NoSuchMethodException e) {
 786                 throw new AssertionError(e);
 787             }
 788         }
 789 
 790         /**
 791          * Returns java.rmi.Remote.class if RMI is present; otherwise {@code null}.
 792          */
 793         static Class<?> remoteClass() {
 794             return remoteClass;
 795         }
 796 
 797         /**
 798          * Returns {@code true} if the given class is java.rmi.Remote.
 799          */
 800         public static boolean isRemote(Class<?> c) {
 801             return (remoteClass != null) && remoteClass.isAssignableFrom(c);
 802         }
 803 
 804         /**
 805          * Returns a new MarshalledObject containing the serialized representation
 806          * of the given object.
 807          */
 808         public static Object newMarshalledObject(Object obj) throws IOException {
 809             try {
 810                 return marshallCtor == null ? null : marshallCtor.newInstance(obj);
 811             } catch (InstantiationException | IllegalAccessException x) {
 812                 throw new AssertionError(x);
 813             } catch (InvocationTargetException x) {
 814                 Throwable cause = x.getCause();
 815                 if (cause instanceof IOException)
 816                     throw (IOException) cause;
 817                 throw new AssertionError(x);
 818             }
 819         }
 820 
 821         /**
 822          * Returns a new copy of the contained marshalled object.
 823          */
 824         public static Object getMarshalledObject(Object obj)
 825                 throws IOException, ClassNotFoundException {
 826             try {
 827                 return marshallGet == null ? null : marshallGet.invoke(obj);
 828             } catch (IllegalAccessException x) {
 829                 throw new AssertionError(x);
 830             } catch (InvocationTargetException x) {
 831                 Throwable cause = x.getCause();
 832                 if (cause instanceof IOException)
 833                     throw (IOException) cause;
 834                 if (cause instanceof ClassNotFoundException)
 835                     throw (ClassNotFoundException) cause;
 836                 throw new AssertionError(x);
 837             }
 838         }
 839 
 840     }
 841 }