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