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