Path: blob/master/src/java.datatransfer/share/classes/java/awt/datatransfer/SystemFlavorMap.java
41159 views
/*1* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.awt.datatransfer;2627import java.io.BufferedReader;28import java.io.IOException;29import java.io.InputStream;30import java.io.InputStreamReader;31import java.lang.ref.SoftReference;32import java.security.AccessController;33import java.security.PrivilegedAction;34import java.util.ArrayList;35import java.util.Collections;36import java.util.HashMap;37import java.util.HashSet;38import java.util.LinkedHashSet;39import java.util.List;40import java.util.Map;41import java.util.Objects;42import java.util.Set;4344import sun.datatransfer.DataFlavorUtil;45import sun.datatransfer.DesktopDatatransferService;4647/**48* The SystemFlavorMap is a configurable map between "natives" (Strings), which49* correspond to platform-specific data formats, and "flavors" (DataFlavors),50* which correspond to platform-independent MIME types. This mapping is used by51* the data transfer subsystem to transfer data between Java and native52* applications, and between Java applications in separate VMs.53*54* @since 1.255*/56public final class SystemFlavorMap implements FlavorMap, FlavorTable {5758/**59* Constant prefix used to tag Java types converted to native platform type.60*/61private static String JavaMIME = "JAVA_DATAFLAVOR:";6263private static final Object FLAVOR_MAP_KEY = new Object();6465/**66* The list of valid, decoded text flavor representation classes, in order67* from best to worst.68*/69private static final String[] UNICODE_TEXT_CLASSES = {70"java.io.Reader", "java.lang.String", "java.nio.CharBuffer", "\"[C\""71};7273/**74* The list of valid, encoded text flavor representation classes, in order75* from best to worst.76*/77private static final String[] ENCODED_TEXT_CLASSES = {78"java.io.InputStream", "java.nio.ByteBuffer", "\"[B\""79};8081/**82* A String representing text/plain MIME type.83*/84private static final String TEXT_PLAIN_BASE_TYPE = "text/plain";8586/**87* A String representing text/html MIME type.88*/89private static final String HTML_TEXT_BASE_TYPE = "text/html";9091/**92* Maps native Strings to Lists of DataFlavors (or base type Strings for93* text DataFlavors).94* <p>95* Do not use the field directly, use {@link #getNativeToFlavor} instead.96*/97private final Map<String, LinkedHashSet<DataFlavor>> nativeToFlavor = new HashMap<>();9899/**100* Accessor to nativeToFlavor map. Since we use lazy initialization we must101* use this accessor instead of direct access to the field which may not be102* initialized yet. This method will initialize the field if needed.103*104* @return nativeToFlavor105*/106private Map<String, LinkedHashSet<DataFlavor>> getNativeToFlavor() {107if (!isMapInitialized) {108initSystemFlavorMap();109}110return nativeToFlavor;111}112113/**114* Maps DataFlavors (or base type Strings for text DataFlavors) to Lists of115* native Strings.116* <p>117* Do not use the field directly, use {@link #getFlavorToNative} instead.118*/119private final Map<DataFlavor, LinkedHashSet<String>> flavorToNative = new HashMap<>();120121/**122* Accessor to flavorToNative map. Since we use lazy initialization we must123* use this accessor instead of direct access to the field which may not be124* initialized yet. This method will initialize the field if needed.125*126* @return flavorToNative127*/128private synchronized Map<DataFlavor, LinkedHashSet<String>> getFlavorToNative() {129if (!isMapInitialized) {130initSystemFlavorMap();131}132return flavorToNative;133}134135/**136* Maps a text DataFlavor primary mime-type to the native. Used only to137* store standard mappings registered in the {@code flavormap.properties}.138* <p>139* Do not use this field directly, use {@link #getTextTypeToNative} instead.140*/141private Map<String, LinkedHashSet<String>> textTypeToNative = new HashMap<>();142143/**144* Shows if the object has been initialized.145*/146private boolean isMapInitialized = false;147148/**149* An accessor to textTypeToNative map. Since we use lazy initialization we150* must use this accessor instead of direct access to the field which may151* not be initialized yet. This method will initialize the field if needed.152*153* @return textTypeToNative154*/155private synchronized Map<String, LinkedHashSet<String>> getTextTypeToNative() {156if (!isMapInitialized) {157initSystemFlavorMap();158// From this point the map should not be modified159textTypeToNative = Collections.unmodifiableMap(textTypeToNative);160}161return textTypeToNative;162}163164/**165* Caches the result of getNativesForFlavor(). Maps DataFlavors to166* SoftReferences which reference LinkedHashSet of String natives.167*/168private final SoftCache<DataFlavor, String> nativesForFlavorCache = new SoftCache<>();169170/**171* Caches the result getFlavorsForNative(). Maps String natives to172* SoftReferences which reference LinkedHashSet of DataFlavors.173*/174private final SoftCache<String, DataFlavor> flavorsForNativeCache = new SoftCache<>();175176/**177* Dynamic mapping generation used for text mappings should not be applied178* to the DataFlavors and String natives for which the mappings have been179* explicitly specified with {@link #setFlavorsForNative} or180* {@link #setNativesForFlavor}. This keeps all such keys.181*/182private Set<Object> disabledMappingGenerationKeys = new HashSet<>();183184/**185* Returns the default FlavorMap for this thread's ClassLoader.186*187* @return the default FlavorMap for this thread's ClassLoader188*/189public static FlavorMap getDefaultFlavorMap() {190return DataFlavorUtil.getDesktopService().getFlavorMap(SystemFlavorMap::new);191}192193private SystemFlavorMap() {194}195196/**197* Initializes a SystemFlavorMap by reading {@code flavormap.properties}.198* For thread-safety must be called under lock on {@code this}.199*/200private void initSystemFlavorMap() {201if (isMapInitialized) {202return;203}204isMapInitialized = true;205206@SuppressWarnings("removal")207InputStream is = AccessController.doPrivileged(208(PrivilegedAction<InputStream>) () -> {209return SystemFlavorMap.class.getResourceAsStream(210"/sun/datatransfer/resources/flavormap.properties");211});212if (is == null) {213throw new InternalError("Default flavor mapping not found");214}215216try (InputStreamReader isr = new InputStreamReader(is);217BufferedReader reader = new BufferedReader(isr)) {218String line;219while ((line = reader.readLine()) != null) {220line = line.trim();221if (line.startsWith("#") || line.isEmpty()) continue;222while (line.endsWith("\\")) {223line = line.substring(0, line.length() - 1) + reader.readLine().trim();224}225int delimiterPosition = line.indexOf('=');226String key = line.substring(0, delimiterPosition).replace("\\ ", " ");227String[] values = line.substring(delimiterPosition + 1, line.length()).split(",");228for (String value : values) {229try {230value = loadConvert(value);231MimeType mime = new MimeType(value);232if ("text".equals(mime.getPrimaryType())) {233String charset = mime.getParameter("charset");234if (DataFlavorUtil.doesSubtypeSupportCharset(mime.getSubType(), charset))235{236// We need to store the charset and eoln237// parameters, if any, so that the238// DataTransferer will have this information239// for conversion into the native format.240DesktopDatatransferService desktopService =241DataFlavorUtil.getDesktopService();242if (desktopService.isDesktopPresent()) {243desktopService.registerTextFlavorProperties(244key, charset,245mime.getParameter("eoln"),246mime.getParameter("terminators"));247}248}249250// But don't store any of these parameters in the251// DataFlavor itself for any text natives (even252// non-charset ones). The SystemFlavorMap will253// synthesize the appropriate mappings later.254mime.removeParameter("charset");255mime.removeParameter("class");256mime.removeParameter("eoln");257mime.removeParameter("terminators");258value = mime.toString();259}260} catch (MimeTypeParseException e) {261e.printStackTrace();262continue;263}264265DataFlavor flavor;266try {267flavor = new DataFlavor(value);268} catch (Exception e) {269try {270flavor = new DataFlavor(value, null);271} catch (Exception ee) {272ee.printStackTrace();273continue;274}275}276277final LinkedHashSet<DataFlavor> dfs = new LinkedHashSet<>();278dfs.add(flavor);279280if ("text".equals(flavor.getPrimaryType())) {281dfs.addAll(convertMimeTypeToDataFlavors(value));282store(flavor.mimeType.getBaseType(), key, getTextTypeToNative());283}284285for (DataFlavor df : dfs) {286store(df, key, getFlavorToNative());287store(key, df, getNativeToFlavor());288}289}290}291} catch (IOException e) {292throw new InternalError("Error reading default flavor mapping", e);293}294}295296// Copied from java.util.Properties297private static String loadConvert(String theString) {298char aChar;299int len = theString.length();300StringBuilder outBuffer = new StringBuilder(len);301302for (int x = 0; x < len; ) {303aChar = theString.charAt(x++);304if (aChar == '\\') {305aChar = theString.charAt(x++);306if (aChar == 'u') {307// Read the xxxx308int value = 0;309for (int i = 0; i < 4; i++) {310aChar = theString.charAt(x++);311switch (aChar) {312case '0': case '1': case '2': case '3': case '4':313case '5': case '6': case '7': case '8': case '9': {314value = (value << 4) + aChar - '0';315break;316}317case 'a': case 'b': case 'c':318case 'd': case 'e': case 'f': {319value = (value << 4) + 10 + aChar - 'a';320break;321}322case 'A': case 'B': case 'C':323case 'D': case 'E': case 'F': {324value = (value << 4) + 10 + aChar - 'A';325break;326}327default: {328throw new IllegalArgumentException(329"Malformed \\uxxxx encoding.");330}331}332}333outBuffer.append((char)value);334} else {335if (aChar == 't') {336aChar = '\t';337} else if (aChar == 'r') {338aChar = '\r';339} else if (aChar == 'n') {340aChar = '\n';341} else if (aChar == 'f') {342aChar = '\f';343}344outBuffer.append(aChar);345}346} else {347outBuffer.append(aChar);348}349}350return outBuffer.toString();351}352353/**354* Stores the listed object under the specified hash key in map. Unlike a355* standard map, the listed object will not replace any object already at356* the appropriate Map location, but rather will be appended to a List357* stored in that location.358*/359private <H, L> void store(H hashed, L listed, Map<H, LinkedHashSet<L>> map) {360LinkedHashSet<L> list = map.get(hashed);361if (list == null) {362list = new LinkedHashSet<>(1);363map.put(hashed, list);364}365if (!list.contains(listed)) {366list.add(listed);367}368}369370/**371* Semantically equivalent to 'nativeToFlavor.get(nat)'. This method handles372* the case where 'nat' is not found in 'nativeToFlavor'. In that case, a373* new DataFlavor is synthesized, stored, and returned, if and only if the374* specified native is encoded as a Java MIME type.375*/376private LinkedHashSet<DataFlavor> nativeToFlavorLookup(String nat) {377LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat);378379if (nat != null && !disabledMappingGenerationKeys.contains(nat)) {380DesktopDatatransferService desktopService = DataFlavorUtil.getDesktopService();381if (desktopService.isDesktopPresent()) {382LinkedHashSet<DataFlavor> platformFlavors =383desktopService.getPlatformMappingsForNative(nat);384if (!platformFlavors.isEmpty()) {385if (flavors != null) {386// Prepending the platform-specific mappings ensures387// that the flavors added with388// addFlavorForUnencodedNative() are at the end of389// list.390platformFlavors.addAll(flavors);391}392flavors = platformFlavors;393}394}395}396397if (flavors == null && isJavaMIMEType(nat)) {398String decoded = decodeJavaMIMEType(nat);399DataFlavor flavor = null;400401try {402flavor = new DataFlavor(decoded);403} catch (Exception e) {404System.err.println("Exception \"" + e.getClass().getName() +405": " + e.getMessage() +406"\"while constructing DataFlavor for: " +407decoded);408}409410if (flavor != null) {411flavors = new LinkedHashSet<>(1);412getNativeToFlavor().put(nat, flavors);413flavors.add(flavor);414flavorsForNativeCache.remove(nat);415416LinkedHashSet<String> natives = getFlavorToNative().get(flavor);417if (natives == null) {418natives = new LinkedHashSet<>(1);419getFlavorToNative().put(flavor, natives);420}421natives.add(nat);422nativesForFlavorCache.remove(flavor);423}424}425426return (flavors != null) ? flavors : new LinkedHashSet<>(0);427}428429/**430* Semantically equivalent to 'flavorToNative.get(flav)'. This method431* handles the case where 'flav' is not found in 'flavorToNative' depending432* on the value of passes 'synthesize' parameter. If 'synthesize' is433* SYNTHESIZE_IF_NOT_FOUND a native is synthesized, stored, and returned by434* encoding the DataFlavor's MIME type. Otherwise an empty List is returned435* and 'flavorToNative' remains unaffected.436*/437private LinkedHashSet<String> flavorToNativeLookup(final DataFlavor flav,438final boolean synthesize) {439440LinkedHashSet<String> natives = getFlavorToNative().get(flav);441442if (flav != null && !disabledMappingGenerationKeys.contains(flav)) {443DesktopDatatransferService desktopService = DataFlavorUtil.getDesktopService();444if (desktopService.isDesktopPresent()) {445LinkedHashSet<String> platformNatives =446desktopService.getPlatformMappingsForFlavor(flav);447if (!platformNatives.isEmpty()) {448if (natives != null) {449// Prepend the platform-specific mappings to ensure450// that the natives added with451// addUnencodedNativeForFlavor() are at the end of452// list.453platformNatives.addAll(natives);454}455natives = platformNatives;456}457}458}459460if (natives == null) {461if (synthesize) {462String encoded = encodeDataFlavor(flav);463natives = new LinkedHashSet<>(1);464getFlavorToNative().put(flav, natives);465natives.add(encoded);466467LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(encoded);468if (flavors == null) {469flavors = new LinkedHashSet<>(1);470getNativeToFlavor().put(encoded, flavors);471}472flavors.add(flav);473474nativesForFlavorCache.remove(flav);475flavorsForNativeCache.remove(encoded);476} else {477natives = new LinkedHashSet<>(0);478}479}480481return new LinkedHashSet<>(natives);482}483484/**485* Returns a {@code List} of {@code String} natives to which the specified486* {@code DataFlavor} can be translated by the data transfer subsystem. The487* {@code List} will be sorted from best native to worst. That is, the first488* native will best reflect data in the specified flavor to the underlying489* native platform.490* <p>491* If the specified {@code DataFlavor} is previously unknown to the data492* transfer subsystem and the data transfer subsystem is unable to translate493* this {@code DataFlavor} to any existing native, then invoking this method494* will establish a mapping in both directions between the specified495* {@code DataFlavor} and an encoded version of its MIME type as its native.496*497* @param flav the {@code DataFlavor} whose corresponding natives should be498* returned. If {@code null} is specified, all natives currently499* known to the data transfer subsystem are returned in a500* non-deterministic order.501* @return a {@code java.util.List} of {@code java.lang.String} objects502* which are platform-specific representations of platform-specific503* data formats504* @see #encodeDataFlavor505* @since 1.4506*/507@Override508public synchronized List<String> getNativesForFlavor(DataFlavor flav) {509LinkedHashSet<String> retval = nativesForFlavorCache.check(flav);510if (retval != null) {511return new ArrayList<>(retval);512}513514if (flav == null) {515retval = 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.519retval = flavorToNativeLookup(flav, false);520} else if (DataFlavorUtil.isFlavorCharsetTextType(flav)) {521retval = new LinkedHashSet<>(0);522523// For text/* flavors, flavor-to-native mappings specified in524// flavormap.properties are stored per flavor's base type.525if ("text".equals(flav.getPrimaryType())) {526LinkedHashSet<String> textTypeNatives =527getTextTypeToNative().get(flav.mimeType.getBaseType());528if (textTypeNatives != null) {529retval.addAll(textTypeNatives);530}531}532533// Also include text/plain natives, but don't duplicate Strings534LinkedHashSet<String> textTypeNatives =535getTextTypeToNative().get(TEXT_PLAIN_BASE_TYPE);536if (textTypeNatives != null) {537retval.addAll(textTypeNatives);538}539540if (retval.isEmpty()) {541retval = flavorToNativeLookup(flav, true);542} else {543// In this branch it is guaranteed that natives explicitly544// listed for flav's MIME type were added with545// addUnencodedNativeForFlavor(), so they have lower priority.546retval.addAll(flavorToNativeLookup(flav, false));547}548} else if (DataFlavorUtil.isFlavorNoncharsetTextType(flav)) {549retval = getTextTypeToNative().get(flav.mimeType.getBaseType());550551if (retval == null || retval.isEmpty()) {552retval = flavorToNativeLookup(flav, true);553} else {554// In this branch it is guaranteed that natives explicitly555// listed for flav's MIME type were added with556// addUnencodedNativeForFlavor(), so they have lower priority.557retval.addAll(flavorToNativeLookup(flav, false));558}559} else {560retval = flavorToNativeLookup(flav, true);561}562563nativesForFlavorCache.put(flav, retval);564// Create a copy, because client code can modify the returned list.565return new ArrayList<>(retval);566}567568/**569* Returns a {@code List} of {@code DataFlavor}s to which the specified570* {@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 the573* specified native to a Java application.574* <p>575* If the specified native is previously unknown to the data transfer576* subsystem, and that native has been properly encoded, then invoking this577* method will establish a mapping in both directions between the specified578* native and a {@code DataFlavor} whose MIME type is a decoded version of579* the native.580* <p>581* If the specified native is not a properly encoded native and the mappings582* for this native have not been altered with {@code setFlavorsForNative},583* then the contents of the {@code List} is platform dependent, but584* {@code null} cannot be returned.585*586* @param nat the native whose corresponding {@code DataFlavor}s should be587* returned. If {@code null} is specified, all {@code DataFlavor}s588* currently known to the data transfer subsystem are returned in a589* non-deterministic order.590* @return a {@code java.util.List} of {@code DataFlavor} objects into which591* platform-specific data in the specified, platform-specific native592* can be translated593* @see #encodeJavaMIMEType594* @since 1.4595*/596@Override597public synchronized List<DataFlavor> getFlavorsForNative(String nat) {598LinkedHashSet<DataFlavor> returnValue = flavorsForNativeCache.check(nat);599if (returnValue != null) {600return new ArrayList<>(returnValue);601} else {602returnValue = new LinkedHashSet<>();603}604605if (nat == null) {606for (String n : getNativesForFlavor(null)) {607returnValue.addAll(getFlavorsForNative(n));608}609} else {610final LinkedHashSet<DataFlavor> flavors = nativeToFlavorLookup(nat);611if (disabledMappingGenerationKeys.contains(nat)) {612return new ArrayList<>(flavors);613}614615final LinkedHashSet<DataFlavor> flavorsWithSynthesized =616nativeToFlavorLookup(nat);617618for (DataFlavor df : flavorsWithSynthesized) {619returnValue.add(df);620if ("text".equals(df.getPrimaryType())) {621String baseType = df.mimeType.getBaseType();622returnValue.addAll(convertMimeTypeToDataFlavors(baseType));623}624}625}626flavorsForNativeCache.put(nat, returnValue);627return new ArrayList<>(returnValue);628}629630@SuppressWarnings("deprecation")631private static Set<DataFlavor> convertMimeTypeToDataFlavors(632final String baseType) {633634final Set<DataFlavor> returnValue = new LinkedHashSet<>();635636String subType = null;637638try {639final MimeType mimeType = new MimeType(baseType);640subType = mimeType.getSubType();641} catch (MimeTypeParseException mtpe) {642// Cannot happen, since we checked all mappings643// on load from flavormap.properties.644}645646if (DataFlavorUtil.doesSubtypeSupportCharset(subType, null)) {647if (TEXT_PLAIN_BASE_TYPE.equals(baseType))648{649returnValue.add(DataFlavor.stringFlavor);650}651652for (String unicodeClassName : UNICODE_TEXT_CLASSES) {653final String mimeType = baseType + ";charset=Unicode;class=" +654unicodeClassName;655656final LinkedHashSet<String> mimeTypes =657handleHtmlMimeTypes(baseType, mimeType);658for (String mt : mimeTypes) {659DataFlavor toAdd = null;660try {661toAdd = new DataFlavor(mt);662} catch (ClassNotFoundException cannotHappen) {663}664returnValue.add(toAdd);665}666}667668for (String charset : DataFlavorUtil.standardEncodings()) {669670for (String encodedTextClass : ENCODED_TEXT_CLASSES) {671final String mimeType =672baseType + ";charset=" + charset +673";class=" + encodedTextClass;674675final LinkedHashSet<String> mimeTypes =676handleHtmlMimeTypes(baseType, mimeType);677678for (String mt : mimeTypes) {679680DataFlavor df = null;681682try {683df = new DataFlavor(mt);684// Check for equality to plainTextFlavor so685// that we can ensure that the exact charset of686// plainTextFlavor, not the canonical charset687// or another equivalent charset with a688// different name, is used.689if (df.equals(DataFlavor.plainTextFlavor)) {690df = DataFlavor.plainTextFlavor;691}692} catch (ClassNotFoundException cannotHappen) {693}694695returnValue.add(df);696}697}698}699700if (TEXT_PLAIN_BASE_TYPE.equals(baseType))701{702returnValue.add(DataFlavor.plainTextFlavor);703}704} else {705// Non-charset text natives should be treated as706// opaque, 8-bit data in any of its various707// representations.708for (String encodedTextClassName : ENCODED_TEXT_CLASSES) {709DataFlavor toAdd = null;710try {711toAdd = new DataFlavor(baseType +712";class=" + encodedTextClassName);713} catch (ClassNotFoundException cannotHappen) {714}715returnValue.add(toAdd);716}717}718return returnValue;719}720721private static final String [] htmlDocumentTypes =722new String [] {"all", "selection", "fragment"};723724private static LinkedHashSet<String> handleHtmlMimeTypes(String baseType,725String mimeType) {726727LinkedHashSet<String> returnValues = new LinkedHashSet<>();728729if (HTML_TEXT_BASE_TYPE.equals(baseType)) {730for (String documentType : htmlDocumentTypes) {731returnValues.add(mimeType + ";document=" + documentType);732}733} else {734returnValues.add(mimeType);735}736737return returnValues;738}739740/**741* Returns a {@code Map} of the specified {@code DataFlavor}s to their most742* preferred {@code String} native. Each native value will be the same as743* the first native in the List returned by {@code getNativesForFlavor} for744* the specified flavor.745* <p>746* If a specified {@code DataFlavor} is previously unknown to the data747* transfer subsystem, then invoking this method will establish a mapping in748* both directions between the specified {@code DataFlavor} and an encoded749* version of its MIME type as its native.750*751* @param flavors an array of {@code DataFlavor}s which will be the key set752* of the returned {@code Map}. If {@code null} is specified, a753* mapping of all {@code DataFlavor}s known to the data transfer754* subsystem to their most preferred {@code String} natives will be755* returned.756* @return a {@code java.util.Map} of {@code DataFlavor}s to {@code String}757* natives758* @see #getNativesForFlavor759* @see #encodeDataFlavor760*/761@Override762public synchronized Map<DataFlavor,String> getNativesForFlavors(DataFlavor[] flavors)763{764// Use getNativesForFlavor to generate extra natives for text flavors765// and stringFlavor766767if (flavors == null) {768List<DataFlavor> flavor_list = getFlavorsForNative(null);769flavors = new DataFlavor[flavor_list.size()];770flavor_list.toArray(flavors);771}772773Map<DataFlavor, String> retval = new HashMap<>(flavors.length, 1.0f);774for (DataFlavor flavor : flavors) {775List<String> natives = getNativesForFlavor(flavor);776String nat = (natives.isEmpty()) ? null : natives.get(0);777retval.put(flavor, nat);778}779780return retval;781}782783/**784* Returns a {@code Map} of the specified {@code String} natives to their785* most preferred {@code DataFlavor}. Each {@code DataFlavor} value will be786* the same as the first {@code DataFlavor} in the List returned by787* {@code getFlavorsForNative} for the specified native.788* <p>789* If a specified native is previously unknown to the data transfer790* subsystem, and that native has been properly encoded, then invoking this791* method will establish a mapping in both directions between the specified792* native and a {@code DataFlavor} whose MIME type is a decoded version of793* the native.794*795* @param natives an array of {@code String}s which will be the key set of796* the returned {@code Map}. If {@code null} is specified, a mapping797* of all supported {@code String} natives to their most preferred798* {@code DataFlavor}s will be returned.799* @return a {@code java.util.Map} of {@code String} natives to800* {@code DataFlavor}s801* @see #getFlavorsForNative802* @see #encodeJavaMIMEType803*/804@Override805public synchronized Map<String,DataFlavor> getFlavorsForNatives(String[] natives)806{807// Use getFlavorsForNative to generate extra flavors for text natives808if (natives == null) {809List<String> nativesList = getNativesForFlavor(null);810natives = new String[nativesList.size()];811nativesList.toArray(natives);812}813814Map<String, DataFlavor> retval = new HashMap<>(natives.length, 1.0f);815for (String aNative : natives) {816List<DataFlavor> flavors = getFlavorsForNative(aNative);817DataFlavor flav = (flavors.isEmpty())? null : flavors.get(0);818retval.put(aNative, flav);819}820return retval;821}822823/**824* Adds a mapping from the specified {@code DataFlavor} (and all825* {@code DataFlavor}s equal to the specified {@code DataFlavor}) to the826* specified {@code String} native. Unlike {@code getNativesForFlavor}, the827* mapping will only be established in one direction, and the native will828* not be encoded. To establish a two-way mapping, call829* {@code addFlavorForUnencodedNative} as well. The new mapping will be of830* lower priority than any existing mapping. This method has no effect if a831* mapping from the specified or equal {@code DataFlavor} to the specified832* {@code String} native already exists.833*834* @param flav the {@code DataFlavor} key for the mapping835* @param nat the {@code String} native value for the mapping836* @throws NullPointerException if flav or nat is {@code null}837* @see #addFlavorForUnencodedNative838* @since 1.4839*/840public synchronized void addUnencodedNativeForFlavor(DataFlavor flav,841String nat) {842Objects.requireNonNull(nat, "Null native not permitted");843Objects.requireNonNull(flav, "Null flavor not permitted");844845LinkedHashSet<String> natives = getFlavorToNative().get(flav);846if (natives == null) {847natives = new LinkedHashSet<>(1);848getFlavorToNative().put(flav, natives);849}850natives.add(nat);851nativesForFlavorCache.remove(flav);852}853854/**855* Discards the current mappings for the specified {@code DataFlavor} and856* all {@code DataFlavor}s equal to the specified {@code DataFlavor}, and857* creates new mappings to the specified {@code String} natives. Unlike858* {@code getNativesForFlavor}, the mappings will only be established in one859* direction, and the natives will not be encoded. To establish two-way860* mappings, call {@code setFlavorsForNative} as well. The first native in861* the array will represent the highest priority mapping. Subsequent natives862* will represent mappings of decreasing priority.863* <p>864* If the array contains several elements that reference equal865* {@code String} natives, this method will establish new mappings for the866* first of those elements and ignore the rest of them.867* <p>868* It is recommended that client code not reset mappings established by the869* data transfer subsystem. This method should only be used for870* application-level mappings.871*872* @param flav the {@code DataFlavor} key for the mappings873* @param natives the {@code String} native values for the mappings874* @throws NullPointerException if flav or natives is {@code null} or if875* natives contains {@code null} elements876* @see #setFlavorsForNative877* @since 1.4878*/879public synchronized void setNativesForFlavor(DataFlavor flav,880String[] natives) {881Objects.requireNonNull(natives, "Null natives not permitted");882Objects.requireNonNull(flav, "Null flavors not permitted");883884getFlavorToNative().remove(flav);885for (String aNative : natives) {886addUnencodedNativeForFlavor(flav, aNative);887}888disabledMappingGenerationKeys.add(flav);889nativesForFlavorCache.remove(flav);890}891892/**893* Adds a mapping from a single {@code String} native to a single894* {@code DataFlavor}. Unlike {@code getFlavorsForNative}, the mapping will895* 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 existing898* mapping. This method has no effect if a mapping from the specified899* {@code String} native to the specified or equal {@code DataFlavor}900* already exists.901*902* @param nat the {@code String} native key for the mapping903* @param flav the {@code DataFlavor} value for the mapping904* @throws NullPointerException if {@code nat} or {@code flav} is905* {@code null}906* @see #addUnencodedNativeForFlavor907* @since 1.4908*/909public synchronized void addFlavorForUnencodedNative(String nat,910DataFlavor flav) {911Objects.requireNonNull(nat, "Null native not permitted");912Objects.requireNonNull(flav, "Null flavor not permitted");913914LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat);915if (flavors == null) {916flavors = new LinkedHashSet<>(1);917getNativeToFlavor().put(nat, flavors);918}919flavors.add(flav);920flavorsForNativeCache.remove(nat);921}922923/**924* Discards the current mappings for the specified {@code String} native,925* and creates new mappings to the specified {@code DataFlavor}s. Unlike926* {@code getFlavorsForNative}, the mappings will only be established in one927* direction, and the natives need not be encoded. To establish two-way928* mappings, call {@code setNativesForFlavor} as well. The first929* {@code DataFlavor} in the array will represent the highest priority930* mapping. Subsequent {@code DataFlavor}s will represent mappings of931* decreasing priority.932* <p>933* If the array contains several elements that reference equal934* {@code DataFlavor}s, this method will establish new mappings for the935* first of those elements and ignore the rest of them.936* <p>937* It is recommended that client code not reset mappings established by the938* data transfer subsystem. This method should only be used for939* application-level mappings.940*941* @param nat the {@code String} native key for the mappings942* @param flavors the {@code DataFlavor} values for the mappings943* @throws NullPointerException if {@code nat} or {@code flavors} is944* {@code null} or if {@code flavors} contains {@code null} elements945* @see #setNativesForFlavor946* @since 1.4947*/948public synchronized void setFlavorsForNative(String nat,949DataFlavor[] flavors) {950Objects.requireNonNull(nat, "Null native not permitted");951Objects.requireNonNull(flavors, "Null flavors not permitted");952953getNativeToFlavor().remove(nat);954for (DataFlavor flavor : flavors) {955addFlavorForUnencodedNative(nat, flavor);956}957disabledMappingGenerationKeys.add(nat);958flavorsForNativeCache.remove(nat);959}960961/**962* Encodes a MIME type for use as a {@code String} native. The format of an963* encoded representation of a MIME type is implementation-dependent. The964* only restrictions are:965* <ul>966* <li>The encoded representation is {@code null} if and only if the MIME967* type {@code String} is {@code null}</li>968* <li>The encoded representations for two non-{@code null} MIME type969* {@code String}s are equal if and only if these {@code String}s are970* equal according to {@code String.equals(Object)}</li>971* </ul>972* The reference implementation of this method returns the specified MIME973* type {@code String} prefixed with {@code JAVA_DATAFLAVOR:}.974*975* @param mimeType the MIME type to encode976* @return the encoded {@code String}, or {@code null} if {@code mimeType}977* is {@code null}978*/979public static String encodeJavaMIMEType(String mimeType) {980return (mimeType != null)981? JavaMIME + mimeType982: null;983}984985/**986* Encodes a {@code DataFlavor} for use as a {@code String} native. The987* format of an encoded {@code DataFlavor} is implementation-dependent. The988* only restrictions are:989* <ul>990* <li>The encoded representation is {@code null} if and only if the991* specified {@code DataFlavor} is {@code null} or its MIME type992* {@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}s995* are equal if and only if the MIME type {@code String}s of these996* {@code DataFlavor}s are equal according to997* {@code String.equals(Object)}</li>998* </ul>999* The reference implementation of this method returns the MIME type1000* {@code String} of the specified {@code DataFlavor} prefixed with1001* {@code JAVA_DATAFLAVOR:}.1002*1003* @param flav the {@code DataFlavor} to encode1004* @return the encoded {@code String}, or {@code null} if {@code flav} is1005* {@code null} or has a {@code null} MIME type1006*/1007public static String encodeDataFlavor(DataFlavor flav) {1008return (flav != null)1009? SystemFlavorMap.encodeJavaMIMEType(flav.getMimeType())1010: null;1011}10121013/**1014* Returns whether the specified {@code String} is an encoded Java MIME1015* type.1016*1017* @param str the {@code String} to test1018* @return {@code true} if the {@code String} is encoded; {@code false}1019* otherwise1020*/1021public static boolean isJavaMIMEType(String str) {1022return (str != null && str.startsWith(JavaMIME, 0));1023}10241025/**1026* Decodes a {@code String} native for use as a Java MIME type.1027*1028* @param nat the {@code String} to decode1029* @return the decoded Java MIME type, or {@code null} if {@code nat} is not1030* an encoded {@code String} native1031*/1032public static String decodeJavaMIMEType(String nat) {1033return (isJavaMIMEType(nat))1034? nat.substring(JavaMIME.length(), nat.length()).trim()1035: null;1036}10371038/**1039* Decodes a {@code String} native for use as a {@code DataFlavor}.1040*1041* @param nat the {@code String} to decode1042* @return the decoded {@code DataFlavor}, or {@code null} if {@code nat} is1043* not an encoded {@code String} native1044* @throws ClassNotFoundException if the class of the data flavor is not1045* loaded1046*/1047public static DataFlavor decodeDataFlavor(String nat)1048throws ClassNotFoundException1049{1050String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat);1051return (retval_str != null)1052? new DataFlavor(retval_str)1053: null;1054}10551056private static final class SoftCache<K, V> {1057Map<K, SoftReference<LinkedHashSet<V>>> cache;10581059public void put(K key, LinkedHashSet<V> value) {1060if (cache == null) {1061cache = new HashMap<>(1);1062}1063cache.put(key, new SoftReference<>(value));1064}10651066public void remove(K key) {1067if (cache == null) return;1068cache.remove(null);1069cache.remove(key);1070}10711072public LinkedHashSet<V> check(K key) {1073if (cache == null) return null;1074SoftReference<LinkedHashSet<V>> ref = cache.get(key);1075if (ref != null) {1076return ref.get();1077}1078return null;1079}1080}1081}108210831084