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> 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 an Iterator which traverses a SortedSet of Strings which are 131 * a total order of the standard character sets supported by the JRE. The 132 * ordering follows the same principles as DataFlavor.selectBestTextFlavor. 133 * So as to avoid loading all available character converters, optional, 134 * non-standard, 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 application types. 578 comp = compareIndices(primaryTypes, primaryType1, primaryType2, 579 UNKNOWN_OBJECT_LOSES); 580 if (comp != 0) { 581 return comp; 582 } 583 584 // Next, look for application/x-java-* types. Prefer unknown 585 // MIME types because if the user provides his own data flavor, 586 // it will likely be the most descriptive one. 587 comp = compareIndices(exactTypes, mimeType1, mimeType2, 588 UNKNOWN_OBJECT_WINS); 589 if (comp != 0) { 590 return comp; 591 } 592 593 // Finally, prefer the representation classes of Remote, 594 // Serializable, and InputStream, in that order. 595 comp = compareIndices(nonTextRepresentations, class1, class2, 596 UNKNOWN_OBJECT_LOSES); 597 if (comp != 0) { 598 return comp; 599 } 600 } 601 602 // The flavours are not equal but still not distinguishable. 603 // Compare String representations in alphabetical order 604 return flavor1.getMimeType().compareTo(flavor2.getMimeType()); 605 } 606 } 607 608 /* 609 * Given the Map that maps objects to Integer indices and a boolean value, 610 * this Comparator imposes a direct or reverse order on set of objects. 611 * <p> 612 * If the specified boolean value is SELECT_BEST, the Comparator imposes the 613 * direct index-based order: an object A is greater than an object B if and 614 * only if the index of A is greater than the index of B. An object that 615 * doesn't have an associated index is less or equal than any other object. 616 * <p> 617 * If the specified boolean value is SELECT_WORST, the Comparator imposes the 618 * reverse index-based order: an object A is greater than an object B if and 619 * only if A is less than B with the direct index-based order. 620 */ 621 private static class IndexOrderComparator implements Comparator<Long> { 622 private final Map<Long, Integer> indexMap; 623 private static final Integer FALLBACK_INDEX = Integer.MIN_VALUE; 624 625 public IndexOrderComparator(Map<Long, Integer> indexMap) { 626 this.indexMap = indexMap; 627 } 628 629 public int compare(Long obj1, Long obj2) { 630 return compareIndices(indexMap, obj1, obj2, FALLBACK_INDEX); 631 } 632 } 633 634 private static class TextFlavorComparator extends DataFlavorComparator { 635 636 static final TextFlavorComparator INSTANCE = new TextFlavorComparator(); 637 /** 638 * Compares two <code>DataFlavor</code> objects. Returns a negative 639 * integer, zero, or a positive integer as the first 640 * <code>DataFlavor</code> is worse than, equal to, or better than the 641 * second. 642 * <p> 643 * <code>DataFlavor</code>s are ordered according to the rules outlined 644 * for <code>selectBestTextFlavor</code>. 645 * 646 * @param flavor1 the first <code>DataFlavor</code> to be compared 647 * @param flavor2 the second <code>DataFlavor</code> to be compared 648 * @return a negative integer, zero, or a positive integer as the first 649 * argument is worse, equal to, or better than the second 650 * @throws ClassCastException if either of the arguments is not an 651 * instance of <code>DataFlavor</code> 652 * @throws NullPointerException if either of the arguments is 653 * <code>null</code> 654 * 655 * @see java.awt.datatransfer.DataFlavor#selectBestTextFlavor 656 */ 657 public int compare(DataFlavor flavor1, DataFlavor flavor2) { 658 if (flavor1.isFlavorTextType()) { 659 if (flavor2.isFlavorTextType()) { 660 return super.compare(flavor1, flavor2); 661 } else { 662 return 1; 663 } 664 } else if (flavor2.isFlavorTextType()) { 665 return -1; 666 } else { 667 return 0; 668 } 669 } 670 } 671 672 /** 673 * A fallback implementation of {@link sun.datatransfer.DesktopDatatransferService} 674 * used if there is no desktop. 675 */ 676 private static final class DefaultDesktopDatatransferService implements DesktopDatatransferService { 677 static final DesktopDatatransferService INSTANCE = getDesktopService(); 678 679 private static DesktopDatatransferService getDesktopService() { 680 ServiceLoader<DesktopDatatransferService> loader = 681 ServiceLoader.load(DesktopDatatransferService.class, null); 682 Iterator<DesktopDatatransferService> iterator = loader.iterator(); 683 if (iterator.hasNext()) { 684 return iterator.next(); 685 } else { 686 return new DefaultDesktopDatatransferService(); 687 } 688 } 689 690 /** 691 * System singleton FlavorTable. 692 * Only used if there is no desktop 693 * to provide an appropriate FlavorMap. 694 */ 695 private volatile FlavorMap flavorMap; 696 697 @Override 698 public void invokeOnEventThread(Runnable r) { 699 r.run(); 700 } 701 702 @Override 703 public String getDefaultUnicodeEncoding() { 704 return StandardCharsets.UTF_8.name(); 705 } 706 707 @Override 708 public FlavorMap getFlavorMap(Supplier<FlavorMap> supplier) { 709 FlavorMap map = flavorMap; 710 if (map == null) { 711 synchronized (this) { 712 map = flavorMap; 713 if (map == null) { 714 flavorMap = map = supplier.get(); 715 } 716 } 717 } 718 return map; 719 } 720 721 @Override 722 public boolean isDesktopPresent() { 723 return false; 724 } 725 726 @Override 727 public LinkedHashSet<DataFlavor> getPlatformMappingsForNative(String nat) { 728 return new LinkedHashSet<>(); 729 } 730 731 @Override 732 public LinkedHashSet<String> getPlatformMappingsForFlavor(DataFlavor df) { 733 return new LinkedHashSet<>(); 734 } 735 736 @Override 737 public void registerTextFlavorProperties(String nat, String charset, 738 String eoln, String terminators) { 739 // Not needed if desktop module is absent 740 } 741 } 742 743 public static DesktopDatatransferService getDesktopService() { 744 return DefaultDesktopDatatransferService.INSTANCE; 745 } 746 747 /** 748 * A class that provides access to java.rmi.Remote and java.rmi.MarshalledObject 749 * without creating a static dependency. 750 */ 751 public static class RMI { 752 private static final Class<?> remoteClass = getClass("java.rmi.Remote"); 753 private static final Class<?> marshallObjectClass = getClass("java.rmi.MarshalledObject"); 754 private static final Constructor<?> marshallCtor = getConstructor(marshallObjectClass, Object.class); 755 private static final Method marshallGet = getMethod(marshallObjectClass, "get"); 756 757 private static Class<?> getClass(String name) { 758 try { 759 return Class.forName(name, true, null); 760 } catch (ClassNotFoundException e) { 761 return null; 762 } 763 } 764 765 private static Constructor<?> getConstructor(Class<?> c, Class<?>... types) { 766 try { 767 return (c == null) ? null : c.getDeclaredConstructor(types); 768 } catch (NoSuchMethodException x) { 769 throw new AssertionError(x); 770 } 771 } 772 773 private static Method getMethod(Class<?> c, String name, Class<?>... types) { 774 try { 775 return (c == null) ? null : c.getMethod(name, types); 776 } catch (NoSuchMethodException e) { 777 throw new AssertionError(e); 778 } 779 } 780 781 /** 782 * Returns java.rmi.Remote.class if RMI is present; otherwise {@code null}. 783 */ 784 static Class<?> remoteClass() { 785 return remoteClass; 786 } 787 788 /** 789 * Returns {@code true} if the given class is java.rmi.Remote. 790 */ 791 public static boolean isRemote(Class<?> c) { 792 return (remoteClass != null) && remoteClass.isAssignableFrom(c); 793 } 794 795 /** 796 * Returns a new MarshalledObject containing the serialized representation 797 * of the given object. 798 */ 799 public static Object newMarshalledObject(Object obj) throws IOException { 800 try { 801 return marshallCtor == null ? null : marshallCtor.newInstance(obj); 802 } catch (InstantiationException | IllegalAccessException x) { 803 throw new AssertionError(x); 804 } catch (InvocationTargetException x) { 805 Throwable cause = x.getCause(); 806 if (cause instanceof IOException) 807 throw (IOException) cause; 808 throw new AssertionError(x); 809 } 810 } 811 812 /** 813 * Returns a new copy of the contained marshalled object. 814 */ 815 public static Object getMarshalledObject(Object obj) 816 throws IOException, ClassNotFoundException { 817 try { 818 return marshallGet == null ? null : marshallGet.invoke(obj); 819 } catch (IllegalAccessException x) { 820 throw new AssertionError(x); 821 } catch (InvocationTargetException x) { 822 Throwable cause = x.getCause(); 823 if (cause instanceof IOException) 824 throw (IOException) cause; 825 if (cause instanceof ClassNotFoundException) 826 throw (ClassNotFoundException) cause; 827 throw new AssertionError(x); 828 } 829 } 830 831 } 832 }