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