/* * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package java.awt.datatransfer; import java.awt.Toolkit; import java.lang.ref.SoftReference; import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader; import java.io.IOException; import java.net.URL; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import sun.awt.datatransfer.DataTransferer; /** * The SystemFlavorMap is a configurable map between "natives" (Strings), which * correspond to platform-specific data formats, and "flavors" (DataFlavors), * which correspond to platform-independent MIME types. This mapping is used * by the data transfer subsystem to transfer data between Java and native * applications, and between Java applications in separate VMs. *
* In the Sun reference implementation, the default SystemFlavorMap is
* initialized by the file
* If the specified
* If the specified native is previously unknown to the data transfer
* subsystem, and that native has been properly encoded, then invoking this
* method will establish a mapping in both directions between the specified
* native and a
* If the specified native is not a properly encoded native and the
* mappings for this native have not been altered with
*
* If a specified
* If a specified native is previously unknown to the data transfer
* subsystem, and that native has been properly encoded, then invoking this
* method will establish a mapping in both directions between the specified
* native and a
* If the array contains several elements that reference equal
*
* It is recommended that client code not reset mappings established by the
* data transfer subsystem. This method should only be used for
* application-level mappings.
*
* @param flav the
* If the array contains several elements that reference equal
*
* It is recommended that client code not reset mappings established by the
* data transfer subsystem. This method should only be used for
* application-level mappings.
*
* @param nat the
* Sun's reference implementation of this method returns the specified MIME
* type
* Sun's reference implementation of this method returns the MIME type
* jre/lib/flavormap.properties and the
* contents of the URL referenced by the AWT property
* AWT.DnD.flavorMapFileURL. See flavormap.properties
* for details.
*
* @since 1.2
*/
public final class SystemFlavorMap implements FlavorMap, FlavorTable {
/**
* Constant prefix used to tag Java types converted to native platform
* type.
*/
private static String JavaMIME = "JAVA_DATAFLAVOR:";
/**
* System singleton which maps a thread's ClassLoader to a SystemFlavorMap.
*/
private static final WeakHashMap flavorMaps = new WeakHashMap();
/**
* Copied from java.util.Properties.
*/
private static final String keyValueSeparators = "=: \t\r\n\f";
private static final String strictKeyValueSeparators = "=:";
private static final String whiteSpaceChars = " \t\r\n\f";
/**
* The list of valid, decoded text flavor representation classes, in order
* from best to worst.
*/
private static final String[] UNICODE_TEXT_CLASSES = {
"java.io.Reader", "java.lang.String", "java.nio.CharBuffer", "\"[C\""
};
/**
* The list of valid, encoded text flavor representation classes, in order
* from best to worst.
*/
private static final String[] ENCODED_TEXT_CLASSES = {
"java.io.InputStream", "java.nio.ByteBuffer", "\"[B\""
};
/**
* A String representing text/plain MIME type.
*/
private static final String TEXT_PLAIN_BASE_TYPE = "text/plain";
/**
* This constant is passed to flavorToNativeLookup() to indicate that a
* a native should be synthesized, stored, and returned by encoding the
* DataFlavor's MIME type in case if the DataFlavor is not found in
* 'flavorToNative' map.
*/
private static final boolean SYNTHESIZE_IF_NOT_FOUND = true;
/**
* Maps native Strings to Lists of DataFlavors (or base type Strings for
* text DataFlavors).
* Do not use the field directly, use getNativeToFlavor() instead.
*/
private Map nativeToFlavor = new HashMap();
/**
* Accessor to nativeToFlavor map. Since we use lazy initialization we must
* use this accessor instead of direct access to the field which may not be
* initialized yet. This method will initialize the field if needed.
*
* @return nativeToFlavor
*/
private Map getNativeToFlavor() {
if (!isMapInitialized) {
initSystemFlavorMap();
}
return nativeToFlavor;
}
/**
* Maps DataFlavors (or base type Strings for text DataFlavors) to Lists of
* native Strings.
* Do not use the field directly, use getFlavorToNative() instead.
*/
private Map flavorToNative = new HashMap();
/**
* Accessor to flavorToNative map. Since we use lazy initialization we must
* use this accessor instead of direct access to the field which may not be
* initialized yet. This method will initialize the field if needed.
*
* @return flavorToNative
*/
private synchronized Map getFlavorToNative() {
if (!isMapInitialized) {
initSystemFlavorMap();
}
return flavorToNative;
}
/**
* Shows if the object has been initialized.
*/
private boolean isMapInitialized = false;
/**
* Caches the result of getNativesForFlavor(). Maps DataFlavors to
* SoftReferences which reference Lists of String natives.
*/
private Map getNativesForFlavorCache = new HashMap();
/**
* Caches the result getFlavorsForNative(). Maps String natives to
* SoftReferences which reference Lists of DataFlavors.
*/
private Map getFlavorsForNativeCache = new HashMap();
/**
* Dynamic mapping generation used for text mappings should not be applied
* to the DataFlavors and String natives for which the mappings have been
* explicitly specified with setFlavorsForNative() or
* setNativesForFlavor(). This keeps all such keys.
*/
private Set disabledMappingGenerationKeys = new HashSet();
/**
* Returns the default FlavorMap for this thread's ClassLoader.
*/
public static FlavorMap getDefaultFlavorMap() {
ClassLoader contextClassLoader =
Thread.currentThread().getContextClassLoader();
if (contextClassLoader == null) {
contextClassLoader = ClassLoader.getSystemClassLoader();
}
FlavorMap fm;
synchronized(flavorMaps) {
fm = (FlavorMap)flavorMaps.get(contextClassLoader);
if (fm == null) {
fm = new SystemFlavorMap();
flavorMaps.put(contextClassLoader, fm);
}
}
return fm;
}
private SystemFlavorMap() {
}
/**
* Initializes a SystemFlavorMap by reading flavormap.properties and
* AWT.DnD.flavorMapFileURL.
* For thread-safety must be called under lock on this.
*/
private void initSystemFlavorMap() {
if (isMapInitialized) {
return;
}
isMapInitialized = true;
BufferedReader flavormapDotProperties =
java.security.AccessController.doPrivileged(
new java.security.PrivilegedActionList of String natives to which the
* specified DataFlavor can be translated by the data transfer
* subsystem. The List will be sorted from best native to
* worst. That is, the first native will best reflect data in the specified
* flavor to the underlying native platform.
* DataFlavor is previously unknown to the
* data transfer subsystem and the data transfer subsystem is unable to
* translate this DataFlavor to any existing native, then
* invoking this method will establish a
* mapping in both directions between the specified DataFlavor
* and an encoded version of its MIME type as its native.
*
* @param flav the DataFlavor whose corresponding natives
* should be returned. If null is specified, all
* natives currently known to the data transfer subsystem are
* returned in a non-deterministic order.
* @return a java.util.List of java.lang.String
* objects which are platform-specific representations of platform-
* specific data formats
*
* @see #encodeDataFlavor
* @since 1.4
*/
public synchronized ListList of DataFlavors to which the
* specified String native can be translated by the data
* transfer subsystem. The List will be sorted from best
* DataFlavor to worst. That is, the first
* DataFlavor will best reflect data in the specified
* native to a Java application.
* DataFlavor whose MIME type is a decoded
* version of the native.
* setFlavorsForNative, then the contents of the
* List is platform dependent, but null
* cannot be returned.
*
* @param nat the native whose corresponding DataFlavors
* should be returned. If null is specified, all
* DataFlavors currently known to the data transfer
* subsystem are returned in a non-deterministic order.
* @return a java.util.List of DataFlavor
* objects into which platform-specific data in the specified,
* platform-specific native can be translated
*
* @see #encodeJavaMIMEType
* @since 1.4
*/
public synchronized ListMap of the specified DataFlavors to
* their most preferred String native. Each native value will
* be the same as the first native in the List returned by
* getNativesForFlavor for the specified flavor.
* DataFlavor is previously unknown to the
* data transfer subsystem, then invoking this method will establish a
* mapping in both directions between the specified DataFlavor
* and an encoded version of its MIME type as its native.
*
* @param flavors an array of DataFlavors which will be the
* key set of the returned Map. If null is
* specified, a mapping of all DataFlavors known to the
* data transfer subsystem to their most preferred
* String natives will be returned.
* @return a java.util.Map of DataFlavors to
* String natives
*
* @see #getNativesForFlavor
* @see #encodeDataFlavor
*/
public synchronized MapMap of the specified String natives
* to their most preferred DataFlavor. Each
* DataFlavor value will be the same as the first
* DataFlavor in the List returned by
* getFlavorsForNative for the specified native.
* DataFlavor whose MIME type is a decoded
* version of the native.
*
* @param natives an array of Strings which will be the
* key set of the returned Map. If null is
* specified, a mapping of all supported String natives
* to their most preferred DataFlavors will be
* returned.
* @return a java.util.Map of String natives to
* DataFlavors
*
* @see #getFlavorsForNative
* @see #encodeJavaMIMEType
*/
public synchronized MapDataFlavor (and all
* DataFlavors equal to the specified DataFlavor)
* to the specified String native.
* Unlike getNativesForFlavor, the mapping will only be
* established in one direction, and the native will not be encoded. To
* establish a two-way mapping, call
* addFlavorForUnencodedNative as well. The new mapping will
* be of lower priority than any existing mapping.
* This method has no effect if a mapping from the specified or equal
* DataFlavor to the specified String native
* already exists.
*
* @param flav the DataFlavor key for the mapping
* @param nat the String native value for the mapping
* @throws NullPointerException if flav or nat is null
*
* @see #addFlavorForUnencodedNative
* @since 1.4
*/
public synchronized void addUnencodedNativeForFlavor(DataFlavor flav,
String nat) {
if (flav == null || nat == null) {
throw new NullPointerException("null arguments not permitted");
}
List natives = (List)getFlavorToNative().get(flav);
if (natives == null) {
natives = new ArrayList(1);
getFlavorToNative().put(flav, natives);
} else if (natives.contains(nat)) {
return;
}
natives.add(nat);
getNativesForFlavorCache.remove(flav);
getNativesForFlavorCache.remove(null);
}
/**
* Discards the current mappings for the specified DataFlavor
* and all DataFlavors equal to the specified
* DataFlavor, and creates new mappings to the
* specified String natives.
* Unlike getNativesForFlavor, the mappings will only be
* established in one direction, and the natives will not be encoded. To
* establish two-way mappings, call setFlavorsForNative
* as well. The first native in the array will represent the highest
* priority mapping. Subsequent natives will represent mappings of
* decreasing priority.
* String natives, this method will establish new mappings
* for the first of those elements and ignore the rest of them.
* DataFlavor key for the mappings
* @param natives the String native values for the mappings
* @throws NullPointerException if flav or natives is null
* or if natives contains null elements
*
* @see #setFlavorsForNative
* @since 1.4
*/
public synchronized void setNativesForFlavor(DataFlavor flav,
String[] natives) {
if (flav == null || natives == null) {
throw new NullPointerException("null arguments not permitted");
}
getFlavorToNative().remove(flav);
for (int i = 0; i < natives.length; i++) {
addUnencodedNativeForFlavor(flav, natives[i]);
}
disabledMappingGenerationKeys.add(flav);
// Clear the cache to handle the case of empty natives.
getNativesForFlavorCache.remove(flav);
getNativesForFlavorCache.remove(null);
}
/**
* Adds a mapping from a single String native to a single
* DataFlavor. Unlike getFlavorsForNative, the
* mapping will only be established in one direction, and the native will
* not be encoded. To establish a two-way mapping, call
* addUnencodedNativeForFlavor as well. The new mapping will
* be of lower priority than any existing mapping.
* This method has no effect if a mapping from the specified
* String native to the specified or equal
* DataFlavor already exists.
*
* @param nat the String native key for the mapping
* @param flav the DataFlavor value for the mapping
* @throws NullPointerException if nat or flav is null
*
* @see #addUnencodedNativeForFlavor
* @since 1.4
*/
public synchronized void addFlavorForUnencodedNative(String nat,
DataFlavor flav) {
if (nat == null || flav == null) {
throw new NullPointerException("null arguments not permitted");
}
List flavors = (List)getNativeToFlavor().get(nat);
if (flavors == null) {
flavors = new ArrayList(1);
getNativeToFlavor().put(nat, flavors);
} else if (flavors.contains(flav)) {
return;
}
flavors.add(flav);
getFlavorsForNativeCache.remove(nat);
getFlavorsForNativeCache.remove(null);
}
/**
* Discards the current mappings for the specified String
* native, and creates new mappings to the specified
* DataFlavors. Unlike getFlavorsForNative, the
* mappings will only be established in one direction, and the natives need
* not be encoded. To establish two-way mappings, call
* setNativesForFlavor as well. The first
* DataFlavor in the array will represent the highest priority
* mapping. Subsequent DataFlavors will represent mappings of
* decreasing priority.
* DataFlavors, this method will establish new mappings
* for the first of those elements and ignore the rest of them.
* String native key for the mappings
* @param flavors the DataFlavor values for the mappings
* @throws NullPointerException if nat or flavors is null
* or if flavors contains null elements
*
* @see #setNativesForFlavor
* @since 1.4
*/
public synchronized void setFlavorsForNative(String nat,
DataFlavor[] flavors) {
if (nat == null || flavors == null) {
throw new NullPointerException("null arguments not permitted");
}
getNativeToFlavor().remove(nat);
for (int i = 0; i < flavors.length; i++) {
addFlavorForUnencodedNative(nat, flavors[i]);
}
disabledMappingGenerationKeys.add(nat);
// Clear the cache to handle the case of empty flavors.
getFlavorsForNativeCache.remove(nat);
getFlavorsForNativeCache.remove(null);
}
/**
* Encodes a MIME type for use as a String native. The format
* of an encoded representation of a MIME type is implementation-dependent.
* The only restrictions are:
*
*
* null if and only if the
* MIME type String is null.null MIME type
* Strings are equal if and only if these Strings
* are equal according to String.equals(Object).String prefixed with JAVA_DATAFLAVOR:.
*
* @param mimeType the MIME type to encode
* @return the encoded String, or null if
* mimeType is null
*/
public static String encodeJavaMIMEType(String mimeType) {
return (mimeType != null)
? JavaMIME + mimeType
: null;
}
/**
* Encodes a DataFlavor for use as a String
* native. The format of an encoded DataFlavor is
* implementation-dependent. The only restrictions are:
*
*
* null if and only if the
* specified DataFlavor is null or its MIME type
* String is null.null
* DataFlavors with non-null MIME type
* Strings are equal if and only if the MIME type
* Strings of these DataFlavors are equal
* according to String.equals(Object).String of the specified DataFlavor prefixed
* with JAVA_DATAFLAVOR:.
*
* @param flav the DataFlavor to encode
* @return the encoded String, or null if
* flav is null or has a null MIME type
*/
public static String encodeDataFlavor(DataFlavor flav) {
return (flav != null)
? SystemFlavorMap.encodeJavaMIMEType(flav.getMimeType())
: null;
}
/**
* Returns whether the specified String is an encoded Java
* MIME type.
*
* @param str the String to test
* @return true if the String is encoded;
* false otherwise
*/
public static boolean isJavaMIMEType(String str) {
return (str != null && str.startsWith(JavaMIME, 0));
}
/**
* Decodes a String native for use as a Java MIME type.
*
* @param nat the String to decode
* @return the decoded Java MIME type, or null if nat is not
* an encoded String native
*/
public static String decodeJavaMIMEType(String nat) {
return (isJavaMIMEType(nat))
? nat.substring(JavaMIME.length(), nat.length()).trim()
: null;
}
/**
* Decodes a String native for use as a
* DataFlavor.
*
* @param nat the String to decode
* @return the decoded DataFlavor, or null if
* nat is not an encoded String native
*/
public static DataFlavor decodeDataFlavor(String nat)
throws ClassNotFoundException
{
String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat);
return (retval_str != null)
? new DataFlavor(retval_str)
: null;
}
}