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