1 /* 2 * Copyright (c) 1997, 2015, 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.security.AccessController; 37 import java.security.PrivilegedAction; 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.HashMap; 41 import java.util.HashSet; 42 import java.util.LinkedHashSet; 43 import java.util.List; 44 import java.util.Map; 45 import java.util.Objects; 46 import java.util.Set; 47 48 /** 49 * The SystemFlavorMap is a configurable map between "natives" (Strings), which 50 * correspond to platform-specific data formats, and "flavors" (DataFlavors), 51 * which correspond to platform-independent MIME types. This mapping is used 52 * by the data transfer subsystem to transfer data between Java and native 53 * applications, and between Java applications in separate VMs. 54 * 55 * @since 1.2 56 */ 57 public final class SystemFlavorMap implements FlavorMap, FlavorTable { 58 59 /** 60 * Constant prefix used to tag Java types converted to native platform 61 * type. 62 */ 63 private static String JavaMIME = "JAVA_DATAFLAVOR:"; 64 65 private static final Object FLAVOR_MAP_KEY = new Object(); 66 67 /** 68 * The list of valid, decoded text flavor representation classes, in order 69 * from best to worst. 70 */ 71 private static final String[] UNICODE_TEXT_CLASSES = { 72 "java.io.Reader", "java.lang.String", "java.nio.CharBuffer", "\"[C\"" 73 }; 74 75 /** 76 * The list of valid, encoded text flavor representation classes, in order 77 * from best to worst. 78 */ 79 private static final String[] ENCODED_TEXT_CLASSES = { 80 "java.io.InputStream", "java.nio.ByteBuffer", "\"[B\"" 81 }; 82 83 /** 84 * A String representing text/plain MIME type. 85 */ 86 private static final String TEXT_PLAIN_BASE_TYPE = "text/plain"; 87 88 /** 89 * A String representing text/html MIME type. 90 */ 91 private static final String HTML_TEXT_BASE_TYPE = "text/html"; 92 93 /** 94 * Maps native Strings to Lists of DataFlavors (or base type Strings for 95 * text DataFlavors). 96 * Do not use the field directly, use getNativeToFlavor() instead. 97 */ 98 private final Map<String, LinkedHashSet<DataFlavor>> nativeToFlavor = new HashMap<>(); 99 100 /** 101 * Accessor to nativeToFlavor map. Since we use lazy initialization we must 102 * use this accessor instead of direct access to the field which may not be 103 * initialized yet. This method will initialize the field if needed. 104 * 105 * @return nativeToFlavor 106 */ 107 private Map<String, LinkedHashSet<DataFlavor>> getNativeToFlavor() { 108 if (!isMapInitialized) { 109 initSystemFlavorMap(); 110 } 111 return nativeToFlavor; 112 } 113 114 /** 115 * Maps DataFlavors (or base type Strings for text DataFlavors) to Lists of 116 * native Strings. 117 * Do not use the field directly, use getFlavorToNative() instead. 118 */ 119 private final Map<DataFlavor, LinkedHashSet<String>> flavorToNative = new HashMap<>(); 120 121 /** 122 * Accessor to flavorToNative map. Since we use lazy initialization we must 123 * use this accessor instead of direct access to the field which may not be 124 * initialized yet. This method will initialize the field if needed. 125 * 126 * @return flavorToNative 127 */ 128 private synchronized Map<DataFlavor, LinkedHashSet<String>> getFlavorToNative() { 129 if (!isMapInitialized) { 130 initSystemFlavorMap(); 131 } 132 return flavorToNative; 133 } 134 135 /** 136 * Maps a text DataFlavor primary mime-type to the native. Used only to store 137 * standard mappings registered in the flavormap.properties 138 * Do not use this field directly, use getTextTypeToNative() instead. 139 */ 140 private Map<String, LinkedHashSet<String>> textTypeToNative = new HashMap<>(); 141 142 /** 143 * Shows if the object has been initialized. 144 */ 145 private boolean isMapInitialized = false; 146 147 /** 148 * An accessor to textTypeToNative map. Since we use lazy initialization we 149 * must use this accessor instead of direct access to the field which may not 150 * be initialized yet. This method will initialize the field if needed. 151 * 152 * @return textTypeToNative 153 */ 154 private synchronized Map<String, LinkedHashSet<String>> getTextTypeToNative() { 155 if (!isMapInitialized) { 156 initSystemFlavorMap(); 157 // From this point the map should not be modified 158 textTypeToNative = Collections.unmodifiableMap(textTypeToNative); 159 } 160 return textTypeToNative; 161 } 162 163 /** 164 * Caches the result of getNativesForFlavor(). Maps DataFlavors to 165 * SoftReferences which reference LinkedHashSet of String natives. 166 */ 167 private final SoftCache<DataFlavor, String> nativesForFlavorCache = new SoftCache<>(); 168 169 /** 170 * Caches the result getFlavorsForNative(). Maps String natives to 171 * SoftReferences which reference LinkedHashSet of DataFlavors. 172 */ 173 private final SoftCache<String, DataFlavor> flavorsForNativeCache = new SoftCache<>(); 174 175 /** 176 * Dynamic mapping generation used for text mappings should not be applied 177 * to the DataFlavors and String natives for which the mappings have been 178 * explicitly specified with setFlavorsForNative() or 179 * setNativesForFlavor(). This keeps all such keys. 180 */ 181 private Set<Object> disabledMappingGenerationKeys = new HashSet<>(); 182 183 /** 184 * Returns the default FlavorMap for this thread's ClassLoader. 185 * 186 * @return the default FlavorMap for this thread's ClassLoader 187 */ 188 public static FlavorMap getDefaultFlavorMap() { 189 return DataFlavorUtil.getDesktopService().getFlavorMap(SystemFlavorMap::new); 190 } 191 192 private SystemFlavorMap() { 193 } 194 195 /** 196 * Initializes a SystemFlavorMap by reading flavormap.properties 197 * For thread-safety must be called under lock on this. 198 */ 199 private void initSystemFlavorMap() { 200 if (isMapInitialized) { 201 return; 202 } 203 isMapInitialized = true; 204 205 InputStream is = AccessController.doPrivileged( 206 (PrivilegedAction<InputStream>) () -> { 207 return SystemFlavorMap.class.getResourceAsStream( 208 "/sun/datatransfer/resources/flavormap.properties"); 209 }); 210 if (is == null) { 211 throw new InternalError("Default flavor mapping not found"); 212 } 213 214 try (InputStreamReader isr = new InputStreamReader(is); 215 BufferedReader reader = new BufferedReader(isr)) { 216 String line; 217 while ((line = reader.readLine()) != null) { 349 } 350 351 /** 352 * Stores the listed object under the specified hash key in map. Unlike a 353 * standard map, the listed object will not replace any object already at 354 * the appropriate Map location, but rather will be appended to a List 355 * stored in that location. 356 */ 357 private <H, L> void store(H hashed, L listed, Map<H, LinkedHashSet<L>> map) { 358 LinkedHashSet<L> list = map.get(hashed); 359 if (list == null) { 360 list = new LinkedHashSet<>(1); 361 map.put(hashed, list); 362 } 363 if (!list.contains(listed)) { 364 list.add(listed); 365 } 366 } 367 368 /** 369 * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method 370 * handles the case where 'nat' is not found in 'nativeToFlavor'. In that 371 * case, a new DataFlavor is synthesized, stored, and returned, if and 372 * only if the specified native is encoded as a Java MIME type. 373 */ 374 private LinkedHashSet<DataFlavor> nativeToFlavorLookup(String nat) { 375 LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat); 376 377 if (nat != null && !disabledMappingGenerationKeys.contains(nat)) { 378 DesktopDatatransferService desktopService = DataFlavorUtil.getDesktopService(); 379 if (desktopService.isDesktopPresent()) { 380 LinkedHashSet<DataFlavor> platformFlavors = 381 desktopService.getPlatformMappingsForNative(nat); 382 if (!platformFlavors.isEmpty()) { 383 if (flavors != null) { 384 // Prepending the platform-specific mappings ensures 385 // that the flavors added with 386 // addFlavorForUnencodedNative() are at the end of 387 // list. 388 platformFlavors.addAll(flavors); 389 } 390 flavors = platformFlavors; 391 } 392 } 463 natives.add(encoded); 464 465 LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(encoded); 466 if (flavors == null) { 467 flavors = new LinkedHashSet<>(1); 468 getNativeToFlavor().put(encoded, flavors); 469 } 470 flavors.add(flav); 471 472 nativesForFlavorCache.remove(flav); 473 flavorsForNativeCache.remove(encoded); 474 } else { 475 natives = new LinkedHashSet<>(0); 476 } 477 } 478 479 return new LinkedHashSet<>(natives); 480 } 481 482 /** 483 * Returns a <code>List</code> of <code>String</code> natives to which the 484 * specified <code>DataFlavor</code> can be translated by the data transfer 485 * subsystem. The <code>List</code> will be sorted from best native to 486 * worst. That is, the first native will best reflect data in the specified 487 * flavor to the underlying native platform. 488 * <p> 489 * If the specified <code>DataFlavor</code> is previously unknown to the 490 * data transfer subsystem and the data transfer subsystem is unable to 491 * translate this <code>DataFlavor</code> to any existing native, then 492 * invoking this method will establish a 493 * mapping in both directions between the specified <code>DataFlavor</code> 494 * and an encoded version of its MIME type as its native. 495 * 496 * @param flav the <code>DataFlavor</code> whose corresponding natives 497 * should be returned. If <code>null</code> is specified, all 498 * natives currently known to the data transfer subsystem are 499 * returned in a non-deterministic order. 500 * @return a <code>java.util.List</code> of <code>java.lang.String</code> 501 * objects which are platform-specific representations of platform- 502 * specific data formats 503 * 504 * @see #encodeDataFlavor 505 * @since 1.4 506 */ 507 @Override 508 public synchronized List<String> getNativesForFlavor(DataFlavor flav) { 509 LinkedHashSet<String> retval = nativesForFlavorCache.check(flav); 510 if (retval != null) { 511 return new ArrayList<>(retval); 512 } 513 514 if (flav == null) { 515 retval = new LinkedHashSet<>(getNativeToFlavor().keySet()); 516 } else if (disabledMappingGenerationKeys.contains(flav)) { 517 // In this case we shouldn't synthesize a native for this flavor, 518 // since its mappings were explicitly specified. 519 retval = flavorToNativeLookup(flav, false); 520 } else if (DataFlavorUtil.isFlavorCharsetTextType(flav)) { 521 retval = new LinkedHashSet<>(0); 522 523 // For text/* flavors, flavor-to-native mappings specified in 549 retval = getTextTypeToNative().get(flav.mimeType.getBaseType()); 550 551 if (retval == null || retval.isEmpty()) { 552 retval = flavorToNativeLookup(flav, true); 553 } else { 554 // In this branch it is guaranteed that natives explicitly 555 // listed for flav's MIME type were added with 556 // addUnencodedNativeForFlavor(), so they have lower priority. 557 retval.addAll(flavorToNativeLookup(flav, false)); 558 } 559 } else { 560 retval = flavorToNativeLookup(flav, true); 561 } 562 563 nativesForFlavorCache.put(flav, retval); 564 // Create a copy, because client code can modify the returned list. 565 return new ArrayList<>(retval); 566 } 567 568 /** 569 * Returns a <code>List</code> of <code>DataFlavor</code>s to which the 570 * specified <code>String</code> native can be translated by the data 571 * transfer subsystem. The <code>List</code> will be sorted from best 572 * <code>DataFlavor</code> to worst. That is, the first 573 * <code>DataFlavor</code> will best reflect data in the specified 574 * native to a Java application. 575 * <p> 576 * If the specified native is previously unknown to the data transfer 577 * subsystem, and that native has been properly encoded, then invoking this 578 * method will establish a mapping in both directions between the specified 579 * native and a <code>DataFlavor</code> whose MIME type is a decoded 580 * version of the native. 581 * <p> 582 * If the specified native is not a properly encoded native and the 583 * mappings for this native have not been altered with 584 * <code>setFlavorsForNative</code>, then the contents of the 585 * <code>List</code> is platform dependent, but <code>null</code> 586 * cannot be returned. 587 * 588 * @param nat the native whose corresponding <code>DataFlavor</code>s 589 * should be returned. If <code>null</code> is specified, all 590 * <code>DataFlavor</code>s currently known to the data transfer 591 * subsystem are returned in a non-deterministic order. 592 * @return a <code>java.util.List</code> of <code>DataFlavor</code> 593 * objects into which platform-specific data in the specified, 594 * platform-specific native can be translated 595 * 596 * @see #encodeJavaMIMEType 597 * @since 1.4 598 */ 599 @Override 600 public synchronized List<DataFlavor> getFlavorsForNative(String nat) { 601 LinkedHashSet<DataFlavor> returnValue = flavorsForNativeCache.check(nat); 602 if (returnValue != null) { 603 return new ArrayList<>(returnValue); 604 } else { 605 returnValue = new LinkedHashSet<>(); 606 } 607 608 if (nat == null) { 609 for (String n : getNativesForFlavor(null)) { 610 returnValue.addAll(getFlavorsForNative(n)); 611 } 612 } else { 613 final LinkedHashSet<DataFlavor> flavors = nativeToFlavorLookup(nat); 614 if (disabledMappingGenerationKeys.contains(nat)) { 615 return new ArrayList<>(flavors); 724 private static final String [] htmlDocumentTypes = 725 new String [] {"all", "selection", "fragment"}; 726 727 private static LinkedHashSet<String> handleHtmlMimeTypes(String baseType, 728 String mimeType) { 729 730 LinkedHashSet<String> returnValues = new LinkedHashSet<>(); 731 732 if (HTML_TEXT_BASE_TYPE.equals(baseType)) { 733 for (String documentType : htmlDocumentTypes) { 734 returnValues.add(mimeType + ";document=" + documentType); 735 } 736 } else { 737 returnValues.add(mimeType); 738 } 739 740 return returnValues; 741 } 742 743 /** 744 * Returns a <code>Map</code> of the specified <code>DataFlavor</code>s to 745 * their most preferred <code>String</code> native. Each native value will 746 * be the same as the first native in the List returned by 747 * <code>getNativesForFlavor</code> for the specified flavor. 748 * <p> 749 * If a specified <code>DataFlavor</code> is previously unknown to the 750 * data transfer subsystem, then invoking this method will establish a 751 * mapping in both directions between the specified <code>DataFlavor</code> 752 * and an encoded version of its MIME type as its native. 753 * 754 * @param flavors an array of <code>DataFlavor</code>s which will be the 755 * key set of the returned <code>Map</code>. If <code>null</code> is 756 * specified, a mapping of all <code>DataFlavor</code>s known to the 757 * data transfer subsystem to their most preferred 758 * <code>String</code> natives will be returned. 759 * @return a <code>java.util.Map</code> of <code>DataFlavor</code>s to 760 * <code>String</code> natives 761 * 762 * @see #getNativesForFlavor 763 * @see #encodeDataFlavor 764 */ 765 @Override 766 public synchronized Map<DataFlavor,String> getNativesForFlavors(DataFlavor[] flavors) 767 { 768 // Use getNativesForFlavor to generate extra natives for text flavors 769 // and stringFlavor 770 771 if (flavors == null) { 772 List<DataFlavor> flavor_list = getFlavorsForNative(null); 773 flavors = new DataFlavor[flavor_list.size()]; 774 flavor_list.toArray(flavors); 775 } 776 777 Map<DataFlavor, String> retval = new HashMap<>(flavors.length, 1.0f); 778 for (DataFlavor flavor : flavors) { 779 List<String> natives = getNativesForFlavor(flavor); 780 String nat = (natives.isEmpty()) ? null : natives.get(0); 781 retval.put(flavor, nat); 782 } 783 784 return retval; 785 } 786 787 /** 788 * Returns a <code>Map</code> of the specified <code>String</code> natives 789 * to their most preferred <code>DataFlavor</code>. Each 790 * <code>DataFlavor</code> value will be the same as the first 791 * <code>DataFlavor</code> in the List returned by 792 * <code>getFlavorsForNative</code> for the specified native. 793 * <p> 794 * If a specified native is previously unknown to the data transfer 795 * subsystem, and that native has been properly encoded, then invoking this 796 * method will establish a mapping in both directions between the specified 797 * native and a <code>DataFlavor</code> whose MIME type is a decoded 798 * version of the native. 799 * 800 * @param natives an array of <code>String</code>s which will be the 801 * key set of the returned <code>Map</code>. If <code>null</code> is 802 * specified, a mapping of all supported <code>String</code> natives 803 * to their most preferred <code>DataFlavor</code>s will be 804 * returned. 805 * @return a <code>java.util.Map</code> of <code>String</code> natives to 806 * <code>DataFlavor</code>s 807 * 808 * @see #getFlavorsForNative 809 * @see #encodeJavaMIMEType 810 */ 811 @Override 812 public synchronized Map<String,DataFlavor> getFlavorsForNatives(String[] natives) 813 { 814 // Use getFlavorsForNative to generate extra flavors for text natives 815 if (natives == null) { 816 List<String> nativesList = getNativesForFlavor(null); 817 natives = new String[nativesList.size()]; 818 nativesList.toArray(natives); 819 } 820 821 Map<String, DataFlavor> retval = new HashMap<>(natives.length, 1.0f); 822 for (String aNative : natives) { 823 List<DataFlavor> flavors = getFlavorsForNative(aNative); 824 DataFlavor flav = (flavors.isEmpty())? null : flavors.get(0); 825 retval.put(aNative, flav); 826 } 827 return retval; 828 } 829 830 /** 831 * Adds a mapping from the specified <code>DataFlavor</code> (and all 832 * <code>DataFlavor</code>s equal to the specified <code>DataFlavor</code>) 833 * to the specified <code>String</code> native. 834 * Unlike <code>getNativesForFlavor</code>, the mapping will only be 835 * established in one direction, and the native will not be encoded. To 836 * establish a two-way mapping, call 837 * <code>addFlavorForUnencodedNative</code> as well. The new mapping will 838 * be of lower priority than any existing mapping. 839 * This method has no effect if a mapping from the specified or equal 840 * <code>DataFlavor</code> to the specified <code>String</code> native 841 * already exists. 842 * 843 * @param flav the <code>DataFlavor</code> key for the mapping 844 * @param nat the <code>String</code> native value for the mapping 845 * @throws NullPointerException if flav or nat is <code>null</code> 846 * 847 * @see #addFlavorForUnencodedNative 848 * @since 1.4 849 */ 850 public synchronized void addUnencodedNativeForFlavor(DataFlavor flav, 851 String nat) { 852 Objects.requireNonNull(nat, "Null native not permitted"); 853 Objects.requireNonNull(flav, "Null flavor not permitted"); 854 855 LinkedHashSet<String> natives = getFlavorToNative().get(flav); 856 if (natives == null) { 857 natives = new LinkedHashSet<>(1); 858 getFlavorToNative().put(flav, natives); 859 } 860 natives.add(nat); 861 nativesForFlavorCache.remove(flav); 862 } 863 864 /** 865 * Discards the current mappings for the specified <code>DataFlavor</code> 866 * and all <code>DataFlavor</code>s equal to the specified 867 * <code>DataFlavor</code>, and creates new mappings to the 868 * specified <code>String</code> natives. 869 * Unlike <code>getNativesForFlavor</code>, the mappings will only be 870 * established in one direction, and the natives will not be encoded. To 871 * establish two-way mappings, call <code>setFlavorsForNative</code> 872 * as well. The first native in the array will represent the highest 873 * priority mapping. Subsequent natives will represent mappings of 874 * decreasing priority. 875 * <p> 876 * If the array contains several elements that reference equal 877 * <code>String</code> natives, this method will establish new mappings 878 * for the first of those elements and ignore the rest of them. 879 * <p> 880 * It is recommended that client code not reset mappings established by the 881 * data transfer subsystem. This method should only be used for 882 * application-level mappings. 883 * 884 * @param flav the <code>DataFlavor</code> key for the mappings 885 * @param natives the <code>String</code> native values for the mappings 886 * @throws NullPointerException if flav or natives is <code>null</code> 887 * or if natives contains <code>null</code> elements 888 * 889 * @see #setFlavorsForNative 890 * @since 1.4 891 */ 892 public synchronized void setNativesForFlavor(DataFlavor flav, 893 String[] natives) { 894 Objects.requireNonNull(natives, "Null natives not permitted"); 895 Objects.requireNonNull(flav, "Null flavors not permitted"); 896 897 getFlavorToNative().remove(flav); 898 for (String aNative : natives) { 899 addUnencodedNativeForFlavor(flav, aNative); 900 } 901 disabledMappingGenerationKeys.add(flav); 902 nativesForFlavorCache.remove(flav); 903 } 904 905 /** 906 * Adds a mapping from a single <code>String</code> native to a single 907 * <code>DataFlavor</code>. Unlike <code>getFlavorsForNative</code>, the 908 * mapping will only be established in one direction, and the native will 909 * not be encoded. To establish a two-way mapping, call 910 * <code>addUnencodedNativeForFlavor</code> as well. The new mapping will 911 * be of lower priority than any existing mapping. 912 * This method has no effect if a mapping from the specified 913 * <code>String</code> native to the specified or equal 914 * <code>DataFlavor</code> already exists. 915 * 916 * @param nat the <code>String</code> native key for the mapping 917 * @param flav the <code>DataFlavor</code> value for the mapping 918 * @throws NullPointerException if nat or flav is <code>null</code> 919 * 920 * @see #addUnencodedNativeForFlavor 921 * @since 1.4 922 */ 923 public synchronized void addFlavorForUnencodedNative(String nat, 924 DataFlavor flav) { 925 Objects.requireNonNull(nat, "Null native not permitted"); 926 Objects.requireNonNull(flav, "Null flavor not permitted"); 927 928 LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat); 929 if (flavors == null) { 930 flavors = new LinkedHashSet<>(1); 931 getNativeToFlavor().put(nat, flavors); 932 } 933 flavors.add(flav); 934 flavorsForNativeCache.remove(nat); 935 } 936 937 /** 938 * Discards the current mappings for the specified <code>String</code> 939 * native, and creates new mappings to the specified 940 * <code>DataFlavor</code>s. Unlike <code>getFlavorsForNative</code>, the 941 * mappings will only be established in one direction, and the natives need 942 * not be encoded. To establish two-way mappings, call 943 * <code>setNativesForFlavor</code> as well. The first 944 * <code>DataFlavor</code> in the array will represent the highest priority 945 * mapping. Subsequent <code>DataFlavor</code>s will represent mappings of 946 * decreasing priority. 947 * <p> 948 * If the array contains several elements that reference equal 949 * <code>DataFlavor</code>s, this method will establish new mappings 950 * for the first of those elements and ignore the rest of them. 951 * <p> 952 * It is recommended that client code not reset mappings established by the 953 * data transfer subsystem. This method should only be used for 954 * application-level mappings. 955 * 956 * @param nat the <code>String</code> native key for the mappings 957 * @param flavors the <code>DataFlavor</code> values for the mappings 958 * @throws NullPointerException if nat or flavors is <code>null</code> 959 * or if flavors contains <code>null</code> elements 960 * 961 * @see #setNativesForFlavor 962 * @since 1.4 963 */ 964 public synchronized void setFlavorsForNative(String nat, 965 DataFlavor[] flavors) { 966 Objects.requireNonNull(nat, "Null native not permitted"); 967 Objects.requireNonNull(flavors, "Null flavors not permitted"); 968 969 getNativeToFlavor().remove(nat); 970 for (DataFlavor flavor : flavors) { 971 addFlavorForUnencodedNative(nat, flavor); 972 } 973 disabledMappingGenerationKeys.add(nat); 974 flavorsForNativeCache.remove(nat); 975 } 976 977 /** 978 * Encodes a MIME type for use as a <code>String</code> native. The format 979 * of an encoded representation of a MIME type is implementation-dependent. 980 * The only restrictions are: 981 * <ul> 982 * <li>The encoded representation is <code>null</code> if and only if the 983 * MIME type <code>String</code> is <code>null</code>.</li> 984 * <li>The encoded representations for two non-<code>null</code> MIME type 985 * <code>String</code>s are equal if and only if these <code>String</code>s 986 * are equal according to <code>String.equals(Object)</code>.</li> 987 * </ul> 988 * <p> 989 * The reference implementation of this method returns the specified MIME 990 * type <code>String</code> prefixed with <code>JAVA_DATAFLAVOR:</code>. 991 * 992 * @param mimeType the MIME type to encode 993 * @return the encoded <code>String</code>, or <code>null</code> if 994 * mimeType is <code>null</code> 995 */ 996 public static String encodeJavaMIMEType(String mimeType) { 997 return (mimeType != null) 998 ? JavaMIME + mimeType 999 : null; 1000 } 1001 1002 /** 1003 * Encodes a <code>DataFlavor</code> for use as a <code>String</code> 1004 * native. The format of an encoded <code>DataFlavor</code> is 1005 * implementation-dependent. The only restrictions are: 1006 * <ul> 1007 * <li>The encoded representation is <code>null</code> if and only if the 1008 * specified <code>DataFlavor</code> is <code>null</code> or its MIME type 1009 * <code>String</code> is <code>null</code>.</li> 1010 * <li>The encoded representations for two non-<code>null</code> 1011 * <code>DataFlavor</code>s with non-<code>null</code> MIME type 1012 * <code>String</code>s are equal if and only if the MIME type 1013 * <code>String</code>s of these <code>DataFlavor</code>s are equal 1014 * according to <code>String.equals(Object)</code>.</li> 1015 * </ul> 1016 * <p> 1017 * The reference implementation of this method returns the MIME type 1018 * <code>String</code> of the specified <code>DataFlavor</code> prefixed 1019 * with <code>JAVA_DATAFLAVOR:</code>. 1020 * 1021 * @param flav the <code>DataFlavor</code> to encode 1022 * @return the encoded <code>String</code>, or <code>null</code> if 1023 * flav is <code>null</code> or has a <code>null</code> MIME type 1024 */ 1025 public static String encodeDataFlavor(DataFlavor flav) { 1026 return (flav != null) 1027 ? SystemFlavorMap.encodeJavaMIMEType(flav.getMimeType()) 1028 : null; 1029 } 1030 1031 /** 1032 * Returns whether the specified <code>String</code> is an encoded Java 1033 * MIME type. 1034 * 1035 * @param str the <code>String</code> to test 1036 * @return <code>true</code> if the <code>String</code> is encoded; 1037 * <code>false</code> otherwise 1038 */ 1039 public static boolean isJavaMIMEType(String str) { 1040 return (str != null && str.startsWith(JavaMIME, 0)); 1041 } 1042 1043 /** 1044 * Decodes a <code>String</code> native for use as a Java MIME type. 1045 * 1046 * @param nat the <code>String</code> to decode 1047 * @return the decoded Java MIME type, or <code>null</code> if nat is not 1048 * an encoded <code>String</code> native 1049 */ 1050 public static String decodeJavaMIMEType(String nat) { 1051 return (isJavaMIMEType(nat)) 1052 ? nat.substring(JavaMIME.length(), nat.length()).trim() 1053 : null; 1054 } 1055 1056 /** 1057 * Decodes a <code>String</code> native for use as a 1058 * <code>DataFlavor</code>. 1059 * 1060 * @param nat the <code>String</code> to decode 1061 * @return the decoded <code>DataFlavor</code>, or <code>null</code> if 1062 * nat is not an encoded <code>String</code> native 1063 * @throws ClassNotFoundException if the class of the data flavor 1064 * is not loaded 1065 */ 1066 public static DataFlavor decodeDataFlavor(String nat) 1067 throws ClassNotFoundException 1068 { 1069 String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat); 1070 return (retval_str != null) 1071 ? new DataFlavor(retval_str) 1072 : null; 1073 } 1074 1075 private static final class SoftCache<K, V> { 1076 Map<K, SoftReference<LinkedHashSet<V>>> cache; 1077 1078 public void put(K key, LinkedHashSet<V> value) { 1079 if (cache == null) { 1080 cache = new HashMap<>(1); 1081 } 1082 cache.put(key, new SoftReference<>(value)); 1083 } 1084 | 1 /* 2 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.awt.datatransfer; 27 28 import java.io.BufferedReader; 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.io.InputStreamReader; 32 import java.lang.ref.SoftReference; 33 import java.security.AccessController; 34 import java.security.PrivilegedAction; 35 import java.util.ArrayList; 36 import java.util.Collections; 37 import java.util.HashMap; 38 import java.util.HashSet; 39 import java.util.LinkedHashSet; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Objects; 43 import java.util.Set; 44 45 import sun.datatransfer.DataFlavorUtil; 46 import sun.datatransfer.DesktopDatatransferService; 47 48 /** 49 * The SystemFlavorMap is a configurable map between "natives" (Strings), which 50 * correspond to platform-specific data formats, and "flavors" (DataFlavors), 51 * which correspond to platform-independent MIME types. This mapping is used by 52 * the data transfer subsystem to transfer data between Java and native 53 * applications, and between Java applications in separate VMs. 54 * 55 * @since 1.2 56 */ 57 public final class SystemFlavorMap implements FlavorMap, FlavorTable { 58 59 /** 60 * Constant prefix used to tag Java types converted to native platform type. 61 */ 62 private static String JavaMIME = "JAVA_DATAFLAVOR:"; 63 64 private static final Object FLAVOR_MAP_KEY = new Object(); 65 66 /** 67 * The list of valid, decoded text flavor representation classes, in order 68 * from best to worst. 69 */ 70 private static final String[] UNICODE_TEXT_CLASSES = { 71 "java.io.Reader", "java.lang.String", "java.nio.CharBuffer", "\"[C\"" 72 }; 73 74 /** 75 * The list of valid, encoded text flavor representation classes, in order 76 * from best to worst. 77 */ 78 private static final String[] ENCODED_TEXT_CLASSES = { 79 "java.io.InputStream", "java.nio.ByteBuffer", "\"[B\"" 80 }; 81 82 /** 83 * A String representing text/plain MIME type. 84 */ 85 private static final String TEXT_PLAIN_BASE_TYPE = "text/plain"; 86 87 /** 88 * A String representing text/html MIME type. 89 */ 90 private static final String HTML_TEXT_BASE_TYPE = "text/html"; 91 92 /** 93 * Maps native Strings to Lists of DataFlavors (or base type Strings for 94 * text DataFlavors). 95 * <p> 96 * Do not use the field directly, use {@link #getNativeToFlavor} instead. 97 */ 98 private final Map<String, LinkedHashSet<DataFlavor>> nativeToFlavor = new HashMap<>(); 99 100 /** 101 * Accessor to nativeToFlavor map. Since we use lazy initialization we must 102 * use this accessor instead of direct access to the field which may not be 103 * initialized yet. This method will initialize the field if needed. 104 * 105 * @return nativeToFlavor 106 */ 107 private Map<String, LinkedHashSet<DataFlavor>> getNativeToFlavor() { 108 if (!isMapInitialized) { 109 initSystemFlavorMap(); 110 } 111 return nativeToFlavor; 112 } 113 114 /** 115 * Maps DataFlavors (or base type Strings for text DataFlavors) to Lists of 116 * native Strings. 117 * <p> 118 * Do not use the field directly, use {@link #getFlavorToNative} instead. 119 */ 120 private final Map<DataFlavor, LinkedHashSet<String>> flavorToNative = new HashMap<>(); 121 122 /** 123 * Accessor to flavorToNative map. Since we use lazy initialization we must 124 * use this accessor instead of direct access to the field which may not be 125 * initialized yet. This method will initialize the field if needed. 126 * 127 * @return flavorToNative 128 */ 129 private synchronized Map<DataFlavor, LinkedHashSet<String>> getFlavorToNative() { 130 if (!isMapInitialized) { 131 initSystemFlavorMap(); 132 } 133 return flavorToNative; 134 } 135 136 /** 137 * Maps a text DataFlavor primary mime-type to the native. Used only to 138 * store standard mappings registered in the {@code flavormap.properties}. 139 * <p> 140 * Do not use this field directly, use {@link #getTextTypeToNative} instead. 141 */ 142 private Map<String, LinkedHashSet<String>> textTypeToNative = new HashMap<>(); 143 144 /** 145 * Shows if the object has been initialized. 146 */ 147 private boolean isMapInitialized = false; 148 149 /** 150 * An accessor to textTypeToNative map. Since we use lazy initialization we 151 * must use this accessor instead of direct access to the field which may 152 * not be initialized yet. This method will initialize the field if needed. 153 * 154 * @return textTypeToNative 155 */ 156 private synchronized Map<String, LinkedHashSet<String>> getTextTypeToNative() { 157 if (!isMapInitialized) { 158 initSystemFlavorMap(); 159 // From this point the map should not be modified 160 textTypeToNative = Collections.unmodifiableMap(textTypeToNative); 161 } 162 return textTypeToNative; 163 } 164 165 /** 166 * Caches the result of getNativesForFlavor(). Maps DataFlavors to 167 * SoftReferences which reference LinkedHashSet of String natives. 168 */ 169 private final SoftCache<DataFlavor, String> nativesForFlavorCache = new SoftCache<>(); 170 171 /** 172 * Caches the result getFlavorsForNative(). Maps String natives to 173 * SoftReferences which reference LinkedHashSet of DataFlavors. 174 */ 175 private final SoftCache<String, DataFlavor> flavorsForNativeCache = new SoftCache<>(); 176 177 /** 178 * Dynamic mapping generation used for text mappings should not be applied 179 * to the DataFlavors and String natives for which the mappings have been 180 * explicitly specified with {@link #setFlavorsForNative} or 181 * {@link #setNativesForFlavor}. This keeps all such keys. 182 */ 183 private Set<Object> disabledMappingGenerationKeys = new HashSet<>(); 184 185 /** 186 * Returns the default FlavorMap for this thread's ClassLoader. 187 * 188 * @return the default FlavorMap for this thread's ClassLoader 189 */ 190 public static FlavorMap getDefaultFlavorMap() { 191 return DataFlavorUtil.getDesktopService().getFlavorMap(SystemFlavorMap::new); 192 } 193 194 private SystemFlavorMap() { 195 } 196 197 /** 198 * Initializes a SystemFlavorMap by reading {@code flavormap.properties}. 199 * For thread-safety must be called under lock on {@code this}. 200 */ 201 private void initSystemFlavorMap() { 202 if (isMapInitialized) { 203 return; 204 } 205 isMapInitialized = true; 206 207 InputStream is = AccessController.doPrivileged( 208 (PrivilegedAction<InputStream>) () -> { 209 return SystemFlavorMap.class.getResourceAsStream( 210 "/sun/datatransfer/resources/flavormap.properties"); 211 }); 212 if (is == null) { 213 throw new InternalError("Default flavor mapping not found"); 214 } 215 216 try (InputStreamReader isr = new InputStreamReader(is); 217 BufferedReader reader = new BufferedReader(isr)) { 218 String line; 219 while ((line = reader.readLine()) != null) { 351 } 352 353 /** 354 * Stores the listed object under the specified hash key in map. Unlike a 355 * standard map, the listed object will not replace any object already at 356 * the appropriate Map location, but rather will be appended to a List 357 * stored in that location. 358 */ 359 private <H, L> void store(H hashed, L listed, Map<H, LinkedHashSet<L>> map) { 360 LinkedHashSet<L> list = map.get(hashed); 361 if (list == null) { 362 list = new LinkedHashSet<>(1); 363 map.put(hashed, list); 364 } 365 if (!list.contains(listed)) { 366 list.add(listed); 367 } 368 } 369 370 /** 371 * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method handles 372 * the case where 'nat' is not found in 'nativeToFlavor'. In that case, a 373 * new DataFlavor is synthesized, stored, and returned, if and only if the 374 * specified native is encoded as a Java MIME type. 375 */ 376 private LinkedHashSet<DataFlavor> nativeToFlavorLookup(String nat) { 377 LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat); 378 379 if (nat != null && !disabledMappingGenerationKeys.contains(nat)) { 380 DesktopDatatransferService desktopService = DataFlavorUtil.getDesktopService(); 381 if (desktopService.isDesktopPresent()) { 382 LinkedHashSet<DataFlavor> platformFlavors = 383 desktopService.getPlatformMappingsForNative(nat); 384 if (!platformFlavors.isEmpty()) { 385 if (flavors != null) { 386 // Prepending the platform-specific mappings ensures 387 // that the flavors added with 388 // addFlavorForUnencodedNative() are at the end of 389 // list. 390 platformFlavors.addAll(flavors); 391 } 392 flavors = platformFlavors; 393 } 394 } 465 natives.add(encoded); 466 467 LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(encoded); 468 if (flavors == null) { 469 flavors = new LinkedHashSet<>(1); 470 getNativeToFlavor().put(encoded, flavors); 471 } 472 flavors.add(flav); 473 474 nativesForFlavorCache.remove(flav); 475 flavorsForNativeCache.remove(encoded); 476 } else { 477 natives = new LinkedHashSet<>(0); 478 } 479 } 480 481 return new LinkedHashSet<>(natives); 482 } 483 484 /** 485 * Returns a {@code List} of {@code String} natives to which the specified 486 * {@code DataFlavor} can be translated by the data transfer subsystem. The 487 * {@code List} will be sorted from best native to worst. That is, the first 488 * native will best reflect data in the specified flavor to the underlying 489 * native platform. 490 * <p> 491 * If the specified {@code DataFlavor} is previously unknown to the data 492 * transfer subsystem and the data transfer subsystem is unable to translate 493 * this {@code DataFlavor} to any existing native, then invoking this method 494 * will establish a mapping in both directions between the specified 495 * {@code DataFlavor} and an encoded version of its MIME type as its native. 496 * 497 * @param flav the {@code DataFlavor} whose corresponding natives should be 498 * returned. If {@code null} is specified, all natives currently 499 * known to the data transfer subsystem are returned in a 500 * non-deterministic order. 501 * @return a {@code java.util.List} of {@code java.lang.String} objects 502 * which are platform-specific representations of platform-specific 503 * data formats 504 * @see #encodeDataFlavor 505 * @since 1.4 506 */ 507 @Override 508 public synchronized List<String> getNativesForFlavor(DataFlavor flav) { 509 LinkedHashSet<String> retval = nativesForFlavorCache.check(flav); 510 if (retval != null) { 511 return new ArrayList<>(retval); 512 } 513 514 if (flav == null) { 515 retval = new LinkedHashSet<>(getNativeToFlavor().keySet()); 516 } else if (disabledMappingGenerationKeys.contains(flav)) { 517 // In this case we shouldn't synthesize a native for this flavor, 518 // since its mappings were explicitly specified. 519 retval = flavorToNativeLookup(flav, false); 520 } else if (DataFlavorUtil.isFlavorCharsetTextType(flav)) { 521 retval = new LinkedHashSet<>(0); 522 523 // For text/* flavors, flavor-to-native mappings specified in 549 retval = getTextTypeToNative().get(flav.mimeType.getBaseType()); 550 551 if (retval == null || retval.isEmpty()) { 552 retval = flavorToNativeLookup(flav, true); 553 } else { 554 // In this branch it is guaranteed that natives explicitly 555 // listed for flav's MIME type were added with 556 // addUnencodedNativeForFlavor(), so they have lower priority. 557 retval.addAll(flavorToNativeLookup(flav, false)); 558 } 559 } else { 560 retval = flavorToNativeLookup(flav, true); 561 } 562 563 nativesForFlavorCache.put(flav, retval); 564 // Create a copy, because client code can modify the returned list. 565 return new ArrayList<>(retval); 566 } 567 568 /** 569 * Returns a {@code List} of {@code DataFlavor}s to which the specified 570 * {@code String} native can be translated by the data transfer subsystem. 571 * The {@code List} will be sorted from best {@code DataFlavor} to worst. 572 * That is, the first {@code DataFlavor} will best reflect data in the 573 * specified native to a Java application. 574 * <p> 575 * If the specified native is previously unknown to the data transfer 576 * subsystem, and that native has been properly encoded, then invoking this 577 * method will establish a mapping in both directions between the specified 578 * native and a {@code DataFlavor} whose MIME type is a decoded version of 579 * the native. 580 * <p> 581 * If the specified native is not a properly encoded native and the mappings 582 * for this native have not been altered with {@code setFlavorsForNative}, 583 * then the contents of the {@code List} is platform dependent, but 584 * {@code null} cannot be returned. 585 * 586 * @param nat the native whose corresponding {@code DataFlavor}s should be 587 * returned. If {@code null} is specified, all {@code DataFlavor}s 588 * currently known to the data transfer subsystem are returned in a 589 * non-deterministic order. 590 * @return a {@code java.util.List} of {@code DataFlavor} objects into which 591 * platform-specific data in the specified, platform-specific native 592 * can be translated 593 * @see #encodeJavaMIMEType 594 * @since 1.4 595 */ 596 @Override 597 public synchronized List<DataFlavor> getFlavorsForNative(String nat) { 598 LinkedHashSet<DataFlavor> returnValue = flavorsForNativeCache.check(nat); 599 if (returnValue != null) { 600 return new ArrayList<>(returnValue); 601 } else { 602 returnValue = new LinkedHashSet<>(); 603 } 604 605 if (nat == null) { 606 for (String n : getNativesForFlavor(null)) { 607 returnValue.addAll(getFlavorsForNative(n)); 608 } 609 } else { 610 final LinkedHashSet<DataFlavor> flavors = nativeToFlavorLookup(nat); 611 if (disabledMappingGenerationKeys.contains(nat)) { 612 return new ArrayList<>(flavors); 721 private static final String [] htmlDocumentTypes = 722 new String [] {"all", "selection", "fragment"}; 723 724 private static LinkedHashSet<String> handleHtmlMimeTypes(String baseType, 725 String mimeType) { 726 727 LinkedHashSet<String> returnValues = new LinkedHashSet<>(); 728 729 if (HTML_TEXT_BASE_TYPE.equals(baseType)) { 730 for (String documentType : htmlDocumentTypes) { 731 returnValues.add(mimeType + ";document=" + documentType); 732 } 733 } else { 734 returnValues.add(mimeType); 735 } 736 737 return returnValues; 738 } 739 740 /** 741 * Returns a {@code Map} of the specified {@code DataFlavor}s to their most 742 * preferred {@code String} native. Each native value will be the same as 743 * the first native in the List returned by {@code getNativesForFlavor} for 744 * the specified flavor. 745 * <p> 746 * If a specified {@code DataFlavor} is previously unknown to the data 747 * transfer subsystem, then invoking this method will establish a mapping in 748 * both directions between the specified {@code DataFlavor} and an encoded 749 * version of its MIME type as its native. 750 * 751 * @param flavors an array of {@code DataFlavor}s which will be the key set 752 * of the returned {@code Map}. If {@code null} is specified, a 753 * mapping of all {@code DataFlavor}s known to the data transfer 754 * subsystem to their most preferred {@code String} natives will be 755 * returned. 756 * @return a {@code java.util.Map} of {@code DataFlavor}s to {@code String} 757 * natives 758 * @see #getNativesForFlavor 759 * @see #encodeDataFlavor 760 */ 761 @Override 762 public synchronized Map<DataFlavor,String> getNativesForFlavors(DataFlavor[] flavors) 763 { 764 // Use getNativesForFlavor to generate extra natives for text flavors 765 // and stringFlavor 766 767 if (flavors == null) { 768 List<DataFlavor> flavor_list = getFlavorsForNative(null); 769 flavors = new DataFlavor[flavor_list.size()]; 770 flavor_list.toArray(flavors); 771 } 772 773 Map<DataFlavor, String> retval = new HashMap<>(flavors.length, 1.0f); 774 for (DataFlavor flavor : flavors) { 775 List<String> natives = getNativesForFlavor(flavor); 776 String nat = (natives.isEmpty()) ? null : natives.get(0); 777 retval.put(flavor, nat); 778 } 779 780 return retval; 781 } 782 783 /** 784 * Returns a {@code Map} of the specified {@code String} natives to their 785 * most preferred {@code DataFlavor}. Each {@code DataFlavor} value will be 786 * the same as the first {@code DataFlavor} in the List returned by 787 * {@code getFlavorsForNative} for the specified native. 788 * <p> 789 * If a specified native is previously unknown to the data transfer 790 * subsystem, and that native has been properly encoded, then invoking this 791 * method will establish a mapping in both directions between the specified 792 * native and a {@code DataFlavor} whose MIME type is a decoded version of 793 * the native. 794 * 795 * @param natives an array of {@code String}s which will be the key set of 796 * the returned {@code Map}. If {@code null} is specified, a mapping 797 * of all supported {@code String} natives to their most preferred 798 * {@code DataFlavor}s will be returned. 799 * @return a {@code java.util.Map} of {@code String} natives to 800 * {@code DataFlavor}s 801 * @see #getFlavorsForNative 802 * @see #encodeJavaMIMEType 803 */ 804 @Override 805 public synchronized Map<String,DataFlavor> getFlavorsForNatives(String[] natives) 806 { 807 // Use getFlavorsForNative to generate extra flavors for text natives 808 if (natives == null) { 809 List<String> nativesList = getNativesForFlavor(null); 810 natives = new String[nativesList.size()]; 811 nativesList.toArray(natives); 812 } 813 814 Map<String, DataFlavor> retval = new HashMap<>(natives.length, 1.0f); 815 for (String aNative : natives) { 816 List<DataFlavor> flavors = getFlavorsForNative(aNative); 817 DataFlavor flav = (flavors.isEmpty())? null : flavors.get(0); 818 retval.put(aNative, flav); 819 } 820 return retval; 821 } 822 823 /** 824 * Adds a mapping from the specified {@code DataFlavor} (and all 825 * {@code DataFlavor}s equal to the specified {@code DataFlavor}) to the 826 * specified {@code String} native. Unlike {@code getNativesForFlavor}, the 827 * mapping will only be established in one direction, and the native will 828 * not be encoded. To establish a two-way mapping, call 829 * {@code addFlavorForUnencodedNative} as well. The new mapping will be of 830 * lower priority than any existing mapping. This method has no effect if a 831 * mapping from the specified or equal {@code DataFlavor} to the specified 832 * {@code String} native already exists. 833 * 834 * @param flav the {@code DataFlavor} key for the mapping 835 * @param nat the {@code String} native value for the mapping 836 * @throws NullPointerException if flav or nat is {@code null} 837 * @see #addFlavorForUnencodedNative 838 * @since 1.4 839 */ 840 public synchronized void addUnencodedNativeForFlavor(DataFlavor flav, 841 String nat) { 842 Objects.requireNonNull(nat, "Null native not permitted"); 843 Objects.requireNonNull(flav, "Null flavor not permitted"); 844 845 LinkedHashSet<String> natives = getFlavorToNative().get(flav); 846 if (natives == null) { 847 natives = new LinkedHashSet<>(1); 848 getFlavorToNative().put(flav, natives); 849 } 850 natives.add(nat); 851 nativesForFlavorCache.remove(flav); 852 } 853 854 /** 855 * Discards the current mappings for the specified {@code DataFlavor} and 856 * all {@code DataFlavor}s equal to the specified {@code DataFlavor}, and 857 * creates new mappings to the specified {@code String} natives. Unlike 858 * {@code getNativesForFlavor}, the mappings will only be established in one 859 * direction, and the natives will not be encoded. To establish two-way 860 * mappings, call {@code setFlavorsForNative} as well. The first native in 861 * the array will represent the highest priority mapping. Subsequent natives 862 * will represent mappings of decreasing priority. 863 * <p> 864 * If the array contains several elements that reference equal 865 * {@code String} natives, this method will establish new mappings for the 866 * first of those elements and ignore the rest of them. 867 * <p> 868 * It is recommended that client code not reset mappings established by the 869 * data transfer subsystem. This method should only be used for 870 * application-level mappings. 871 * 872 * @param flav the {@code DataFlavor} key for the mappings 873 * @param natives the {@code String} native values for the mappings 874 * @throws NullPointerException if flav or natives is {@code null} or if 875 * natives contains {@code null} elements 876 * @see #setFlavorsForNative 877 * @since 1.4 878 */ 879 public synchronized void setNativesForFlavor(DataFlavor flav, 880 String[] natives) { 881 Objects.requireNonNull(natives, "Null natives not permitted"); 882 Objects.requireNonNull(flav, "Null flavors not permitted"); 883 884 getFlavorToNative().remove(flav); 885 for (String aNative : natives) { 886 addUnencodedNativeForFlavor(flav, aNative); 887 } 888 disabledMappingGenerationKeys.add(flav); 889 nativesForFlavorCache.remove(flav); 890 } 891 892 /** 893 * Adds a mapping from a single {@code String} native to a single 894 * {@code DataFlavor}. Unlike {@code getFlavorsForNative}, the mapping will 895 * only be established in one direction, and the native will not be encoded. 896 * To establish a two-way mapping, call {@code addUnencodedNativeForFlavor} 897 * as well. The new mapping will be of lower priority than any existing 898 * mapping. This method has no effect if a mapping from the specified 899 * {@code String} native to the specified or equal {@code DataFlavor} 900 * already exists. 901 * 902 * @param nat the {@code String} native key for the mapping 903 * @param flav the {@code DataFlavor} value for the mapping 904 * @throws NullPointerException if {@code nat} or {@code flav} is 905 * {@code null} 906 * @see #addUnencodedNativeForFlavor 907 * @since 1.4 908 */ 909 public synchronized void addFlavorForUnencodedNative(String nat, 910 DataFlavor flav) { 911 Objects.requireNonNull(nat, "Null native not permitted"); 912 Objects.requireNonNull(flav, "Null flavor not permitted"); 913 914 LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat); 915 if (flavors == null) { 916 flavors = new LinkedHashSet<>(1); 917 getNativeToFlavor().put(nat, flavors); 918 } 919 flavors.add(flav); 920 flavorsForNativeCache.remove(nat); 921 } 922 923 /** 924 * Discards the current mappings for the specified {@code String} native, 925 * and creates new mappings to the specified {@code DataFlavor}s. Unlike 926 * {@code getFlavorsForNative}, the mappings will only be established in one 927 * direction, and the natives need not be encoded. To establish two-way 928 * mappings, call {@code setNativesForFlavor} as well. The first 929 * {@code DataFlavor} in the array will represent the highest priority 930 * mapping. Subsequent {@code DataFlavor}s will represent mappings of 931 * decreasing priority. 932 * <p> 933 * If the array contains several elements that reference equal 934 * {@code DataFlavor}s, this method will establish new mappings for the 935 * first of those elements and ignore the rest of them. 936 * <p> 937 * It is recommended that client code not reset mappings established by the 938 * data transfer subsystem. This method should only be used for 939 * application-level mappings. 940 * 941 * @param nat the {@code String} native key for the mappings 942 * @param flavors the {@code DataFlavor} values for the mappings 943 * @throws NullPointerException if {@code nat} or {@code flavors} is 944 * {@code null} or if {@code flavors} contains {@code null} elements 945 * @see #setNativesForFlavor 946 * @since 1.4 947 */ 948 public synchronized void setFlavorsForNative(String nat, 949 DataFlavor[] flavors) { 950 Objects.requireNonNull(nat, "Null native not permitted"); 951 Objects.requireNonNull(flavors, "Null flavors not permitted"); 952 953 getNativeToFlavor().remove(nat); 954 for (DataFlavor flavor : flavors) { 955 addFlavorForUnencodedNative(nat, flavor); 956 } 957 disabledMappingGenerationKeys.add(nat); 958 flavorsForNativeCache.remove(nat); 959 } 960 961 /** 962 * Encodes a MIME type for use as a {@code String} native. The format of an 963 * encoded representation of a MIME type is implementation-dependent. The 964 * only restrictions are: 965 * <ul> 966 * <li>The encoded representation is {@code null} if and only if the MIME 967 * type {@code String} is {@code null}</li> 968 * <li>The encoded representations for two non-{@code null} MIME type 969 * {@code String}s are equal if and only if these {@code String}s are 970 * equal according to {@code String.equals(Object)}</li> 971 * </ul> 972 * The reference implementation of this method returns the specified MIME 973 * type {@code String} prefixed with {@code JAVA_DATAFLAVOR:}. 974 * 975 * @param mimeType the MIME type to encode 976 * @return the encoded {@code String}, or {@code null} if {@code mimeType} 977 * is {@code null} 978 */ 979 public static String encodeJavaMIMEType(String mimeType) { 980 return (mimeType != null) 981 ? JavaMIME + mimeType 982 : null; 983 } 984 985 /** 986 * Encodes a {@code DataFlavor} for use as a {@code String} native. The 987 * format of an encoded {@code DataFlavor} is implementation-dependent. The 988 * only restrictions are: 989 * <ul> 990 * <li>The encoded representation is {@code null} if and only if the 991 * specified {@code DataFlavor} is {@code null} or its MIME type 992 * {@code String} is {@code null}</li> 993 * <li>The encoded representations for two non-{@code null} 994 * {@code DataFlavor}s with non-{@code null} MIME type {@code String}s 995 * are equal if and only if the MIME type {@code String}s of these 996 * {@code DataFlavor}s are equal according to 997 * {@code String.equals(Object)}</li> 998 * </ul> 999 * The reference implementation of this method returns the MIME type 1000 * {@code String} of the specified {@code DataFlavor} prefixed with 1001 * {@code JAVA_DATAFLAVOR:}. 1002 * 1003 * @param flav the {@code DataFlavor} to encode 1004 * @return the encoded {@code String}, or {@code null} if {@code flav} is 1005 * {@code null} or has a {@code null} MIME type 1006 */ 1007 public static String encodeDataFlavor(DataFlavor flav) { 1008 return (flav != null) 1009 ? SystemFlavorMap.encodeJavaMIMEType(flav.getMimeType()) 1010 : null; 1011 } 1012 1013 /** 1014 * Returns whether the specified {@code String} is an encoded Java MIME 1015 * type. 1016 * 1017 * @param str the {@code String} to test 1018 * @return {@code true} if the {@code String} is encoded; {@code false} 1019 * otherwise 1020 */ 1021 public static boolean isJavaMIMEType(String str) { 1022 return (str != null && str.startsWith(JavaMIME, 0)); 1023 } 1024 1025 /** 1026 * Decodes a {@code String} native for use as a Java MIME type. 1027 * 1028 * @param nat the {@code String} to decode 1029 * @return the decoded Java MIME type, or {@code null} if {@code nat} is not 1030 * an encoded {@code String} native 1031 */ 1032 public static String decodeJavaMIMEType(String nat) { 1033 return (isJavaMIMEType(nat)) 1034 ? nat.substring(JavaMIME.length(), nat.length()).trim() 1035 : null; 1036 } 1037 1038 /** 1039 * Decodes a {@code String} native for use as a {@code DataFlavor}. 1040 * 1041 * @param nat the {@code String} to decode 1042 * @return the decoded {@code DataFlavor}, or {@code null} if {@code nat} is 1043 * not an encoded {@code String} native 1044 * @throws ClassNotFoundException if the class of the data flavor is not 1045 * loaded 1046 */ 1047 public static DataFlavor decodeDataFlavor(String nat) 1048 throws ClassNotFoundException 1049 { 1050 String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat); 1051 return (retval_str != null) 1052 ? new DataFlavor(retval_str) 1053 : null; 1054 } 1055 1056 private static final class SoftCache<K, V> { 1057 Map<K, SoftReference<LinkedHashSet<V>>> cache; 1058 1059 public void put(K key, LinkedHashSet<V> value) { 1060 if (cache == null) { 1061 cache = new HashMap<>(1); 1062 } 1063 cache.put(key, new SoftReference<>(value)); 1064 } 1065 |