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 }