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