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