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