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