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