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