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