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