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