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