/* * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ 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 DataFlavor
s 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 DataFlavor
s
* should be returned. If null
is specified, all
* DataFlavor
s 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 DataFlavor
s 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 DataFlavor
s which will be the
* key set of the returned Map
. If null
is
* specified, a mapping of all DataFlavor
s known to the
* data transfer subsystem to their most preferred
* String
natives will be returned.
* @return a java.util.Map
of DataFlavor
s 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 String
s 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 DataFlavor
s will be
* returned.
* @return a java.util.Map
of String
natives to
* DataFlavor
s
*
* @see #getFlavorsForNative
* @see #encodeJavaMIMEType
*/
public synchronized MapDataFlavor
(and all
* DataFlavor
s 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 DataFlavor
s 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
* DataFlavor
s. 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 DataFlavor
s will represent mappings of
* decreasing priority.
* DataFlavor
s, 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
* String
s are equal if and only if these String
s
* 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
* DataFlavor
s with non-null
MIME type
* String
s are equal if and only if the MIME type
* String
s of these DataFlavor
s 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;
}
}