1 /* 2 * Copyright (c) 1997, 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 java.awt.datatransfer; 27 28 import java.awt.Toolkit; 29 30 import java.lang.ref.SoftReference; 31 32 import java.io.BufferedReader; 33 import java.io.File; 34 import java.io.InputStreamReader; 35 import java.io.IOException; 36 37 import java.net.URL; 38 import java.net.MalformedURLException; 39 40 import java.util.ArrayList; 41 import java.util.HashMap; 42 import java.util.HashSet; 43 import java.util.LinkedHashSet; 44 import java.util.List; 45 import java.util.Map; 46 import java.util.Set; 47 import java.util.WeakHashMap; 48 49 import sun.awt.datatransfer.DataTransferer; 50 51 /** 52 * The SystemFlavorMap is a configurable map between "natives" (Strings), which 53 * correspond to platform-specific data formats, and "flavors" (DataFlavors), 54 * which correspond to platform-independent MIME types. This mapping is used 55 * by the data transfer subsystem to transfer data between Java and native 56 * applications, and between Java applications in separate VMs. 57 * <p> 58 * 59 * @since 1.2 60 */ 61 public final class SystemFlavorMap implements FlavorMap, FlavorTable { 62 63 /** 64 * Constant prefix used to tag Java types converted to native platform 65 * type. 66 */ 67 private static String JavaMIME = "JAVA_DATAFLAVOR:"; 68 69 /** 70 * System singleton which maps a thread's ClassLoader to a SystemFlavorMap. 71 */ 72 private static final WeakHashMap<ClassLoader, FlavorMap> flavorMaps = new WeakHashMap<>(); 73 74 /** 75 * Copied from java.util.Properties. 76 */ 77 private static final String keyValueSeparators = "=: \t\r\n\f"; 78 private static final String strictKeyValueSeparators = "=:"; 79 private static final String whiteSpaceChars = " \t\r\n\f"; 80 81 /** 82 * The list of valid, decoded text flavor representation classes, in order 83 * from best to worst. 84 */ 85 private static final String[] UNICODE_TEXT_CLASSES = { 86 "java.io.Reader", "java.lang.String", "java.nio.CharBuffer", "\"[C\"" 87 }; 88 89 /** 90 * The list of valid, encoded text flavor representation classes, in order 91 * from best to worst. 92 */ 93 private static final String[] ENCODED_TEXT_CLASSES = { 94 "java.io.InputStream", "java.nio.ByteBuffer", "\"[B\"" 95 }; 96 97 /** 98 * A String representing text/plain MIME type. 99 */ 100 private static final String TEXT_PLAIN_BASE_TYPE = "text/plain"; 101 102 /** 103 * A String representing text/html MIME type. 104 */ 105 private static final String HTML_TEXT_BASE_TYPE = "text/html"; 106 107 /** 108 * This constant is passed to flavorToNativeLookup() to indicate that a 109 * a native should be synthesized, stored, and returned by encoding the 110 * DataFlavor's MIME type in case if the DataFlavor is not found in 111 * 'flavorToNative' map. 112 */ 113 private static final boolean SYNTHESIZE_IF_NOT_FOUND = true; 114 115 /** 116 * Maps native Strings to Lists of DataFlavors (or base type Strings for 117 * text DataFlavors). 118 * Do not use the field directly, use getNativeToFlavor() instead. 119 */ 120 private final Map<String, List<DataFlavor>> nativeToFlavor = new HashMap<>(); 121 122 /** 123 * Accessor to nativeToFlavor map. Since we use lazy initialization we must 124 * use this accessor instead of direct access to the field which may not be 125 * initialized yet. This method will initialize the field if needed. 126 * 127 * @return nativeToFlavor 128 */ 129 private Map<String, List<DataFlavor>> getNativeToFlavor() { 130 if (!isMapInitialized) { 131 initSystemFlavorMap(); 132 } 133 return nativeToFlavor; 134 } 135 136 /** 137 * Maps DataFlavors (or base type Strings for text DataFlavors) to Lists of 138 * native Strings. 139 * Do not use the field directly, use getFlavorToNative() instead. 140 */ 141 private final Map<DataFlavor, List<String>> flavorToNative = new HashMap<>(); 142 143 /** 144 * Accessor to flavorToNative map. Since we use lazy initialization we must 145 * use this accessor instead of direct access to the field which may not be 146 * initialized yet. This method will initialize the field if needed. 147 * 148 * @return flavorToNative 149 */ 150 private synchronized Map<DataFlavor, List<String>> getFlavorToNative() { 151 if (!isMapInitialized) { 152 initSystemFlavorMap(); 153 } 154 return flavorToNative; 155 } 156 157 /** 158 * Shows if the object has been initialized. 159 */ 160 private boolean isMapInitialized = false; 161 162 /** 163 * Caches the result of getNativesForFlavor(). Maps DataFlavors to 164 * SoftReferences which reference Lists of String natives. 165 */ 166 private Map<DataFlavor, SoftReference<List<String>>> getNativesForFlavorCache = new HashMap<>(); 167 168 /** 169 * Caches the result getFlavorsForNative(). Maps String natives to 170 * SoftReferences which reference Lists of DataFlavors. 171 */ 172 private Map<String, SoftReference<List<DataFlavor>>> getFlavorsForNativeCache = new HashMap<>(); 173 174 /** 175 * Dynamic mapping generation used for text mappings should not be applied 176 * to the DataFlavors and String natives for which the mappings have been 177 * explicitly specified with setFlavorsForNative() or 178 * setNativesForFlavor(). This keeps all such keys. 179 */ 180 private Set disabledMappingGenerationKeys = new HashSet(); 181 182 /** 183 * Returns the default FlavorMap for this thread's ClassLoader. 184 * @return the default FlavorMap for this thread's ClassLoader 185 */ 186 public static FlavorMap getDefaultFlavorMap() { 187 ClassLoader contextClassLoader = 188 Thread.currentThread().getContextClassLoader(); 189 if (contextClassLoader == null) { 190 contextClassLoader = ClassLoader.getSystemClassLoader(); 191 } 192 193 FlavorMap fm; 194 195 synchronized(flavorMaps) { 196 fm = flavorMaps.get(contextClassLoader); 197 if (fm == null) { 198 fm = new SystemFlavorMap(); 199 flavorMaps.put(contextClassLoader, fm); 200 } 201 } 202 203 return fm; 204 } 205 206 private SystemFlavorMap() { 207 } 208 209 /** 210 * Initializes a SystemFlavorMap by reading flavormap.properties and 211 * AWT.DnD.flavorMapFileURL. 212 * For thread-safety must be called under lock on this. 213 */ 214 private void initSystemFlavorMap() { 215 if (isMapInitialized) { 216 return; 217 } 218 219 isMapInitialized = true; 220 BufferedReader flavormapDotProperties = 221 java.security.AccessController.doPrivileged( 222 new java.security.PrivilegedAction<BufferedReader>() { 223 public BufferedReader run() { 224 String fileName = 225 System.getProperty("java.home") + 226 File.separator + 227 "lib" + 228 File.separator + 229 "flavormap.properties"; 230 try { 231 return new BufferedReader 232 (new InputStreamReader 233 (new File(fileName).toURI().toURL().openStream(), "ISO-8859-1")); 234 } catch (MalformedURLException e) { 235 System.err.println("MalformedURLException:" + e + " while loading default flavormap.properties file:" + fileName); 236 } catch (IOException e) { 237 System.err.println("IOException:" + e + " while loading default flavormap.properties file:" + fileName); 238 } 239 return null; 240 } 241 }); 242 243 BufferedReader flavormapURL = 244 java.security.AccessController.doPrivileged( 245 new java.security.PrivilegedAction<BufferedReader>() { 246 public BufferedReader run() { 247 String url = Toolkit.getProperty("AWT.DnD.flavorMapFileURL", null); 248 249 if (url == null) { 250 return null; 251 } 252 253 try { 254 return new BufferedReader 255 (new InputStreamReader 256 (new URL(url).openStream(), "ISO-8859-1")); 257 } catch (MalformedURLException e) { 258 System.err.println("MalformedURLException:" + e + " while reading AWT.DnD.flavorMapFileURL:" + url); 259 } catch (IOException e) { 260 System.err.println("IOException:" + e + " while reading AWT.DnD.flavorMapFileURL:" + url); 261 } 262 return null; 263 } 264 }); 265 266 if (flavormapDotProperties != null) { 267 try { 268 parseAndStoreReader(flavormapDotProperties); 269 } catch (IOException e) { 270 System.err.println("IOException:" + e + " while parsing default flavormap.properties file"); 271 } 272 } 273 274 if (flavormapURL != null) { 275 try { 276 parseAndStoreReader(flavormapURL); 277 } catch (IOException e) { 278 System.err.println("IOException:" + e + " while parsing AWT.DnD.flavorMapFileURL"); 279 } 280 } 281 } 282 /** 283 * Copied code from java.util.Properties. Parsing the data ourselves is the 284 * only way to handle duplicate keys and values. 285 */ 286 private void parseAndStoreReader(BufferedReader in) throws IOException { 287 while (true) { 288 // Get next line 289 String line = in.readLine(); 290 if (line == null) { 291 return; 292 } 293 294 if (line.length() > 0) { 295 // Continue lines that end in slashes if they are not comments 296 char firstChar = line.charAt(0); 297 if (firstChar != '#' && firstChar != '!') { 298 while (continueLine(line)) { 299 String nextLine = in.readLine(); 300 if (nextLine == null) { 301 nextLine = ""; 302 } 303 String loppedLine = 304 line.substring(0, line.length() - 1); 305 // Advance beyond whitespace on new line 306 int startIndex = 0; 307 for(; startIndex < nextLine.length(); startIndex++) { 308 if (whiteSpaceChars. 309 indexOf(nextLine.charAt(startIndex)) == -1) 310 { 311 break; 312 } 313 } 314 nextLine = nextLine.substring(startIndex, 315 nextLine.length()); 316 line = loppedLine+nextLine; 317 } 318 319 // Find start of key 320 int len = line.length(); 321 int keyStart = 0; 322 for(; keyStart < len; keyStart++) { 323 if(whiteSpaceChars. 324 indexOf(line.charAt(keyStart)) == -1) { 325 break; 326 } 327 } 328 329 // Blank lines are ignored 330 if (keyStart == len) { 331 continue; 332 } 333 334 // Find separation between key and value 335 int separatorIndex = keyStart; 336 for(; separatorIndex < len; separatorIndex++) { 337 char currentChar = line.charAt(separatorIndex); 338 if (currentChar == '\\') { 339 separatorIndex++; 340 } else if (keyValueSeparators. 341 indexOf(currentChar) != -1) { 342 break; 343 } 344 } 345 346 // Skip over whitespace after key if any 347 int valueIndex = separatorIndex; 348 for (; valueIndex < len; valueIndex++) { 349 if (whiteSpaceChars. 350 indexOf(line.charAt(valueIndex)) == -1) { 351 break; 352 } 353 } 354 355 // Skip over one non whitespace key value separators if any 356 if (valueIndex < len) { 357 if (strictKeyValueSeparators. 358 indexOf(line.charAt(valueIndex)) != -1) { 359 valueIndex++; 360 } 361 } 362 363 // Skip over white space after other separators if any 364 while (valueIndex < len) { 365 if (whiteSpaceChars. 366 indexOf(line.charAt(valueIndex)) == -1) { 367 break; 368 } 369 valueIndex++; 370 } 371 372 String key = line.substring(keyStart, separatorIndex); 373 String value = (separatorIndex < len) 374 ? line.substring(valueIndex, len) 375 : ""; 376 377 // Convert then store key and value 378 key = loadConvert(key); 379 value = loadConvert(value); 380 381 try { 382 MimeType mime = new MimeType(value); 383 if ("text".equals(mime.getPrimaryType())) { 384 String charset = mime.getParameter("charset"); 385 if (DataTransferer.doesSubtypeSupportCharset 386 (mime.getSubType(), charset)) 387 { 388 // We need to store the charset and eoln 389 // parameters, if any, so that the 390 // DataTransferer will have this information 391 // for conversion into the native format. 392 DataTransferer transferer = 393 DataTransferer.getInstance(); 394 if (transferer != null) { 395 transferer.registerTextFlavorProperties 396 (key, charset, 397 mime.getParameter("eoln"), 398 mime.getParameter("terminators")); 399 } 400 } 401 402 // But don't store any of these parameters in the 403 // DataFlavor itself for any text natives (even 404 // non-charset ones). The SystemFlavorMap will 405 // synthesize the appropriate mappings later. 406 mime.removeParameter("charset"); 407 mime.removeParameter("class"); 408 mime.removeParameter("eoln"); 409 mime.removeParameter("terminators"); 410 value = mime.toString(); 411 } 412 } catch (MimeTypeParseException e) { 413 e.printStackTrace(); 414 continue; 415 } 416 417 DataFlavor flavor; 418 try { 419 flavor = new DataFlavor(value); 420 } catch (Exception e) { 421 try { 422 flavor = new DataFlavor(value, (String)null); 423 } catch (Exception ee) { 424 ee.printStackTrace(); 425 continue; 426 } 427 } 428 429 final LinkedHashSet<DataFlavor> dfs = new LinkedHashSet<>(); 430 431 dfs.add(flavor); 432 433 if ("text".equals(flavor.getPrimaryType())) { 434 dfs.addAll(convertMimeTypeToDataFlavors(value)); 435 } 436 437 for (DataFlavor df : dfs) { 438 store(df, key, getFlavorToNative()); 439 store(key, df, getNativeToFlavor()); 440 } 441 } 442 } 443 } 444 } 445 446 /** 447 * Copied from java.util.Properties. 448 */ 449 private boolean continueLine (String line) { 450 int slashCount = 0; 451 int index = line.length() - 1; 452 while((index >= 0) && (line.charAt(index--) == '\\')) { 453 slashCount++; 454 } 455 return (slashCount % 2 == 1); 456 } 457 458 /** 459 * Copied from java.util.Properties. 460 */ 461 private String loadConvert(String theString) { 462 char aChar; 463 int len = theString.length(); 464 StringBuilder outBuffer = new StringBuilder(len); 465 466 for (int x = 0; x < len; ) { 467 aChar = theString.charAt(x++); 468 if (aChar == '\\') { 469 aChar = theString.charAt(x++); 470 if (aChar == 'u') { 471 // Read the xxxx 472 int value = 0; 473 for (int i = 0; i < 4; i++) { 474 aChar = theString.charAt(x++); 475 switch (aChar) { 476 case '0': case '1': case '2': case '3': case '4': 477 case '5': case '6': case '7': case '8': case '9': { 478 value = (value << 4) + aChar - '0'; 479 break; 480 } 481 case 'a': case 'b': case 'c': 482 case 'd': case 'e': case 'f': { 483 value = (value << 4) + 10 + aChar - 'a'; 484 break; 485 } 486 case 'A': case 'B': case 'C': 487 case 'D': case 'E': case 'F': { 488 value = (value << 4) + 10 + aChar - 'A'; 489 break; 490 } 491 default: { 492 throw new IllegalArgumentException( 493 "Malformed \\uxxxx encoding."); 494 } 495 } 496 } 497 outBuffer.append((char)value); 498 } else { 499 if (aChar == 't') { 500 aChar = '\t'; 501 } else if (aChar == 'r') { 502 aChar = '\r'; 503 } else if (aChar == 'n') { 504 aChar = '\n'; 505 } else if (aChar == 'f') { 506 aChar = '\f'; 507 } 508 outBuffer.append(aChar); 509 } 510 } else { 511 outBuffer.append(aChar); 512 } 513 } 514 return outBuffer.toString(); 515 } 516 517 /** 518 * Stores the listed object under the specified hash key in map. Unlike a 519 * standard map, the listed object will not replace any object already at 520 * the appropriate Map location, but rather will be appended to a List 521 * stored in that location. 522 */ 523 private <H, L> void store(H hashed, L listed, Map<H, List<L>> map) { 524 List<L> list = map.get(hashed); 525 if (list == null) { 526 list = new ArrayList<>(1); 527 map.put(hashed, list); 528 } 529 if (!list.contains(listed)) { 530 list.add(listed); 531 } 532 } 533 534 /** 535 * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method 536 * handles the case where 'nat' is not found in 'nativeToFlavor'. In that 537 * case, a new DataFlavor is synthesized, stored, and returned, if and 538 * only if the specified native is encoded as a Java MIME type. 539 */ 540 private List<DataFlavor> nativeToFlavorLookup(String nat) { 541 List<DataFlavor> flavors = getNativeToFlavor().get(nat); 542 543 if (nat != null && !disabledMappingGenerationKeys.contains(nat)) { 544 DataTransferer transferer = DataTransferer.getInstance(); 545 if (transferer != null) { 546 List<DataFlavor> platformFlavors = 547 transferer.getPlatformMappingsForNative(nat); 548 if (!platformFlavors.isEmpty()) { 549 if (flavors != null) { 550 platformFlavors.removeAll(new HashSet<>(flavors)); 551 // Prepending the platform-specific mappings ensures 552 // that the flavors added with 553 // addFlavorForUnencodedNative() are at the end of 554 // list. 555 platformFlavors.addAll(flavors); 556 } 557 flavors = platformFlavors; 558 } 559 } 560 } 561 562 if (flavors == null && isJavaMIMEType(nat)) { 563 String decoded = decodeJavaMIMEType(nat); 564 DataFlavor flavor = null; 565 566 try { 567 flavor = new DataFlavor(decoded); 568 } catch (Exception e) { 569 System.err.println("Exception \"" + e.getClass().getName() + 570 ": " + e.getMessage() + 571 "\"while constructing DataFlavor for: " + 572 decoded); 573 } 574 575 if (flavor != null) { 576 flavors = new ArrayList<>(1); 577 getNativeToFlavor().put(nat, flavors); 578 flavors.add(flavor); 579 getFlavorsForNativeCache.remove(nat); 580 getFlavorsForNativeCache.remove(null); 581 582 List<String> natives = getFlavorToNative().get(flavor); 583 if (natives == null) { 584 natives = new ArrayList<>(1); 585 getFlavorToNative().put(flavor, natives); 586 } 587 natives.add(nat); 588 getNativesForFlavorCache.remove(flavor); 589 getNativesForFlavorCache.remove(null); 590 } 591 } 592 593 return (flavors != null) ? flavors : new ArrayList<>(0); 594 } 595 596 /** 597 * Semantically equivalent to 'flavorToNative.get(flav)'. This method 598 * handles the case where 'flav' is not found in 'flavorToNative' depending 599 * on the value of passes 'synthesize' parameter. If 'synthesize' is 600 * SYNTHESIZE_IF_NOT_FOUND a native is synthesized, stored, and returned by 601 * encoding the DataFlavor's MIME type. Otherwise an empty List is returned 602 * and 'flavorToNative' remains unaffected. 603 */ 604 private List<String> flavorToNativeLookup(final DataFlavor flav, 605 final boolean synthesize) { 606 List<String> natives = getFlavorToNative().get(flav); 607 608 if (flav != null && !disabledMappingGenerationKeys.contains(flav)) { 609 DataTransferer transferer = DataTransferer.getInstance(); 610 if (transferer != null) { 611 List<String> platformNatives = 612 transferer.getPlatformMappingsForFlavor(flav); 613 if (!platformNatives.isEmpty()) { 614 if (natives != null) { 615 platformNatives.removeAll(new HashSet<>(natives)); 616 // Prepend the platform-specific mappings to ensure 617 // that the natives added with 618 // addUnencodedNativeForFlavor() are at the end of 619 // list. 620 platformNatives.addAll(natives); 621 } 622 natives = platformNatives; 623 } 624 } 625 } 626 627 if (natives == null) { 628 if (synthesize) { 629 String encoded = encodeDataFlavor(flav); 630 natives = new ArrayList<>(1); 631 getFlavorToNative().put(flav, natives); 632 natives.add(encoded); 633 getNativesForFlavorCache.remove(flav); 634 getNativesForFlavorCache.remove(null); 635 636 List<DataFlavor> flavors = getNativeToFlavor().get(encoded); 637 if (flavors == null) { 638 flavors = new ArrayList<>(1); 639 getNativeToFlavor().put(encoded, flavors); 640 } 641 flavors.add(flav); 642 getFlavorsForNativeCache.remove(encoded); 643 getFlavorsForNativeCache.remove(null); 644 } else { 645 natives = new ArrayList<>(0); 646 } 647 } 648 649 return natives; 650 } 651 652 /** 653 * Returns a <code>List</code> of <code>String</code> natives to which the 654 * specified <code>DataFlavor</code> can be translated by the data transfer 655 * subsystem. The <code>List</code> will be sorted from best native to 656 * worst. That is, the first native will best reflect data in the specified 657 * flavor to the underlying native platform. 658 * <p> 659 * If the specified <code>DataFlavor</code> is previously unknown to the 660 * data transfer subsystem and the data transfer subsystem is unable to 661 * translate this <code>DataFlavor</code> to any existing native, then 662 * invoking this method will establish a 663 * mapping in both directions between the specified <code>DataFlavor</code> 664 * and an encoded version of its MIME type as its native. 665 * 666 * @param flav the <code>DataFlavor</code> whose corresponding natives 667 * should be returned. If <code>null</code> is specified, all 668 * natives currently known to the data transfer subsystem are 669 * returned in a non-deterministic order. 670 * @return a <code>java.util.List</code> of <code>java.lang.String</code> 671 * objects which are platform-specific representations of platform- 672 * specific data formats 673 * 674 * @see #encodeDataFlavor 675 * @since 1.4 676 */ 677 public synchronized List<String> getNativesForFlavor(DataFlavor flav) { 678 List<String> retval = null; 679 680 // Check cache, even for null flav 681 SoftReference<List<String>> ref = getNativesForFlavorCache.get(flav); 682 if (ref != null) { 683 retval = ref.get(); 684 if (retval != null) { 685 // Create a copy, because client code can modify the returned 686 // list. 687 return new ArrayList<>(retval); 688 } 689 } 690 691 if (flav == null) { 692 retval = new ArrayList<>(getNativeToFlavor().keySet()); 693 } else if (disabledMappingGenerationKeys.contains(flav)) { 694 // In this case we shouldn't synthesize a native for this flavor, 695 // since its mappings were explicitly specified. 696 retval = flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND); 697 } else if (DataTransferer.isFlavorCharsetTextType(flav)) { 698 699 // For text/* flavors, flavor-to-native mappings specified in 700 // flavormap.properties are stored per flavor's base type. 701 if ("text".equals(flav.getPrimaryType())) { 702 retval = getAllNativesForType(flav.mimeType.getBaseType()); 703 if (retval != null) { 704 // To prevent the List stored in the map from modification. 705 retval = new ArrayList(retval); 706 } 707 } 708 709 // Also include text/plain natives, but don't duplicate Strings 710 List<String> textPlainList = getAllNativesForType(TEXT_PLAIN_BASE_TYPE); 711 712 if (textPlainList != null && !textPlainList.isEmpty()) { 713 // To prevent the List stored in the map from modification. 714 // This also guarantees that removeAll() is supported. 715 textPlainList = new ArrayList<>(textPlainList); 716 if (retval != null && !retval.isEmpty()) { 717 // Use HashSet to get constant-time performance for search. 718 textPlainList.removeAll(new HashSet<>(retval)); 719 retval.addAll(textPlainList); 720 } else { 721 retval = textPlainList; 722 } 723 } 724 725 if (retval == null || retval.isEmpty()) { 726 retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND); 727 } else { 728 // In this branch it is guaranteed that natives explicitly 729 // listed for flav's MIME type were added with 730 // addUnencodedNativeForFlavor(), so they have lower priority. 731 List<String> explicitList = 732 flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND); 733 734 // flavorToNativeLookup() never returns null. 735 // It can return an empty List, however. 736 if (!explicitList.isEmpty()) { 737 // To prevent the List stored in the map from modification. 738 // This also guarantees that removeAll() is supported. 739 explicitList = new ArrayList<>(explicitList); 740 // Use HashSet to get constant-time performance for search. 741 explicitList.removeAll(new HashSet<>(retval)); 742 retval.addAll(explicitList); 743 } 744 } 745 } else if (DataTransferer.isFlavorNoncharsetTextType(flav)) { 746 retval = getAllNativesForType(flav.mimeType.getBaseType()); 747 748 if (retval == null || retval.isEmpty()) { 749 retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND); 750 } else { 751 // In this branch it is guaranteed that natives explicitly 752 // listed for flav's MIME type were added with 753 // addUnencodedNativeForFlavor(), so they have lower priority. 754 List<String> explicitList = 755 flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND); 756 757 // flavorToNativeLookup() never returns null. 758 // It can return an empty List, however. 759 if (!explicitList.isEmpty()) { 760 // To prevent the List stored in the map from modification. 761 // This also guarantees that add/removeAll() are supported. 762 retval = new ArrayList<>(retval); 763 explicitList = new ArrayList<>(explicitList); 764 // Use HashSet to get constant-time performance for search. 765 explicitList.removeAll(new HashSet<>(retval)); 766 retval.addAll(explicitList); 767 } 768 } 769 } else { 770 retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND); 771 } 772 773 getNativesForFlavorCache.put(flav, new SoftReference<>(retval)); 774 // Create a copy, because client code can modify the returned list. 775 return new ArrayList<>(retval); 776 } 777 778 /** 779 * Returns a <code>List</code> of <code>DataFlavor</code>s to which the 780 * specified <code>String</code> native can be translated by the data 781 * transfer subsystem. The <code>List</code> will be sorted from best 782 * <code>DataFlavor</code> to worst. That is, the first 783 * <code>DataFlavor</code> will best reflect data in the specified 784 * native to a Java application. 785 * <p> 786 * If the specified native is previously unknown to the data transfer 787 * subsystem, and that native has been properly encoded, then invoking this 788 * method will establish a mapping in both directions between the specified 789 * native and a <code>DataFlavor</code> whose MIME type is a decoded 790 * version of the native. 791 * <p> 792 * If the specified native is not a properly encoded native and the 793 * mappings for this native have not been altered with 794 * <code>setFlavorsForNative</code>, then the contents of the 795 * <code>List</code> is platform dependent, but <code>null</code> 796 * cannot be returned. 797 * 798 * @param nat the native whose corresponding <code>DataFlavor</code>s 799 * should be returned. If <code>null</code> is specified, all 800 * <code>DataFlavor</code>s currently known to the data transfer 801 * subsystem are returned in a non-deterministic order. 802 * @return a <code>java.util.List</code> of <code>DataFlavor</code> 803 * objects into which platform-specific data in the specified, 804 * platform-specific native can be translated 805 * 806 * @see #encodeJavaMIMEType 807 * @since 1.4 808 */ 809 public synchronized List<DataFlavor> getFlavorsForNative(String nat) { 810 811 // Check cache, even for null nat 812 SoftReference<List<DataFlavor>> ref = getFlavorsForNativeCache.get(nat); 813 if (ref != null) { 814 List<DataFlavor> retval = ref.get(); 815 if (retval != null) { 816 return new ArrayList<>(retval); 817 } 818 } 819 820 final LinkedHashSet <DataFlavor> returnValue = 821 new LinkedHashSet<>(); 822 823 if (nat == null) { 824 final List<String> natives = getNativesForFlavor(null); 825 826 for (String n : natives) 827 { 828 final List<DataFlavor> flavors = getFlavorsForNative(n); 829 830 for (DataFlavor df : flavors) 831 { 832 returnValue.add(df); 833 } 834 } 835 } else { 836 837 final List<DataFlavor> flavors = nativeToFlavorLookup(nat); 838 839 if (disabledMappingGenerationKeys.contains(nat)) { 840 return flavors; 841 } 842 843 final List<DataFlavor> flavorsAndBaseTypes = 844 nativeToFlavorLookup(nat); 845 846 for (DataFlavor df : flavorsAndBaseTypes) { 847 returnValue.add(df); 848 if ("text".equals(df.getPrimaryType())) { 849 try { 850 returnValue.addAll( 851 convertMimeTypeToDataFlavors( 852 new MimeType(df.getMimeType() 853 ).getBaseType())); 854 } catch (MimeTypeParseException e) { 855 e.printStackTrace(); 856 } 857 } 858 } 859 860 } 861 862 final List<DataFlavor> arrayList = new ArrayList<>(returnValue); 863 getFlavorsForNativeCache.put(nat, new SoftReference<>(arrayList)); 864 return new ArrayList<>(arrayList); 865 } 866 867 private static Set<DataFlavor> convertMimeTypeToDataFlavors( 868 final String baseType) { 869 870 final Set<DataFlavor> returnValue = new LinkedHashSet<>(); 871 872 String subType = null; 873 874 try { 875 final MimeType mimeType = new MimeType(baseType); 876 subType = mimeType.getSubType(); 877 } catch (MimeTypeParseException mtpe) { 878 // Cannot happen, since we checked all mappings 879 // on load from flavormap.properties. 880 assert(false); 881 } 882 883 if (DataTransferer.doesSubtypeSupportCharset(subType, null)) { 884 if (TEXT_PLAIN_BASE_TYPE.equals(baseType)) 885 { 886 returnValue.add(DataFlavor.stringFlavor); 887 } 888 889 for (String unicodeClassName : UNICODE_TEXT_CLASSES) { 890 final String mimeType = baseType + ";charset=Unicode;class=" + 891 unicodeClassName; 892 893 final LinkedHashSet<String> mimeTypes = 894 handleHtmlMimeTypes(baseType, mimeType); 895 for (String mt : mimeTypes) { 896 DataFlavor toAdd = null; 897 try { 898 toAdd = new DataFlavor(mt); 899 } catch (ClassNotFoundException cannotHappen) { 900 } 901 returnValue.add(toAdd); 902 } 903 } 904 905 for (String charset : DataTransferer.standardEncodings()) { 906 907 for (String encodedTextClass : ENCODED_TEXT_CLASSES) { 908 final String mimeType = 909 baseType + ";charset=" + charset + 910 ";class=" + encodedTextClass; 911 912 final LinkedHashSet<String> mimeTypes = 913 handleHtmlMimeTypes(baseType, mimeType); 914 915 for (String mt : mimeTypes) { 916 917 DataFlavor df = null; 918 919 try { 920 df = new DataFlavor(mt); 921 // Check for equality to plainTextFlavor so 922 // that we can ensure that the exact charset of 923 // plainTextFlavor, not the canonical charset 924 // or another equivalent charset with a 925 // different name, is used. 926 if (df.equals(DataFlavor.plainTextFlavor)) { 927 df = DataFlavor.plainTextFlavor; 928 } 929 } catch (ClassNotFoundException cannotHappen) { 930 } 931 932 returnValue.add(df); 933 } 934 } 935 } 936 937 if (TEXT_PLAIN_BASE_TYPE.equals(baseType)) 938 { 939 returnValue.add(DataFlavor.plainTextFlavor); 940 } 941 } else { 942 // Non-charset text natives should be treated as 943 // opaque, 8-bit data in any of its various 944 // representations. 945 for (String encodedTextClassName : ENCODED_TEXT_CLASSES) { 946 DataFlavor toAdd = null; 947 try { 948 toAdd = new DataFlavor(baseType + 949 ";class=" + encodedTextClassName); 950 } catch (ClassNotFoundException cannotHappen) { 951 } 952 returnValue.add(toAdd); 953 } 954 } 955 return returnValue; 956 } 957 958 private static final String [] htmlDocumntTypes = 959 new String [] {"all", "selection", "fragment"}; 960 961 private static LinkedHashSet<String> handleHtmlMimeTypes( 962 String baseType, String mimeType) { 963 964 LinkedHashSet<String> returnValues = new LinkedHashSet<>(); 965 966 if (HTML_TEXT_BASE_TYPE.equals(baseType)) { 967 for (String documentType : htmlDocumntTypes) { 968 returnValues.add(mimeType + ";document=" + documentType); 969 } 970 } else { 971 returnValues.add(mimeType); 972 } 973 974 return returnValues; 975 } 976 977 /** 978 * Returns a <code>Map</code> of the specified <code>DataFlavor</code>s to 979 * their most preferred <code>String</code> native. Each native value will 980 * be the same as the first native in the List returned by 981 * <code>getNativesForFlavor</code> for the specified flavor. 982 * <p> 983 * If a specified <code>DataFlavor</code> is previously unknown to the 984 * data transfer subsystem, then invoking this method will establish a 985 * mapping in both directions between the specified <code>DataFlavor</code> 986 * and an encoded version of its MIME type as its native. 987 * 988 * @param flavors an array of <code>DataFlavor</code>s which will be the 989 * key set of the returned <code>Map</code>. If <code>null</code> is 990 * specified, a mapping of all <code>DataFlavor</code>s known to the 991 * data transfer subsystem to their most preferred 992 * <code>String</code> natives will be returned. 993 * @return a <code>java.util.Map</code> of <code>DataFlavor</code>s to 994 * <code>String</code> natives 995 * 996 * @see #getNativesForFlavor 997 * @see #encodeDataFlavor 998 */ 999 public synchronized Map<DataFlavor,String> 1000 getNativesForFlavors(DataFlavor[] flavors) 1001 { 1002 // Use getNativesForFlavor to generate extra natives for text flavors 1003 // and stringFlavor 1004 1005 if (flavors == null) { 1006 List flavor_list = getFlavorsForNative(null); 1007 flavors = new DataFlavor[flavor_list.size()]; 1008 flavor_list.toArray(flavors); 1009 } 1010 1011 Map<DataFlavor, String> retval = new HashMap<>(flavors.length, 1.0f); 1012 for (DataFlavor flavor : flavors) { 1013 List<String> natives = getNativesForFlavor(flavor); 1014 String nat = (natives.isEmpty()) ? null : natives.get(0); 1015 retval.put(flavor, nat); 1016 } 1017 1018 return retval; 1019 } 1020 1021 /** 1022 * Returns a <code>Map</code> of the specified <code>String</code> natives 1023 * to their most preferred <code>DataFlavor</code>. Each 1024 * <code>DataFlavor</code> value will be the same as the first 1025 * <code>DataFlavor</code> in the List returned by 1026 * <code>getFlavorsForNative</code> for the specified native. 1027 * <p> 1028 * If a specified native is previously unknown to the data transfer 1029 * subsystem, and that native has been properly encoded, then invoking this 1030 * method will establish a mapping in both directions between the specified 1031 * native and a <code>DataFlavor</code> whose MIME type is a decoded 1032 * version of the native. 1033 * 1034 * @param natives an array of <code>String</code>s which will be the 1035 * key set of the returned <code>Map</code>. If <code>null</code> is 1036 * specified, a mapping of all supported <code>String</code> natives 1037 * to their most preferred <code>DataFlavor</code>s will be 1038 * returned. 1039 * @return a <code>java.util.Map</code> of <code>String</code> natives to 1040 * <code>DataFlavor</code>s 1041 * 1042 * @see #getFlavorsForNative 1043 * @see #encodeJavaMIMEType 1044 */ 1045 public synchronized Map<String,DataFlavor> 1046 getFlavorsForNatives(String[] natives) 1047 { 1048 // Use getFlavorsForNative to generate extra flavors for text natives 1049 1050 if (natives == null) { 1051 List native_list = getNativesForFlavor(null); 1052 natives = new String[native_list.size()]; 1053 native_list.toArray(natives); 1054 } 1055 1056 Map<String, DataFlavor> retval = new HashMap<>(natives.length, 1.0f); 1057 for (String aNative : natives) { 1058 List<DataFlavor> flavors = getFlavorsForNative(aNative); 1059 DataFlavor flav = (flavors.isEmpty())? null : flavors.get(0); 1060 retval.put(aNative, flav); 1061 } 1062 1063 return retval; 1064 } 1065 1066 /** 1067 * Adds a mapping from the specified <code>DataFlavor</code> (and all 1068 * <code>DataFlavor</code>s equal to the specified <code>DataFlavor</code>) 1069 * to the specified <code>String</code> native. 1070 * Unlike <code>getNativesForFlavor</code>, the mapping will only be 1071 * established in one direction, and the native will not be encoded. To 1072 * establish a two-way mapping, call 1073 * <code>addFlavorForUnencodedNative</code> as well. The new mapping will 1074 * be of lower priority than any existing mapping. 1075 * This method has no effect if a mapping from the specified or equal 1076 * <code>DataFlavor</code> to the specified <code>String</code> native 1077 * already exists. 1078 * 1079 * @param flav the <code>DataFlavor</code> key for the mapping 1080 * @param nat the <code>String</code> native value for the mapping 1081 * @throws NullPointerException if flav or nat is <code>null</code> 1082 * 1083 * @see #addFlavorForUnencodedNative 1084 * @since 1.4 1085 */ 1086 public synchronized void addUnencodedNativeForFlavor(DataFlavor flav, 1087 String nat) { 1088 if (flav == null || nat == null) { 1089 throw new NullPointerException("null arguments not permitted"); 1090 } 1091 1092 List<String> natives = getFlavorToNative().get(flav); 1093 if (natives == null) { 1094 natives = new ArrayList<>(1); 1095 getFlavorToNative().put(flav, natives); 1096 } else if (natives.contains(nat)) { 1097 return; 1098 } 1099 natives.add(nat); 1100 getNativesForFlavorCache.remove(flav); 1101 getNativesForFlavorCache.remove(null); 1102 } 1103 1104 /** 1105 * Discards the current mappings for the specified <code>DataFlavor</code> 1106 * and all <code>DataFlavor</code>s equal to the specified 1107 * <code>DataFlavor</code>, and creates new mappings to the 1108 * specified <code>String</code> natives. 1109 * Unlike <code>getNativesForFlavor</code>, the mappings will only be 1110 * established in one direction, and the natives will not be encoded. To 1111 * establish two-way mappings, call <code>setFlavorsForNative</code> 1112 * as well. The first native in the array will represent the highest 1113 * priority mapping. Subsequent natives will represent mappings of 1114 * decreasing priority. 1115 * <p> 1116 * If the array contains several elements that reference equal 1117 * <code>String</code> natives, this method will establish new mappings 1118 * for the first of those elements and ignore the rest of them. 1119 * <p> 1120 * It is recommended that client code not reset mappings established by the 1121 * data transfer subsystem. This method should only be used for 1122 * application-level mappings. 1123 * 1124 * @param flav the <code>DataFlavor</code> key for the mappings 1125 * @param natives the <code>String</code> native values for the mappings 1126 * @throws NullPointerException if flav or natives is <code>null</code> 1127 * or if natives contains <code>null</code> elements 1128 * 1129 * @see #setFlavorsForNative 1130 * @since 1.4 1131 */ 1132 public synchronized void setNativesForFlavor(DataFlavor flav, 1133 String[] natives) { 1134 if (flav == null || natives == null) { 1135 throw new NullPointerException("null arguments not permitted"); 1136 } 1137 1138 getFlavorToNative().remove(flav); 1139 for (String aNative : natives) { 1140 addUnencodedNativeForFlavor(flav, aNative); 1141 } 1142 disabledMappingGenerationKeys.add(flav); 1143 // Clear the cache to handle the case of empty natives. 1144 getNativesForFlavorCache.remove(flav); 1145 getNativesForFlavorCache.remove(null); 1146 } 1147 1148 /** 1149 * Adds a mapping from a single <code>String</code> native to a single 1150 * <code>DataFlavor</code>. Unlike <code>getFlavorsForNative</code>, the 1151 * mapping will only be established in one direction, and the native will 1152 * not be encoded. To establish a two-way mapping, call 1153 * <code>addUnencodedNativeForFlavor</code> as well. The new mapping will 1154 * be of lower priority than any existing mapping. 1155 * This method has no effect if a mapping from the specified 1156 * <code>String</code> native to the specified or equal 1157 * <code>DataFlavor</code> already exists. 1158 * 1159 * @param nat the <code>String</code> native key for the mapping 1160 * @param flav the <code>DataFlavor</code> value for the mapping 1161 * @throws NullPointerException if nat or flav is <code>null</code> 1162 * 1163 * @see #addUnencodedNativeForFlavor 1164 * @since 1.4 1165 */ 1166 public synchronized void addFlavorForUnencodedNative(String nat, 1167 DataFlavor flav) { 1168 if (nat == null || flav == null) { 1169 throw new NullPointerException("null arguments not permitted"); 1170 } 1171 1172 List<DataFlavor> flavors = getNativeToFlavor().get(nat); 1173 if (flavors == null) { 1174 flavors = new ArrayList<>(1); 1175 getNativeToFlavor().put(nat, flavors); 1176 } else if (flavors.contains(flav)) { 1177 return; 1178 } 1179 flavors.add(flav); 1180 getFlavorsForNativeCache.remove(nat); 1181 getFlavorsForNativeCache.remove(null); 1182 } 1183 1184 /** 1185 * Discards the current mappings for the specified <code>String</code> 1186 * native, and creates new mappings to the specified 1187 * <code>DataFlavor</code>s. Unlike <code>getFlavorsForNative</code>, the 1188 * mappings will only be established in one direction, and the natives need 1189 * not be encoded. To establish two-way mappings, call 1190 * <code>setNativesForFlavor</code> as well. The first 1191 * <code>DataFlavor</code> in the array will represent the highest priority 1192 * mapping. Subsequent <code>DataFlavor</code>s will represent mappings of 1193 * decreasing priority. 1194 * <p> 1195 * If the array contains several elements that reference equal 1196 * <code>DataFlavor</code>s, this method will establish new mappings 1197 * for the first of those elements and ignore the rest of them. 1198 * <p> 1199 * It is recommended that client code not reset mappings established by the 1200 * data transfer subsystem. This method should only be used for 1201 * application-level mappings. 1202 * 1203 * @param nat the <code>String</code> native key for the mappings 1204 * @param flavors the <code>DataFlavor</code> values for the mappings 1205 * @throws NullPointerException if nat or flavors is <code>null</code> 1206 * or if flavors contains <code>null</code> elements 1207 * 1208 * @see #setNativesForFlavor 1209 * @since 1.4 1210 */ 1211 public synchronized void setFlavorsForNative(String nat, 1212 DataFlavor[] flavors) { 1213 if (nat == null || flavors == null) { 1214 throw new NullPointerException("null arguments not permitted"); 1215 } 1216 1217 getNativeToFlavor().remove(nat); 1218 for (DataFlavor flavor : flavors) { 1219 addFlavorForUnencodedNative(nat, flavor); 1220 } 1221 disabledMappingGenerationKeys.add(nat); 1222 // Clear the cache to handle the case of empty flavors. 1223 getFlavorsForNativeCache.remove(nat); 1224 getFlavorsForNativeCache.remove(null); 1225 } 1226 1227 /** 1228 * Encodes a MIME type for use as a <code>String</code> native. The format 1229 * of an encoded representation of a MIME type is implementation-dependent. 1230 * The only restrictions are: 1231 * <ul> 1232 * <li>The encoded representation is <code>null</code> if and only if the 1233 * MIME type <code>String</code> is <code>null</code>.</li> 1234 * <li>The encoded representations for two non-<code>null</code> MIME type 1235 * <code>String</code>s are equal if and only if these <code>String</code>s 1236 * are equal according to <code>String.equals(Object)</code>.</li> 1237 * </ul> 1238 * <p> 1239 * The reference implementation of this method returns the specified MIME 1240 * type <code>String</code> prefixed with <code>JAVA_DATAFLAVOR:</code>. 1241 * 1242 * @param mimeType the MIME type to encode 1243 * @return the encoded <code>String</code>, or <code>null</code> if 1244 * mimeType is <code>null</code> 1245 */ 1246 public static String encodeJavaMIMEType(String mimeType) { 1247 return (mimeType != null) 1248 ? JavaMIME + mimeType 1249 : null; 1250 } 1251 1252 /** 1253 * Encodes a <code>DataFlavor</code> for use as a <code>String</code> 1254 * native. The format of an encoded <code>DataFlavor</code> is 1255 * implementation-dependent. The only restrictions are: 1256 * <ul> 1257 * <li>The encoded representation is <code>null</code> if and only if the 1258 * specified <code>DataFlavor</code> is <code>null</code> or its MIME type 1259 * <code>String</code> is <code>null</code>.</li> 1260 * <li>The encoded representations for two non-<code>null</code> 1261 * <code>DataFlavor</code>s with non-<code>null</code> MIME type 1262 * <code>String</code>s are equal if and only if the MIME type 1263 * <code>String</code>s of these <code>DataFlavor</code>s are equal 1264 * according to <code>String.equals(Object)</code>.</li> 1265 * </ul> 1266 * <p> 1267 * The reference implementation of this method returns the MIME type 1268 * <code>String</code> of the specified <code>DataFlavor</code> prefixed 1269 * with <code>JAVA_DATAFLAVOR:</code>. 1270 * 1271 * @param flav the <code>DataFlavor</code> to encode 1272 * @return the encoded <code>String</code>, or <code>null</code> if 1273 * flav is <code>null</code> or has a <code>null</code> MIME type 1274 */ 1275 public static String encodeDataFlavor(DataFlavor flav) { 1276 return (flav != null) 1277 ? SystemFlavorMap.encodeJavaMIMEType(flav.getMimeType()) 1278 : null; 1279 } 1280 1281 /** 1282 * Returns whether the specified <code>String</code> is an encoded Java 1283 * MIME type. 1284 * 1285 * @param str the <code>String</code> to test 1286 * @return <code>true</code> if the <code>String</code> is encoded; 1287 * <code>false</code> otherwise 1288 */ 1289 public static boolean isJavaMIMEType(String str) { 1290 return (str != null && str.startsWith(JavaMIME, 0)); 1291 } 1292 1293 /** 1294 * Decodes a <code>String</code> native for use as a Java MIME type. 1295 * 1296 * @param nat the <code>String</code> to decode 1297 * @return the decoded Java MIME type, or <code>null</code> if nat is not 1298 * an encoded <code>String</code> native 1299 */ 1300 public static String decodeJavaMIMEType(String nat) { 1301 return (isJavaMIMEType(nat)) 1302 ? nat.substring(JavaMIME.length(), nat.length()).trim() 1303 : null; 1304 } 1305 1306 /** 1307 * Decodes a <code>String</code> native for use as a 1308 * <code>DataFlavor</code>. 1309 * 1310 * @param nat the <code>String</code> to decode 1311 * @return the decoded <code>DataFlavor</code>, or <code>null</code> if 1312 * nat is not an encoded <code>String</code> native 1313 * @throws ClassNotFoundException if the class of the data flavor 1314 * is not loaded 1315 */ 1316 public static DataFlavor decodeDataFlavor(String nat) 1317 throws ClassNotFoundException 1318 { 1319 String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat); 1320 return (retval_str != null) 1321 ? new DataFlavor(retval_str) 1322 : null; 1323 } 1324 1325 private List<String> getAllNativesForType(String type) { 1326 Set<String> retval = null; 1327 for (DataFlavor dataFlavor : convertMimeTypeToDataFlavors(type)) { 1328 List<String> natives = getFlavorToNative().get(dataFlavor); 1329 if (natives != null && !natives.isEmpty()) { 1330 if (retval == null) { 1331 retval = new LinkedHashSet<>(); 1332 } 1333 retval.addAll(natives); 1334 } 1335 } 1336 return retval == null ? null : new ArrayList<>(retval); 1337 } 1338 }