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