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