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