SystemFlavorMap.java revision 11425:a441d42065cf
167754Smsmith/* 267754Smsmith * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. 367754Smsmith * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4114237Snjl * 567754Smsmith * This code is free software; you can redistribute it and/or modify it 667754Smsmith * under the terms of the GNU General Public License version 2 only, as 767754Smsmith * published by the Free Software Foundation. Oracle designates this 867754Smsmith * particular file as subject to the "Classpath" exception as provided 967754Smsmith * by Oracle in the LICENSE file that accompanied this code. 1067754Smsmith * 1167754Smsmith * This code is distributed in the hope that it will be useful, but WITHOUT 12114237Snjl * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1370243Smsmith * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1467754Smsmith * version 2 for more details (a copy is included in the LICENSE file that 1567754Smsmith * accompanied this code). 1667754Smsmith * 1767754Smsmith * You should have received a copy of the GNU General Public License version 1867754Smsmith * 2 along with this work; if not, write to the Free Software Foundation, 1967754Smsmith * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 2067754Smsmith * 2167754Smsmith * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 2267754Smsmith * or visit www.oracle.com if you need additional information or have any 2367754Smsmith * questions. 2467754Smsmith */ 2567754Smsmith 2667754Smsmithpackage java.awt.datatransfer; 2767754Smsmith 2867754Smsmithimport sun.datatransfer.DataFlavorUtil; 2967754Smsmithimport sun.datatransfer.DesktopDatatransferService; 3067754Smsmith 3167754Smsmithimport java.io.BufferedReader; 3267754Smsmithimport java.io.IOException; 3367754Smsmithimport java.io.InputStream; 3467754Smsmithimport java.io.InputStreamReader; 3567754Smsmithimport java.lang.ref.SoftReference; 3667754Smsmithimport java.util.ArrayList; 3767754Smsmithimport java.util.Collections; 3867754Smsmithimport java.util.HashMap; 3967754Smsmithimport java.util.HashSet; 4067754Smsmithimport java.util.LinkedHashSet; 4167754Smsmithimport java.util.List; 4267754Smsmithimport java.util.Map; 4367754Smsmithimport java.util.Objects; 4467754Smsmithimport java.util.Set; 4567754Smsmith 4667754Smsmith/** 4767754Smsmith * The SystemFlavorMap is a configurable map between "natives" (Strings), which 4867754Smsmith * correspond to platform-specific data formats, and "flavors" (DataFlavors), 4967754Smsmith * which correspond to platform-independent MIME types. This mapping is used 5067754Smsmith * by the data transfer subsystem to transfer data between Java and native 5167754Smsmith * applications, and between Java applications in separate VMs. 5267754Smsmith * 5367754Smsmith * @since 1.2 5467754Smsmith */ 5567754Smsmithpublic final class SystemFlavorMap implements FlavorMap, FlavorTable { 5667754Smsmith 5767754Smsmith /** 5867754Smsmith * Constant prefix used to tag Java types converted to native platform 5967754Smsmith * type. 6067754Smsmith */ 6167754Smsmith private static String JavaMIME = "JAVA_DATAFLAVOR:"; 6267754Smsmith 6367754Smsmith private static final Object FLAVOR_MAP_KEY = new Object(); 6467754Smsmith 6567754Smsmith /** 6667754Smsmith * The list of valid, decoded text flavor representation classes, in order 6767754Smsmith * from best to worst. 6867754Smsmith */ 6967754Smsmith private static final String[] UNICODE_TEXT_CLASSES = { 7067754Smsmith "java.io.Reader", "java.lang.String", "java.nio.CharBuffer", "\"[C\"" 7167754Smsmith }; 7267754Smsmith 7367754Smsmith /** 7467754Smsmith * The list of valid, encoded text flavor representation classes, in order 7567754Smsmith * from best to worst. 7667754Smsmith */ 7767754Smsmith private static final String[] ENCODED_TEXT_CLASSES = { 7867754Smsmith "java.io.InputStream", "java.nio.ByteBuffer", "\"[B\"" 7967754Smsmith }; 8067754Smsmith 8167754Smsmith /** 8267754Smsmith * A String representing text/plain MIME type. 8367754Smsmith */ 8467754Smsmith private static final String TEXT_PLAIN_BASE_TYPE = "text/plain"; 8567754Smsmith 8667754Smsmith /** 8767754Smsmith * A String representing text/html MIME type. 8867754Smsmith */ 8967754Smsmith private static final String HTML_TEXT_BASE_TYPE = "text/html"; 9067754Smsmith 9167754Smsmith /** 9267754Smsmith * Maps native Strings to Lists of DataFlavors (or base type Strings for 9367754Smsmith * text DataFlavors). 9467754Smsmith * Do not use the field directly, use getNativeToFlavor() instead. 9567754Smsmith */ 9667754Smsmith private final Map<String, LinkedHashSet<DataFlavor>> nativeToFlavor = new HashMap<>(); 9767754Smsmith 9867754Smsmith /** 9967754Smsmith * Accessor to nativeToFlavor map. Since we use lazy initialization we must 10067754Smsmith * use this accessor instead of direct access to the field which may not be 10167754Smsmith * initialized yet. This method will initialize the field if needed. 10267754Smsmith * 10367754Smsmith * @return nativeToFlavor 10467754Smsmith */ 10567754Smsmith private Map<String, LinkedHashSet<DataFlavor>> getNativeToFlavor() { 10667754Smsmith if (!isMapInitialized) { 10767754Smsmith initSystemFlavorMap(); 10867754Smsmith } 10967754Smsmith return nativeToFlavor; 11067754Smsmith } 11167754Smsmith 11267754Smsmith /** 11367754Smsmith * Maps DataFlavors (or base type Strings for text DataFlavors) to Lists of 11467754Smsmith * native Strings. 11567754Smsmith * Do not use the field directly, use getFlavorToNative() instead. 11667754Smsmith */ 11767754Smsmith private final Map<DataFlavor, LinkedHashSet<String>> flavorToNative = new HashMap<>(); 11867754Smsmith 11967754Smsmith /** 12067754Smsmith * Accessor to flavorToNative map. Since we use lazy initialization we must 12167754Smsmith * use this accessor instead of direct access to the field which may not be 12267754Smsmith * initialized yet. This method will initialize the field if needed. 12367754Smsmith * 12469450Smsmith * @return flavorToNative 12567754Smsmith */ 12667754Smsmith private synchronized Map<DataFlavor, LinkedHashSet<String>> getFlavorToNative() { 12767754Smsmith if (!isMapInitialized) { 12867754Smsmith initSystemFlavorMap(); 12967754Smsmith } 13067754Smsmith return flavorToNative; 13167754Smsmith } 13277424Smsmith 13367754Smsmith /** 13477424Smsmith * Maps a text DataFlavor primary mime-type to the native. Used only to store 13591116Smsmith * standard mappings registered in the flavormap.properties 13667754Smsmith * Do not use this field directly, use getTextTypeToNative() instead. 13767754Smsmith */ 13899679Siwasaki private Map<String, LinkedHashSet<String>> textTypeToNative = new HashMap<>(); 13967754Smsmith 14067754Smsmith /** 14167754Smsmith * Shows if the object has been initialized. 14267754Smsmith */ 14383174Smsmith private boolean isMapInitialized = false; 14467754Smsmith 14583174Smsmith /** 14667754Smsmith * An accessor to textTypeToNative map. Since we use lazy initialization we 14783174Smsmith * must use this accessor instead of direct access to the field which may not 14867754Smsmith * be initialized yet. This method will initialize the field if needed. 14983174Smsmith * 15067754Smsmith * @return textTypeToNative 15167754Smsmith */ 15267754Smsmith private synchronized Map<String, LinkedHashSet<String>> getTextTypeToNative() { 15387031Smsmith if (!isMapInitialized) { 15467754Smsmith initSystemFlavorMap(); 15567754Smsmith // From this point the map should not be modified 15667754Smsmith textTypeToNative = Collections.unmodifiableMap(textTypeToNative); 15767754Smsmith } 15867754Smsmith return textTypeToNative; 15967754Smsmith } 16067754Smsmith 16167754Smsmith /** 16267754Smsmith * Caches the result of getNativesForFlavor(). Maps DataFlavors to 16367754Smsmith * SoftReferences which reference LinkedHashSet of String natives. 16467754Smsmith */ 16567754Smsmith private final SoftCache<DataFlavor, String> nativesForFlavorCache = new SoftCache<>(); 16667754Smsmith 16767754Smsmith /** 16867754Smsmith * Caches the result getFlavorsForNative(). Maps String natives to 16967754Smsmith * SoftReferences which reference LinkedHashSet of DataFlavors. 17067754Smsmith */ 17167754Smsmith private final SoftCache<String, DataFlavor> flavorsForNativeCache = new SoftCache<>(); 17267754Smsmith 17367754Smsmith /** 17467754Smsmith * Dynamic mapping generation used for text mappings should not be applied 17567754Smsmith * to the DataFlavors and String natives for which the mappings have been 17667754Smsmith * explicitly specified with setFlavorsForNative() or 17767754Smsmith * setNativesForFlavor(). This keeps all such keys. 17867754Smsmith */ 17967754Smsmith private Set<Object> disabledMappingGenerationKeys = new HashSet<>(); 18067754Smsmith 18167754Smsmith /** 18267754Smsmith * Returns the default FlavorMap for this thread's ClassLoader. 18367754Smsmith * 18467754Smsmith * @return the default FlavorMap for this thread's ClassLoader 18567754Smsmith */ 18667754Smsmith public static FlavorMap getDefaultFlavorMap() { 18767754Smsmith return DataFlavorUtil.getDesktopService().getFlavorMap(SystemFlavorMap::new); 18867754Smsmith } 18967754Smsmith 19067754Smsmith private SystemFlavorMap() { 19167754Smsmith } 19291116Smsmith 19367754Smsmith /** 19467754Smsmith * Initializes a SystemFlavorMap by reading flavormap.properties 19567754Smsmith * For thread-safety must be called under lock on this. 19667754Smsmith */ 19767754Smsmith private void initSystemFlavorMap() { 19867754Smsmith if (isMapInitialized) { 19987031Smsmith return; 20091116Smsmith } 20167754Smsmith isMapInitialized = true; 20267754Smsmith 20367754Smsmith InputStream is = SystemFlavorMap.class.getResourceAsStream("/sun/datatransfer/resources/flavormap.properties"); 20467754Smsmith if (is == null) { 20567754Smsmith throw new InternalError("Default flavor mapping not found"); 20667754Smsmith } 20767754Smsmith 20867754Smsmith try (InputStreamReader isr = new InputStreamReader(is); 20967754Smsmith BufferedReader reader = new BufferedReader(isr)) { 21067754Smsmith String line; 21167754Smsmith while ((line = reader.readLine()) != null) { 21267754Smsmith line = line.trim(); 21367754Smsmith if (line.startsWith("#") || line.isEmpty()) continue; 21499679Siwasaki while (line.endsWith("\\")) { 21567754Smsmith line = line.substring(0, line.length() - 1) + reader.readLine().trim(); 21667754Smsmith } 21767754Smsmith int delimiterPosition = line.indexOf('='); 21867754Smsmith String key = line.substring(0, delimiterPosition).replaceAll("\\ ", " "); 21967754Smsmith String[] values = line.substring(delimiterPosition + 1, line.length()).split(","); 22099679Siwasaki for (String value : values) { 22167754Smsmith try { 22267754Smsmith value = loadConvert(value); 22367754Smsmith MimeType mime = new MimeType(value); 22467754Smsmith if ("text".equals(mime.getPrimaryType())) { 22567754Smsmith String charset = mime.getParameter("charset"); 22667754Smsmith if (DataFlavorUtil.doesSubtypeSupportCharset(mime.getSubType(), charset)) 22783174Smsmith { 22867754Smsmith // We need to store the charset and eoln 22967754Smsmith // parameters, if any, so that the 23067754Smsmith // DataTransferer will have this information 23191116Smsmith // for conversion into the native format. 23267754Smsmith DesktopDatatransferService desktopService = 23367754Smsmith DataFlavorUtil.getDesktopService(); 23491116Smsmith if (desktopService.isDesktopPresent()) { 23591116Smsmith desktopService.registerTextFlavorProperties( 23691116Smsmith key, charset, 23791116Smsmith mime.getParameter("eoln"), 23899679Siwasaki mime.getParameter("terminators")); 23991116Smsmith } 24091116Smsmith } 24167754Smsmith 24267754Smsmith // But don't store any of these parameters in the 24367754Smsmith // DataFlavor itself for any text natives (even 24485756Smsmith // non-charset ones). The SystemFlavorMap will 24567754Smsmith // synthesize the appropriate mappings later. 24667754Smsmith mime.removeParameter("charset"); 24767754Smsmith mime.removeParameter("class"); 24899679Siwasaki mime.removeParameter("eoln"); 24967754Smsmith mime.removeParameter("terminators"); 25067754Smsmith value = mime.toString(); 25167754Smsmith } 25269746Smsmith } catch (MimeTypeParseException e) { 25367754Smsmith e.printStackTrace(); 25499679Siwasaki continue; 25567754Smsmith } 25685756Smsmith 25767754Smsmith DataFlavor flavor; 25887031Smsmith try { 25969746Smsmith flavor = new DataFlavor(value); 26069746Smsmith } catch (Exception e) { 26185756Smsmith try { 26270243Smsmith flavor = new DataFlavor(value, null); 26370243Smsmith } catch (Exception ee) { 26470243Smsmith ee.printStackTrace(); 26599146Siwasaki continue; 26669746Smsmith } 26787031Smsmith } 26887031Smsmith 26987031Smsmith final LinkedHashSet<DataFlavor> dfs = new LinkedHashSet<>(); 27099679Siwasaki dfs.add(flavor); 27187031Smsmith 27287031Smsmith if ("text".equals(flavor.getPrimaryType())) { 27387031Smsmith dfs.addAll(convertMimeTypeToDataFlavors(value)); 27487031Smsmith store(flavor.mimeType.getBaseType(), key, getTextTypeToNative()); 27587031Smsmith } 27687031Smsmith 27787031Smsmith for (DataFlavor df : dfs) { 27899146Siwasaki store(df, key, getFlavorToNative()); 27987031Smsmith store(key, df, getNativeToFlavor()); 28099679Siwasaki } 28199679Siwasaki } 28299679Siwasaki } 28399679Siwasaki } catch (IOException e) { 28499679Siwasaki throw new InternalError("Error reading default flavor mapping", e); 28569746Smsmith } 28677424Smsmith } 28769746Smsmith 28869746Smsmith // Copied from java.util.Properties 28999679Siwasaki private static String loadConvert(String theString) { 29069746Smsmith char aChar; 29169746Smsmith int len = theString.length(); 29299146Siwasaki StringBuilder outBuffer = new StringBuilder(len); 29399679Siwasaki 29499146Siwasaki for (int x = 0; x < len; ) { 29599146Siwasaki aChar = theString.charAt(x++); 29699146Siwasaki if (aChar == '\\') { 29799679Siwasaki aChar = theString.charAt(x++); 29899679Siwasaki if (aChar == 'u') { 29999679Siwasaki // Read the xxxx 30099146Siwasaki int value = 0; 30199679Siwasaki for (int i = 0; i < 4; i++) { 30299146Siwasaki aChar = theString.charAt(x++); 30399146Siwasaki switch (aChar) { 30499679Siwasaki case '0': case '1': case '2': case '3': case '4': 30599146Siwasaki case '5': case '6': case '7': case '8': case '9': { 30699146Siwasaki value = (value << 4) + aChar - '0'; 30799679Siwasaki break; 30899679Siwasaki } 30999146Siwasaki case 'a': case 'b': case 'c': 31099146Siwasaki case 'd': case 'e': case 'f': { 31167754Smsmith value = (value << 4) + 10 + aChar - 'a'; 31267754Smsmith break; 31367754Smsmith } 31477424Smsmith case 'A': case 'B': case 'C': 31567754Smsmith case 'D': case 'E': case 'F': { 31667754Smsmith value = (value << 4) + 10 + aChar - 'A'; 31799679Siwasaki break; 31867754Smsmith } 31967754Smsmith default: { 32067754Smsmith throw new IllegalArgumentException( 32167754Smsmith "Malformed \\uxxxx encoding."); 32267754Smsmith } 32399679Siwasaki } 32467754Smsmith } 32567754Smsmith outBuffer.append((char)value); 32667754Smsmith } else { 32767754Smsmith if (aChar == 't') { 32867754Smsmith aChar = '\t'; 32967754Smsmith } else if (aChar == 'r') { 33099679Siwasaki aChar = '\r'; 33199679Siwasaki } else if (aChar == 'n') { 33299679Siwasaki aChar = '\n'; 33399679Siwasaki } else if (aChar == 'f') { 33499679Siwasaki aChar = '\f'; 33567754Smsmith } 33667754Smsmith outBuffer.append(aChar); 33767754Smsmith } 33899679Siwasaki } else { 33967754Smsmith outBuffer.append(aChar); 34067754Smsmith } 34167754Smsmith } 34267754Smsmith return outBuffer.toString(); 34367754Smsmith } 34467754Smsmith 34567754Smsmith /** 34667754Smsmith * Stores the listed object under the specified hash key in map. Unlike a 34767754Smsmith * standard map, the listed object will not replace any object already at 34899679Siwasaki * the appropriate Map location, but rather will be appended to a List 34967754Smsmith * stored in that location. 35067754Smsmith */ 35167754Smsmith private <H, L> void store(H hashed, L listed, Map<H, LinkedHashSet<L>> map) { 35267754Smsmith LinkedHashSet<L> list = map.get(hashed); 35399679Siwasaki if (list == null) { 35499679Siwasaki list = new LinkedHashSet<>(1); 35599679Siwasaki map.put(hashed, list); 35699679Siwasaki } 35799679Siwasaki if (!list.contains(listed)) { 35867754Smsmith list.add(listed); 35967754Smsmith } 36067754Smsmith } 36167754Smsmith 36299679Siwasaki /** 36367754Smsmith * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method 36467754Smsmith * handles the case where 'nat' is not found in 'nativeToFlavor'. In that 36567754Smsmith * case, a new DataFlavor is synthesized, stored, and returned, if and 36667754Smsmith * only if the specified native is encoded as a Java MIME type. 36767754Smsmith */ 36867754Smsmith private LinkedHashSet<DataFlavor> nativeToFlavorLookup(String nat) { 36967754Smsmith LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat); 37067754Smsmith 37167754Smsmith if (nat != null && !disabledMappingGenerationKeys.contains(nat)) { 37267754Smsmith DesktopDatatransferService desktopService = DataFlavorUtil.getDesktopService(); 37367754Smsmith if (desktopService.isDesktopPresent()) { 37467754Smsmith LinkedHashSet<DataFlavor> platformFlavors = 37599679Siwasaki desktopService.getPlatformMappingsForNative(nat); 37667754Smsmith if (!platformFlavors.isEmpty()) { 37767754Smsmith if (flavors != null) { 37899679Siwasaki // Prepending the platform-specific mappings ensures 37967754Smsmith // that the flavors added with 38067754Smsmith // addFlavorForUnencodedNative() are at the end of 38167754Smsmith // list. 38267754Smsmith platformFlavors.addAll(flavors); 38367754Smsmith } 38467754Smsmith flavors = platformFlavors; 38567754Smsmith } 38667754Smsmith } 38767754Smsmith } 38899146Siwasaki 38967754Smsmith if (flavors == null && isJavaMIMEType(nat)) { 39099146Siwasaki String decoded = decodeJavaMIMEType(nat); 39199146Siwasaki DataFlavor flavor = null; 39267754Smsmith 39367754Smsmith try { 39467754Smsmith flavor = new DataFlavor(decoded); 39587031Smsmith } catch (Exception e) { 39667754Smsmith System.err.println("Exception \"" + e.getClass().getName() + 39767754Smsmith ": " + e.getMessage() + 39867754Smsmith "\"while constructing DataFlavor for: " + 39967754Smsmith decoded); 40067754Smsmith } 40184491Smsmith 40267754Smsmith if (flavor != null) { 40367754Smsmith flavors = new LinkedHashSet<>(1); 40467754Smsmith getNativeToFlavor().put(nat, flavors); 40591116Smsmith flavors.add(flavor); 40667754Smsmith flavorsForNativeCache.remove(nat); 40767754Smsmith 40867754Smsmith LinkedHashSet<String> natives = getFlavorToNative().get(flavor); 40967754Smsmith if (natives == null) { 41067754Smsmith natives = new LinkedHashSet<>(1); 41167754Smsmith getFlavorToNative().put(flavor, natives); 41267754Smsmith } 41367754Smsmith natives.add(nat); 41467754Smsmith nativesForFlavorCache.remove(flavor); 41567754Smsmith } 41667754Smsmith } 41767754Smsmith 41867754Smsmith return (flavors != null) ? flavors : new LinkedHashSet<>(0); 41967754Smsmith } 42067754Smsmith 42191116Smsmith /** 42267754Smsmith * Semantically equivalent to 'flavorToNative.get(flav)'. This method 42391116Smsmith * handles the case where 'flav' is not found in 'flavorToNative' depending 42491116Smsmith * on the value of passes 'synthesize' parameter. If 'synthesize' is 42591116Smsmith * SYNTHESIZE_IF_NOT_FOUND a native is synthesized, stored, and returned by 42691116Smsmith * encoding the DataFlavor's MIME type. Otherwise an empty List is returned 42767754Smsmith * and 'flavorToNative' remains unaffected. 42891116Smsmith */ 42967754Smsmith private LinkedHashSet<String> flavorToNativeLookup(final DataFlavor flav, 43091116Smsmith final boolean synthesize) { 43167754Smsmith 43291116Smsmith LinkedHashSet<String> natives = getFlavorToNative().get(flav); 43367754Smsmith 43467754Smsmith if (flav != null && !disabledMappingGenerationKeys.contains(flav)) { 43591116Smsmith DesktopDatatransferService desktopService = DataFlavorUtil.getDesktopService(); 43667754Smsmith if (desktopService.isDesktopPresent()) { 43791116Smsmith LinkedHashSet<String> platformNatives = 43891116Smsmith desktopService.getPlatformMappingsForFlavor(flav); 43991116Smsmith if (!platformNatives.isEmpty()) { 440102550Siwasaki if (natives != null) { 441102550Siwasaki // Prepend the platform-specific mappings to ensure 44291116Smsmith // that the natives added with 443102550Siwasaki // addUnencodedNativeForFlavor() are at the end of 444102550Siwasaki // list. 445102550Siwasaki platformNatives.addAll(natives); 446102550Siwasaki } 447102550Siwasaki natives = platformNatives; 44867754Smsmith } 44987031Smsmith } 45083174Smsmith } 45183174Smsmith 45283174Smsmith if (natives == null) { 45383174Smsmith if (synthesize) { 45491116Smsmith String encoded = encodeDataFlavor(flav); 45567754Smsmith natives = new LinkedHashSet<>(1); 45667754Smsmith getFlavorToNative().put(flav, natives); 45767754Smsmith natives.add(encoded); 45867754Smsmith 45967754Smsmith LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(encoded); 46067754Smsmith if (flavors == null) { 46167754Smsmith flavors = new LinkedHashSet<>(1); 46267754Smsmith getNativeToFlavor().put(encoded, flavors); 46367754Smsmith } 46467754Smsmith flavors.add(flav); 46567754Smsmith 46667754Smsmith nativesForFlavorCache.remove(flav); 46767754Smsmith flavorsForNativeCache.remove(encoded); 46867754Smsmith } else { 46967754Smsmith natives = new LinkedHashSet<>(0); 47067754Smsmith } 47167754Smsmith } 47267754Smsmith 47367754Smsmith return new LinkedHashSet<>(natives); 47467754Smsmith } 47567754Smsmith 47667754Smsmith /** 47767754Smsmith * Returns a <code>List</code> of <code>String</code> natives to which the 47867754Smsmith * specified <code>DataFlavor</code> can be translated by the data transfer 47967754Smsmith * subsystem. The <code>List</code> will be sorted from best native to 48067754Smsmith * worst. That is, the first native will best reflect data in the specified 48167754Smsmith * flavor to the underlying native platform. 48267754Smsmith * <p> 48367754Smsmith * If the specified <code>DataFlavor</code> is previously unknown to the 48499679Siwasaki * data transfer subsystem and the data transfer subsystem is unable to 48567754Smsmith * translate this <code>DataFlavor</code> to any existing native, then 48667754Smsmith * invoking this method will establish a 48767754Smsmith * mapping in both directions between the specified <code>DataFlavor</code> 48867754Smsmith * and an encoded version of its MIME type as its native. 48967754Smsmith * 49067754Smsmith * @param flav the <code>DataFlavor</code> whose corresponding natives 49167754Smsmith * should be returned. If <code>null</code> is specified, all 49267754Smsmith * natives currently known to the data transfer subsystem are 49367754Smsmith * returned in a non-deterministic order. 49467754Smsmith * @return a <code>java.util.List</code> of <code>java.lang.String</code> 49567754Smsmith * objects which are platform-specific representations of platform- 49667754Smsmith * specific data formats 49767754Smsmith * 49867754Smsmith * @see #encodeDataFlavor 49967754Smsmith * @since 1.4 50067754Smsmith */ 50167754Smsmith @Override 50267754Smsmith public synchronized List<String> getNativesForFlavor(DataFlavor flav) { 50367754Smsmith LinkedHashSet<String> retval = nativesForFlavorCache.check(flav); 50467754Smsmith if (retval != null) { 50567754Smsmith return new ArrayList<>(retval); 50667754Smsmith } 50767754Smsmith 50867754Smsmith if (flav == null) { 50967754Smsmith retval = new LinkedHashSet<>(getNativeToFlavor().keySet()); 51067754Smsmith } else if (disabledMappingGenerationKeys.contains(flav)) { 51167754Smsmith // In this case we shouldn't synthesize a native for this flavor, 51267754Smsmith // since its mappings were explicitly specified. 51367754Smsmith retval = flavorToNativeLookup(flav, false); 51467754Smsmith } else if (DataFlavorUtil.isFlavorCharsetTextType(flav)) { 51567754Smsmith retval = new LinkedHashSet<>(0); 51667754Smsmith 51767754Smsmith // For text/* flavors, flavor-to-native mappings specified in 51867754Smsmith // flavormap.properties are stored per flavor's base type. 51967754Smsmith if ("text".equals(flav.getPrimaryType())) { 52067754Smsmith LinkedHashSet<String> textTypeNatives = 52167754Smsmith getTextTypeToNative().get(flav.mimeType.getBaseType()); 52267754Smsmith if (textTypeNatives != null) { 52367754Smsmith retval.addAll(textTypeNatives); 52467754Smsmith } 52567754Smsmith } 52667754Smsmith 52799146Siwasaki // Also include text/plain natives, but don't duplicate Strings 52867754Smsmith LinkedHashSet<String> textTypeNatives = 52967754Smsmith getTextTypeToNative().get(TEXT_PLAIN_BASE_TYPE); 53091116Smsmith if (textTypeNatives != null) { 53167754Smsmith retval.addAll(textTypeNatives); 53299679Siwasaki } 53399679Siwasaki 53499679Siwasaki if (retval.isEmpty()) { 53599679Siwasaki retval = flavorToNativeLookup(flav, true); 53667754Smsmith } else { 53799679Siwasaki // In this branch it is guaranteed that natives explicitly 53884491Smsmith // listed for flav's MIME type were added with 53984491Smsmith // addUnencodedNativeForFlavor(), so they have lower priority. 54067754Smsmith retval.addAll(flavorToNativeLookup(flav, false)); 541100966Siwasaki } 54291116Smsmith } else if (DataFlavorUtil.isFlavorNoncharsetTextType(flav)) { 54367754Smsmith retval = getTextTypeToNative().get(flav.mimeType.getBaseType()); 54467754Smsmith 54567754Smsmith if (retval == null || retval.isEmpty()) { 54667754Smsmith retval = flavorToNativeLookup(flav, true); 54767754Smsmith } else { 54867754Smsmith // In this branch it is guaranteed that natives explicitly 54967754Smsmith // listed for flav's MIME type were added with 55067754Smsmith // addUnencodedNativeForFlavor(), so they have lower priority. 55167754Smsmith retval.addAll(flavorToNativeLookup(flav, false)); 55267754Smsmith } 55399679Siwasaki } else { 55499679Siwasaki retval = flavorToNativeLookup(flav, true); 55567754Smsmith } 55667754Smsmith 55791116Smsmith nativesForFlavorCache.put(flav, retval); 55867754Smsmith // Create a copy, because client code can modify the returned list. 55967754Smsmith return new ArrayList<>(retval); 56069450Smsmith } 56167754Smsmith 56267754Smsmith /** 56384491Smsmith * Returns a <code>List</code> of <code>DataFlavor</code>s to which the 56491116Smsmith * specified <code>String</code> native can be translated by the data 56571867Smsmith * transfer subsystem. The <code>List</code> will be sorted from best 56671867Smsmith * <code>DataFlavor</code> to worst. That is, the first 56769450Smsmith * <code>DataFlavor</code> will best reflect data in the specified 56871867Smsmith * native to a Java application. 56971867Smsmith * <p> 57082367Smsmith * If the specified native is previously unknown to the data transfer 57182367Smsmith * subsystem, and that native has been properly encoded, then invoking this 57280062Smsmith * method will establish a mapping in both directions between the specified 57371867Smsmith * native and a <code>DataFlavor</code> whose MIME type is a decoded 57471867Smsmith * version of the native. 57582367Smsmith * <p> 57680062Smsmith * If the specified native is not a properly encoded native and the 57771867Smsmith * mappings for this native have not been altered with 57871867Smsmith * <code>setFlavorsForNative</code>, then the contents of the 57969450Smsmith * <code>List</code> is platform dependent, but <code>null</code> 58067754Smsmith * cannot be returned. 58167754Smsmith * 58267754Smsmith * @param nat the native whose corresponding <code>DataFlavor</code>s 583114237Snjl * should be returned. If <code>null</code> is specified, all 584107325Siwasaki * <code>DataFlavor</code>s currently known to the data transfer 58582367Smsmith * subsystem are returned in a non-deterministic order. 58667754Smsmith * @return a <code>java.util.List</code> of <code>DataFlavor</code> 58767754Smsmith * objects into which platform-specific data in the specified, 58867754Smsmith * platform-specific native can be translated 58967754Smsmith * 59067754Smsmith * @see #encodeJavaMIMEType 59167754Smsmith * @since 1.4 59284491Smsmith */ 59367754Smsmith @Override 59467754Smsmith public synchronized List<DataFlavor> getFlavorsForNative(String nat) { 59569450Smsmith LinkedHashSet<DataFlavor> returnValue = flavorsForNativeCache.check(nat); 59667754Smsmith if (returnValue != null) { 59767754Smsmith return new ArrayList<>(returnValue); 59867754Smsmith } else { 59967754Smsmith returnValue = new LinkedHashSet<>(); 60067754Smsmith } 60167754Smsmith 60299679Siwasaki if (nat == null) { 60367754Smsmith for (String n : getNativesForFlavor(null)) { 60467754Smsmith returnValue.addAll(getFlavorsForNative(n)); 60567754Smsmith } 60667754Smsmith } else { 607114237Snjl final LinkedHashSet<DataFlavor> flavors = nativeToFlavorLookup(nat); 608107325Siwasaki if (disabledMappingGenerationKeys.contains(nat)) { 60999679Siwasaki return new ArrayList<>(flavors); 61067754Smsmith } 61167754Smsmith 61267754Smsmith final LinkedHashSet<DataFlavor> flavorsWithSynthesized = 61367754Smsmith nativeToFlavorLookup(nat); 61467754Smsmith 61567754Smsmith for (DataFlavor df : flavorsWithSynthesized) { 61667754Smsmith returnValue.add(df); 61784491Smsmith if ("text".equals(df.getPrimaryType())) { 61885756Smsmith String baseType = df.mimeType.getBaseType(); 61967754Smsmith returnValue.addAll(convertMimeTypeToDataFlavors(baseType)); 62085756Smsmith } 62185756Smsmith } 62267754Smsmith } 62367754Smsmith flavorsForNativeCache.put(nat, returnValue); 62467754Smsmith return new ArrayList<>(returnValue); 62567754Smsmith } 62684491Smsmith 62784491Smsmith @SuppressWarnings("deprecation") 62867754Smsmith private static Set<DataFlavor> convertMimeTypeToDataFlavors( 62967754Smsmith final String baseType) { 63085756Smsmith 63167754Smsmith final Set<DataFlavor> returnValue = new LinkedHashSet<>(); 63267754Smsmith 63367754Smsmith String subType = null; 63482367Smsmith 63599146Siwasaki try { 63699146Siwasaki final MimeType mimeType = new MimeType(baseType); 63767754Smsmith subType = mimeType.getSubType(); 63891116Smsmith } catch (MimeTypeParseException mtpe) { 63967754Smsmith // Cannot happen, since we checked all mappings 64067754Smsmith // on load from flavormap.properties. 64167754Smsmith } 64267754Smsmith 64367754Smsmith if (DataFlavorUtil.doesSubtypeSupportCharset(subType, null)) { 64485756Smsmith if (TEXT_PLAIN_BASE_TYPE.equals(baseType)) 64585756Smsmith { 64685756Smsmith returnValue.add(DataFlavor.stringFlavor); 64785756Smsmith } 64885756Smsmith 64985756Smsmith for (String unicodeClassName : UNICODE_TEXT_CLASSES) { 65085756Smsmith final String mimeType = baseType + ";charset=Unicode;class=" + 65185756Smsmith unicodeClassName; 65267754Smsmith 65367754Smsmith final LinkedHashSet<String> mimeTypes = 65467754Smsmith handleHtmlMimeTypes(baseType, mimeType); 65567754Smsmith for (String mt : mimeTypes) { 65684491Smsmith DataFlavor toAdd = null; 65767754Smsmith try { 65899679Siwasaki toAdd = new DataFlavor(mt); 65999679Siwasaki } catch (ClassNotFoundException cannotHappen) { 66067754Smsmith } 661100966Siwasaki returnValue.add(toAdd); 662100966Siwasaki } 663100966Siwasaki } 664100966Siwasaki 665102550Siwasaki for (String charset : DataFlavorUtil.standardEncodings()) { 666102550Siwasaki 66767754Smsmith for (String encodedTextClass : ENCODED_TEXT_CLASSES) { 668107325Siwasaki final String mimeType = 669107325Siwasaki baseType + ";charset=" + charset + 670102550Siwasaki ";class=" + encodedTextClass; 671102550Siwasaki 672102550Siwasaki final LinkedHashSet<String> mimeTypes = 673102550Siwasaki handleHtmlMimeTypes(baseType, mimeType); 674102550Siwasaki 67567754Smsmith for (String mt : mimeTypes) { 67684491Smsmith 67767754Smsmith DataFlavor df = null; 67867754Smsmith 679102550Siwasaki try { 680102550Siwasaki df = new DataFlavor(mt); 681102550Siwasaki // Check for equality to plainTextFlavor so 682102550Siwasaki // that we can ensure that the exact charset of 683102550Siwasaki // plainTextFlavor, not the canonical charset 684102550Siwasaki // or another equivalent charset with a 685102550Siwasaki // different name, is used. 68667754Smsmith if (df.equals(DataFlavor.plainTextFlavor)) { 68767754Smsmith df = DataFlavor.plainTextFlavor; 68884491Smsmith } 68967754Smsmith } catch (ClassNotFoundException cannotHappen) { 69099679Siwasaki } 69199679Siwasaki 69299679Siwasaki returnValue.add(df); 69399679Siwasaki } 69499679Siwasaki } 69599679Siwasaki } 69699679Siwasaki 69799679Siwasaki if (TEXT_PLAIN_BASE_TYPE.equals(baseType)) 69867754Smsmith { 69999679Siwasaki returnValue.add(DataFlavor.plainTextFlavor); 70099679Siwasaki } 70199679Siwasaki } else { 70299679Siwasaki // Non-charset text natives should be treated as 70384491Smsmith // opaque, 8-bit data in any of its various 70499679Siwasaki // representations. 70599679Siwasaki for (String encodedTextClassName : ENCODED_TEXT_CLASSES) { 70699679Siwasaki DataFlavor toAdd = null; 70799679Siwasaki try { 70884491Smsmith toAdd = new DataFlavor(baseType + 70999679Siwasaki ";class=" + encodedTextClassName); 71099679Siwasaki } catch (ClassNotFoundException cannotHappen) { 71199679Siwasaki } 71299679Siwasaki returnValue.add(toAdd); 71399679Siwasaki } 71499679Siwasaki } 71587031Smsmith return returnValue; 71699679Siwasaki } 71799679Siwasaki 71899679Siwasaki private static final String [] htmlDocumentTypes = 71967754Smsmith new String [] {"all", "selection", "fragment"}; 72067754Smsmith 72199679Siwasaki private static LinkedHashSet<String> handleHtmlMimeTypes(String baseType, 72267754Smsmith String mimeType) { 72367754Smsmith 72499679Siwasaki LinkedHashSet<String> returnValues = new LinkedHashSet<>(); 72567754Smsmith 72683174Smsmith if (HTML_TEXT_BASE_TYPE.equals(baseType)) { 72783174Smsmith for (String documentType : htmlDocumentTypes) { 72883174Smsmith returnValues.add(mimeType + ";document=" + documentType); 72983174Smsmith } 730107325Siwasaki } else { 73183174Smsmith returnValues.add(mimeType); 73283174Smsmith } 73383174Smsmith 73483174Smsmith return returnValues; 73583174Smsmith } 73683174Smsmith 73783174Smsmith /** 73899679Siwasaki * Returns a <code>Map</code> of the specified <code>DataFlavor</code>s to 73999679Siwasaki * their most preferred <code>String</code> native. Each native value will 74067754Smsmith * be the same as the first native in the List returned by 74167754Smsmith * <code>getNativesForFlavor</code> for the specified flavor. 74267754Smsmith * <p> 74367754Smsmith * If a specified <code>DataFlavor</code> is previously unknown to the 74467754Smsmith * data transfer subsystem, then invoking this method will establish a 74567754Smsmith * mapping in both directions between the specified <code>DataFlavor</code> 74684491Smsmith * and an encoded version of its MIME type as its native. 74784491Smsmith * 74867754Smsmith * @param flavors an array of <code>DataFlavor</code>s which will be the 74967754Smsmith * key set of the returned <code>Map</code>. If <code>null</code> is 75067754Smsmith * specified, a mapping of all <code>DataFlavor</code>s known to the 75167754Smsmith * data transfer subsystem to their most preferred 75267754Smsmith * <code>String</code> natives will be returned. 75384491Smsmith * @return a <code>java.util.Map</code> of <code>DataFlavor</code>s to 75483174Smsmith * <code>String</code> natives 75567754Smsmith * 75670243Smsmith * @see #getNativesForFlavor 75767754Smsmith * @see #encodeDataFlavor 75867754Smsmith */ 75999679Siwasaki @Override 76099679Siwasaki public synchronized Map<DataFlavor,String> getNativesForFlavors(DataFlavor[] flavors) 76167754Smsmith { 76267754Smsmith // Use getNativesForFlavor to generate extra natives for text flavors 76367754Smsmith // and stringFlavor 76467754Smsmith 76567754Smsmith if (flavors == null) { 76667754Smsmith List<DataFlavor> flavor_list = getFlavorsForNative(null); 76767754Smsmith flavors = new DataFlavor[flavor_list.size()]; 76867754Smsmith flavor_list.toArray(flavors); 76967754Smsmith } 77067754Smsmith 77187031Smsmith Map<DataFlavor, String> retval = new HashMap<>(flavors.length, 1.0f); 77284491Smsmith for (DataFlavor flavor : flavors) { 77384491Smsmith List<String> natives = getNativesForFlavor(flavor); 77467754Smsmith String nat = (natives.isEmpty()) ? null : natives.get(0); 77567754Smsmith retval.put(flavor, nat); 77667754Smsmith } 77767754Smsmith 77867754Smsmith return retval; 77967754Smsmith } 78067754Smsmith 78167754Smsmith /** 78267754Smsmith * Returns a <code>Map</code> of the specified <code>String</code> natives 78367754Smsmith * to their most preferred <code>DataFlavor</code>. Each 78467754Smsmith * <code>DataFlavor</code> value will be the same as the first 78567754Smsmith * <code>DataFlavor</code> in the List returned by 78667754Smsmith * <code>getFlavorsForNative</code> for the specified native. 78767754Smsmith * <p> 78899679Siwasaki * If a specified native is previously unknown to the data transfer 78967754Smsmith * subsystem, and that native has been properly encoded, then invoking this 79084491Smsmith * method will establish a mapping in both directions between the specified 79167754Smsmith * native and a <code>DataFlavor</code> whose MIME type is a decoded 79282367Smsmith * version of the native. 793102550Siwasaki * 794102550Siwasaki * @param natives an array of <code>String</code>s which will be the 79599679Siwasaki * key set of the returned <code>Map</code>. If <code>null</code> is 79667754Smsmith * specified, a mapping of all supported <code>String</code> natives 79767754Smsmith * to their most preferred <code>DataFlavor</code>s will be 79867754Smsmith * returned. 79967754Smsmith * @return a <code>java.util.Map</code> of <code>String</code> natives to 80067754Smsmith * <code>DataFlavor</code>s 80167754Smsmith * 80284491Smsmith * @see #getFlavorsForNative 80367754Smsmith * @see #encodeJavaMIMEType 80484491Smsmith */ 80567754Smsmith @Override 80699146Siwasaki public synchronized Map<String,DataFlavor> getFlavorsForNatives(String[] natives) 80767754Smsmith { 80899679Siwasaki // Use getFlavorsForNative to generate extra flavors for text natives 80967754Smsmith if (natives == null) { 81067754Smsmith List<String> nativesList = getNativesForFlavor(null); 81167754Smsmith natives = new String[nativesList.size()]; 81267754Smsmith nativesList.toArray(natives); 81382367Smsmith } 81467754Smsmith 81567754Smsmith Map<String, DataFlavor> retval = new HashMap<>(natives.length, 1.0f); 81699146Siwasaki for (String aNative : natives) { 81767754Smsmith List<DataFlavor> flavors = getFlavorsForNative(aNative); 81867754Smsmith DataFlavor flav = (flavors.isEmpty())? null : flavors.get(0); 819107325Siwasaki retval.put(aNative, flav); 82067754Smsmith } 82167754Smsmith return retval; 82277424Smsmith } 82367754Smsmith 824107325Siwasaki /** 825102550Siwasaki * Adds a mapping from the specified <code>DataFlavor</code> (and all 826102550Siwasaki * <code>DataFlavor</code>s equal to the specified <code>DataFlavor</code>) 827107325Siwasaki * to the specified <code>String</code> native. 828102550Siwasaki * Unlike <code>getNativesForFlavor</code>, the mapping will only be 829102550Siwasaki * established in one direction, and the native will not be encoded. To 83084491Smsmith * establish a two-way mapping, call 83167754Smsmith * <code>addFlavorForUnencodedNative</code> as well. The new mapping will 83267754Smsmith * be of lower priority than any existing mapping. 83367754Smsmith * This method has no effect if a mapping from the specified or equal 83467754Smsmith * <code>DataFlavor</code> to the specified <code>String</code> native 83567754Smsmith * already exists. 83667754Smsmith * 83767754Smsmith * @param flav the <code>DataFlavor</code> key for the mapping 838114237Snjl * @param nat the <code>String</code> native value for the mapping 839107325Siwasaki * @throws NullPointerException if flav or nat is <code>null</code> 84067754Smsmith * 841102550Siwasaki * @see #addFlavorForUnencodedNative 84299679Siwasaki * @since 1.4 843107325Siwasaki */ 844107325Siwasaki public synchronized void addUnencodedNativeForFlavor(DataFlavor flav, 845102550Siwasaki String nat) { 846102550Siwasaki Objects.requireNonNull(nat, "Null native not permitted"); 847107325Siwasaki Objects.requireNonNull(flav, "Null flavor not permitted"); 848102550Siwasaki 849102550Siwasaki LinkedHashSet<String> natives = getFlavorToNative().get(flav); 85067754Smsmith if (natives == null) { 85167754Smsmith natives = new LinkedHashSet<>(1); 85299679Siwasaki getFlavorToNative().put(flav, natives); 85367754Smsmith } 85467754Smsmith natives.add(nat); 85584491Smsmith nativesForFlavorCache.remove(flav); 85667754Smsmith } 85767754Smsmith 85899679Siwasaki /** 85999146Siwasaki * Discards the current mappings for the specified <code>DataFlavor</code> 86099146Siwasaki * and all <code>DataFlavor</code>s equal to the specified 86167754Smsmith * <code>DataFlavor</code>, and creates new mappings to the 86299146Siwasaki * specified <code>String</code> natives. 86399146Siwasaki * Unlike <code>getNativesForFlavor</code>, the mappings will only be 86483174Smsmith * established in one direction, and the natives will not be encoded. To 86583174Smsmith * establish two-way mappings, call <code>setFlavorsForNative</code> 86683174Smsmith * as well. The first native in the array will represent the highest 86783174Smsmith * priority mapping. Subsequent natives will represent mappings of 86883174Smsmith * decreasing priority. 86999679Siwasaki * <p> 87099679Siwasaki * If the array contains several elements that reference equal 87183174Smsmith * <code>String</code> natives, this method will establish new mappings 87283174Smsmith * for the first of those elements and ignore the rest of them. 87383174Smsmith * <p> 87483174Smsmith * It is recommended that client code not reset mappings established by the 87583174Smsmith * data transfer subsystem. This method should only be used for 87683174Smsmith * application-level mappings. 87791116Smsmith * 87899146Siwasaki * @param flav the <code>DataFlavor</code> key for the mappings 87999146Siwasaki * @param natives the <code>String</code> native values for the mappings 88099146Siwasaki * @throws NullPointerException if flav or natives is <code>null</code> 88199146Siwasaki * or if natives contains <code>null</code> elements 88299146Siwasaki * 88399146Siwasaki * @see #setFlavorsForNative 88499679Siwasaki * @since 1.4 88599679Siwasaki */ 88699146Siwasaki public synchronized void setNativesForFlavor(DataFlavor flav, 88799146Siwasaki String[] natives) { 88899146Siwasaki Objects.requireNonNull(natives, "Null natives not permitted"); 889102550Siwasaki Objects.requireNonNull(flav, "Null flavors not permitted"); 89099146Siwasaki 89199146Siwasaki getFlavorToNative().remove(flav); 89299146Siwasaki for (String aNative : natives) { 89399679Siwasaki addUnencodedNativeForFlavor(flav, aNative); 89499679Siwasaki } 89599146Siwasaki disabledMappingGenerationKeys.add(flav); 89699146Siwasaki nativesForFlavorCache.remove(flav); 89799146Siwasaki } 89899146Siwasaki 89999146Siwasaki /** 90099146Siwasaki * Adds a mapping from a single <code>String</code> native to a single 90199146Siwasaki * <code>DataFlavor</code>. Unlike <code>getFlavorsForNative</code>, the 90299146Siwasaki * mapping will only be established in one direction, and the native will 90399146Siwasaki * not be encoded. To establish a two-way mapping, call 90499146Siwasaki * <code>addUnencodedNativeForFlavor</code> as well. The new mapping will 90591116Smsmith * be of lower priority than any existing mapping. 90691116Smsmith * This method has no effect if a mapping from the specified 90791116Smsmith * <code>String</code> native to the specified or equal 90891116Smsmith * <code>DataFlavor</code> already exists. 90999146Siwasaki * 91099679Siwasaki * @param nat the <code>String</code> native key for the mapping 91199679Siwasaki * @param flav the <code>DataFlavor</code> value for the mapping 91299679Siwasaki * @throws NullPointerException if nat or flav is <code>null</code> 91399679Siwasaki * 91491116Smsmith * @see #addUnencodedNativeForFlavor 91567754Smsmith * @since 1.4 91667754Smsmith */ 91767754Smsmith public synchronized void addFlavorForUnencodedNative(String nat, 91867754Smsmith DataFlavor flav) { 91991116Smsmith Objects.requireNonNull(nat, "Null native not permitted"); 92091116Smsmith Objects.requireNonNull(flav, "Null flavor not permitted"); 92191116Smsmith 92267754Smsmith LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat); 92391116Smsmith if (flavors == null) { 92467754Smsmith flavors = new LinkedHashSet<>(1); 925114237Snjl getNativeToFlavor().put(nat, flavors); 926107325Siwasaki } 92799679Siwasaki flavors.add(flav); 92899679Siwasaki flavorsForNativeCache.remove(nat); 92999679Siwasaki } 93099679Siwasaki 93191116Smsmith /** 93291116Smsmith * Discards the current mappings for the specified <code>String</code> 93391116Smsmith * native, and creates new mappings to the specified 93491116Smsmith * <code>DataFlavor</code>s. Unlike <code>getFlavorsForNative</code>, the 93591116Smsmith * mappings will only be established in one direction, and the natives need 93691116Smsmith * not be encoded. To establish two-way mappings, call 93799679Siwasaki * <code>setNativesForFlavor</code> as well. The first 93891116Smsmith * <code>DataFlavor</code> in the array will represent the highest priority 93991116Smsmith * mapping. Subsequent <code>DataFlavor</code>s will represent mappings of 94091116Smsmith * decreasing priority. 94167754Smsmith * <p> 94291116Smsmith * If the array contains several elements that reference equal 94367754Smsmith * <code>DataFlavor</code>s, this method will establish new mappings 94467754Smsmith * for the first of those elements and ignore the rest of them. 94599679Siwasaki * <p> 94667754Smsmith * It is recommended that client code not reset mappings established by the 94767754Smsmith * data transfer subsystem. This method should only be used for 94891116Smsmith * application-level mappings. 94991116Smsmith * 95091116Smsmith * @param nat the <code>String</code> native key for the mappings 95167754Smsmith * @param flavors the <code>DataFlavor</code> values for the mappings 95291116Smsmith * @throws NullPointerException if nat or flavors is <code>null</code> 95391116Smsmith * or if flavors contains <code>null</code> elements 95467754Smsmith * 95599679Siwasaki * @see #setNativesForFlavor 95667754Smsmith * @since 1.4 95791116Smsmith */ 95867754Smsmith public synchronized void setFlavorsForNative(String nat, 95991116Smsmith DataFlavor[] flavors) { 96091116Smsmith Objects.requireNonNull(nat, "Null native not permitted"); 96191116Smsmith Objects.requireNonNull(flavors, "Null flavors not permitted"); 96291116Smsmith 96391116Smsmith getNativeToFlavor().remove(nat); 96491116Smsmith for (DataFlavor flavor : flavors) { 96591116Smsmith addFlavorForUnencodedNative(nat, flavor); 96691116Smsmith } 96799679Siwasaki disabledMappingGenerationKeys.add(nat); 96891116Smsmith flavorsForNativeCache.remove(nat); 96967754Smsmith } 97091116Smsmith 97191116Smsmith /** 97291116Smsmith * Encodes a MIME type for use as a <code>String</code> native. The format 97391116Smsmith * of an encoded representation of a MIME type is implementation-dependent. 97491116Smsmith * The only restrictions are: 97599679Siwasaki * <ul> 97691116Smsmith * <li>The encoded representation is <code>null</code> if and only if the 97791116Smsmith * MIME type <code>String</code> is <code>null</code>.</li> 97891116Smsmith * <li>The encoded representations for two non-<code>null</code> MIME type 97991116Smsmith * <code>String</code>s are equal if and only if these <code>String</code>s 98067754Smsmith * are equal according to <code>String.equals(Object)</code>.</li> 98191116Smsmith * </ul> 98291116Smsmith * <p> 98367754Smsmith * The reference implementation of this method returns the specified MIME 98491116Smsmith * type <code>String</code> prefixed with <code>JAVA_DATAFLAVOR:</code>. 98567754Smsmith * 98667754Smsmith * @param mimeType the MIME type to encode 98767754Smsmith * @return the encoded <code>String</code>, or <code>null</code> if 98891116Smsmith * mimeType is <code>null</code> 98991116Smsmith */ 99091116Smsmith public static String encodeJavaMIMEType(String mimeType) { 99191116Smsmith return (mimeType != null) 99291116Smsmith ? JavaMIME + mimeType 99399679Siwasaki : null; 99491116Smsmith } 99599679Siwasaki 99699679Siwasaki /** 99791116Smsmith * Encodes a <code>DataFlavor</code> for use as a <code>String</code> 99891116Smsmith * native. The format of an encoded <code>DataFlavor</code> is 99991116Smsmith * implementation-dependent. The only restrictions are: 100091116Smsmith * <ul> 100191116Smsmith * <li>The encoded representation is <code>null</code> if and only if the 100291116Smsmith * specified <code>DataFlavor</code> is <code>null</code> or its MIME type 100391116Smsmith * <code>String</code> is <code>null</code>.</li> 100491116Smsmith * <li>The encoded representations for two non-<code>null</code> 100591116Smsmith * <code>DataFlavor</code>s with non-<code>null</code> MIME type 100667754Smsmith * <code>String</code>s are equal if and only if the MIME type 100791116Smsmith * <code>String</code>s of these <code>DataFlavor</code>s are equal 100867754Smsmith * according to <code>String.equals(Object)</code>.</li> 100991116Smsmith * </ul> 101091116Smsmith * <p> 101191116Smsmith * The reference implementation of this method returns the MIME type 101267754Smsmith * <code>String</code> of the specified <code>DataFlavor</code> prefixed 101367754Smsmith * with <code>JAVA_DATAFLAVOR:</code>. 101491116Smsmith * 101567754Smsmith * @param flav the <code>DataFlavor</code> to encode 1016114237Snjl * @return the encoded <code>String</code>, or <code>null</code> if 1017107325Siwasaki * flav is <code>null</code> or has a <code>null</code> MIME type 101869746Smsmith */ 1019102550Siwasaki public static String encodeDataFlavor(DataFlavor flav) { 1020102550Siwasaki return (flav != null) 1021102550Siwasaki ? SystemFlavorMap.encodeJavaMIMEType(flav.getMimeType()) 1022102550Siwasaki : null; 1023102550Siwasaki } 102469746Smsmith 1025102550Siwasaki /** 1026102550Siwasaki * Returns whether the specified <code>String</code> is an encoded Java 102769746Smsmith * MIME type. 1028102550Siwasaki * 1029102550Siwasaki * @param str the <code>String</code> to test 1030102550Siwasaki * @return <code>true</code> if the <code>String</code> is encoded; 103191116Smsmith * <code>false</code> otherwise 103291116Smsmith */ 103367754Smsmith public static boolean isJavaMIMEType(String str) { 103469746Smsmith return (str != null && str.startsWith(JavaMIME, 0)); 103591116Smsmith } 103691116Smsmith 103769746Smsmith /** 103891116Smsmith * Decodes a <code>String</code> native for use as a Java MIME type. 103991116Smsmith * 104099679Siwasaki * @param nat the <code>String</code> to decode 104191116Smsmith * @return the decoded Java MIME type, or <code>null</code> if nat is not 1042114237Snjl * an encoded <code>String</code> native 1043107325Siwasaki */ 104491116Smsmith public static String decodeJavaMIMEType(String nat) { 104567754Smsmith return (isJavaMIMEType(nat)) 104691116Smsmith ? nat.substring(JavaMIME.length(), nat.length()).trim() 104784491Smsmith : null; 104891116Smsmith } 104999679Siwasaki 105099679Siwasaki /** 105169746Smsmith * Decodes a <code>String</code> native for use as a 105291116Smsmith * <code>DataFlavor</code>. 105391116Smsmith * 105467754Smsmith * @param nat the <code>String</code> to decode 105591116Smsmith * @return the decoded <code>DataFlavor</code>, or <code>null</code> if 105691116Smsmith * nat is not an encoded <code>String</code> native 105769746Smsmith * @throws ClassNotFoundException if the class of the data flavor 105891116Smsmith * is not loaded 105991116Smsmith */ 106069746Smsmith public static DataFlavor decodeDataFlavor(String nat) 106167754Smsmith throws ClassNotFoundException 106291116Smsmith { 106391116Smsmith String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat); 106491116Smsmith return (retval_str != null) 106591116Smsmith ? new DataFlavor(retval_str) 106691116Smsmith : null; 106791116Smsmith } 106891116Smsmith 106991116Smsmith private static final class SoftCache<K, V> { 107067754Smsmith Map<K, SoftReference<LinkedHashSet<V>>> cache; 107191116Smsmith 107291116Smsmith public void put(K key, LinkedHashSet<V> value) { 1073114237Snjl if (cache == null) { 1074107325Siwasaki cache = new HashMap<>(1); 107567754Smsmith } 107691116Smsmith cache.put(key, new SoftReference<>(value)); 107767754Smsmith } 107891116Smsmith 107967754Smsmith public void remove(K key) { 108069746Smsmith if (cache == null) return; 108191116Smsmith cache.remove(null); 108269746Smsmith cache.remove(key); 108391116Smsmith } 108491116Smsmith 108591116Smsmith public LinkedHashSet<V> check(K key) { 108667754Smsmith if (cache == null) return null; 108791116Smsmith SoftReference<LinkedHashSet<V>> ref = cache.get(key); 108867754Smsmith if (ref != null) { 1089114237Snjl return ref.get(); 1090107325Siwasaki } 109167754Smsmith return null; 109291116Smsmith } 109367754Smsmith } 109467754Smsmith} 109591116Smsmith