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