mirror of
https://github.com/2003scape/deep-c-rsc.git
synced 2024-03-22 05:49:51 -04:00
2760 lines
105 KiB
Java
2760 lines
105 KiB
Java
/*
|
|
* This file is modified by Ivan Maidanski <ivmai@ivmaisoft.com>
|
|
* Project name: JCGO-SUNAWT (http://www.ivmaisoft.com/jcgo/)
|
|
*/
|
|
|
|
/*
|
|
* @(#)DataTransferer.java 1.28 03/01/23
|
|
*
|
|
* Copyright 2003 Sun Microsystems, Inc. All rights reserved.
|
|
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*/
|
|
|
|
package sun.awt.datatransfer;
|
|
|
|
import java.awt.AWTError;
|
|
import java.awt.EventQueue;
|
|
import java.awt.Image;
|
|
import java.awt.Graphics;
|
|
import java.awt.Toolkit;
|
|
|
|
import java.awt.datatransfer.DataFlavor;
|
|
import java.awt.datatransfer.FlavorMap;
|
|
import java.awt.datatransfer.FlavorTable;
|
|
import java.awt.datatransfer.StringSelection;
|
|
import java.awt.datatransfer.Transferable;
|
|
import java.awt.datatransfer.UnsupportedFlavorException;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.File;
|
|
import java.io.FilePermission;
|
|
import java.io.InputStream;
|
|
import java.io.InputStreamReader;
|
|
import java.io.IOException;
|
|
import java.io.ObjectInputStream;
|
|
import java.io.ObjectOutputStream;
|
|
import java.io.Reader;
|
|
import java.io.SequenceInputStream;
|
|
import java.io.StringReader;
|
|
import java.io.UnsupportedEncodingException;
|
|
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.CharBuffer;
|
|
import java.nio.charset.Charset;
|
|
import java.nio.charset.CharsetEncoder;
|
|
import java.nio.charset.IllegalCharsetNameException;
|
|
import java.nio.charset.UnsupportedCharsetException;
|
|
|
|
import java.lang.reflect.Constructor;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.lang.reflect.Modifier;
|
|
|
|
import java.rmi.MarshalledObject;
|
|
|
|
import java.security.AccessController;
|
|
import java.security.PrivilegedAction;
|
|
import java.security.PrivilegedActionException;
|
|
import java.security.PrivilegedExceptionAction;
|
|
import java.security.ProtectionDomain;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.SortedSet;
|
|
import java.util.Set;
|
|
import java.util.Stack;
|
|
import java.util.TreeSet;
|
|
|
|
import sun.awt.AppContext;
|
|
import sun.awt.DebugHelper;
|
|
import sun.awt.SunToolkit;
|
|
|
|
import java.awt.image.BufferedImage;
|
|
import java.awt.image.ImageObserver;
|
|
import java.awt.image.RenderedImage;
|
|
import java.awt.image.WritableRaster;
|
|
import java.awt.image.ColorModel;
|
|
|
|
import javax.imageio.ImageIO;
|
|
import javax.imageio.ImageReader;
|
|
import javax.imageio.ImageReadParam;
|
|
import javax.imageio.ImageWriter;
|
|
import javax.imageio.ImageTypeSpecifier;
|
|
|
|
import javax.imageio.spi.ImageWriterSpi;
|
|
|
|
import javax.imageio.stream.ImageInputStream;
|
|
import javax.imageio.stream.ImageOutputStream;
|
|
|
|
import sun.awt.image.ImageRepresentation;
|
|
|
|
// TODO
|
|
//
|
|
// Remove import
|
|
import sun.io.CharToByteConverter;
|
|
|
|
|
|
/**
|
|
* Provides a set of functions to be shared among the DataFlavor class and
|
|
* platform-specific data transfer implementations.
|
|
*
|
|
* The concept of "flavors" and "natives" is extended to include "formats",
|
|
* which are the numeric values Win32 and X11 use to express particular data
|
|
* types. Like FlavorMap, which provides getNativesForFlavors(DataFlavor[]) and
|
|
* getFlavorsForNatives(String[]) functions, DataTransferer provides a set
|
|
* of getFormatsFor(Transferable|Flavor|Flavors) and
|
|
* getFlavorsFor(Format|Formats) functions.
|
|
*
|
|
* Also provided are functions for translating a Transferable into a byte
|
|
* array, given a source DataFlavor and a target format, and for translating
|
|
* a byte array or InputStream into an Object, given a source format and
|
|
* a target DataFlavor.
|
|
*
|
|
* @author David Mendenhall
|
|
* @author Danila Sinopalnikov
|
|
* @version 1.28, 01/23/03
|
|
*
|
|
* @since 1.3.1
|
|
*/
|
|
public abstract class DataTransferer {
|
|
|
|
/**
|
|
* Cached value of Class.forName("[C");
|
|
*/
|
|
public static final Class charArrayClass = char[].class;
|
|
|
|
/**
|
|
* Cached value of Class.forName("[B");
|
|
*/
|
|
public static final Class byteArrayClass = byte[].class;
|
|
|
|
/**
|
|
* The <code>DataFlavor</code> representing plain text with Unicode
|
|
* encoding, where:
|
|
* <pre>
|
|
* representationClass = java.lang.String
|
|
* mimeType = "text/plain; charset=Unicode"
|
|
* </pre>
|
|
*/
|
|
public static final DataFlavor plainTextStringFlavor;
|
|
|
|
/**
|
|
* The <code>DataFlavor</code> representing a Java text encoding String
|
|
* encoded in UTF-8, where
|
|
* <pre>
|
|
* representationClass = [B
|
|
* mimeType = "application/x-java-text-encoding"
|
|
* </pre>
|
|
*/
|
|
public static final DataFlavor javaTextEncodingFlavor;
|
|
|
|
private static SortedSet standardEncodings;
|
|
|
|
/**
|
|
* Encodings that we know are or are not supported go in this Map. It's
|
|
* much faster than actually encoding some text.
|
|
*/
|
|
// TODO
|
|
//
|
|
// Eliminate this field in conjunction with isEncodingSupported(String)
|
|
private static final Map knownEncodings;
|
|
|
|
/**
|
|
* Tracks whether a particular text/* MIME type supports the charset
|
|
* parameter. The Map is initialized with all of the standard MIME types
|
|
* listed in the DataFlavor.selectBestTextFlavor method comment. Additional
|
|
* entries may be added during the life of the JRE for text/<other> types.
|
|
*/
|
|
private static final Map textMIMESubtypeCharsetSupport;
|
|
|
|
/**
|
|
* Cache of the platform default encoding as specified in the
|
|
* "file.encoding" system property.
|
|
*/
|
|
private static String defaultEncoding;
|
|
|
|
/**
|
|
* A collection of all natives listed in flavormap.properties with
|
|
* a primary MIME type of "text".
|
|
*/
|
|
private static final Set textNatives =
|
|
Collections.synchronizedSet(new HashSet());
|
|
|
|
/**
|
|
* The native encodings/charsets for the Set of textNatives.
|
|
*/
|
|
private static final Map nativeCharsets =
|
|
Collections.synchronizedMap(new HashMap());
|
|
|
|
/**
|
|
* The end-of-line markers for the Set of textNatives.
|
|
*/
|
|
private static final Map nativeEOLNs =
|
|
Collections.synchronizedMap(new HashMap());
|
|
|
|
/**
|
|
* The number of terminating NUL bytes for the Set of textNatives.
|
|
*/
|
|
private static final Map nativeTerminators =
|
|
Collections.synchronizedMap(new HashMap());
|
|
|
|
/**
|
|
* The key used to store pending data conversion requests for an AppContext.
|
|
*/
|
|
private static final String DATA_CONVERTER_KEY = "DATA_CONVERTER_KEY";
|
|
|
|
/**
|
|
* The singleton DataTransferer instance. It is created during MToolkit
|
|
* or WToolkit initialization.
|
|
*/
|
|
private static DataTransferer transferer;
|
|
|
|
private static final String DEPLOYMENT_CACHE_PROPERTIES[] = {
|
|
"deployment.system.cachedir", "deployment.user.cachedir",
|
|
"deployment.javaws.cachedir", "deployment.javapi.cachedir"
|
|
};
|
|
|
|
private static final ArrayList deploymentCacheDirectoryList =
|
|
new ArrayList();
|
|
|
|
static {
|
|
DataFlavor tPlainTextStringFlavor = null;
|
|
try {
|
|
tPlainTextStringFlavor = new DataFlavor
|
|
("text/plain;charset=Unicode;class=java.lang.String");
|
|
} catch (ClassNotFoundException cannotHappen) {
|
|
throw new InternalError();
|
|
}
|
|
plainTextStringFlavor = tPlainTextStringFlavor;
|
|
|
|
DataFlavor tJavaTextEncodingFlavor = null;
|
|
try {
|
|
tJavaTextEncodingFlavor = new DataFlavor
|
|
("application/x-java-text-encoding;class=\"[B\"");
|
|
} catch (ClassNotFoundException cannotHappen) {
|
|
throw new InternalError();
|
|
}
|
|
javaTextEncodingFlavor = tJavaTextEncodingFlavor;
|
|
|
|
// TODO
|
|
//
|
|
// Eliminate this code in conjunction with isEncodingSupported(String)
|
|
Map tempMap = new HashMap(11);
|
|
tempMap.put("ASCII", Boolean.TRUE); // US-ASCII
|
|
tempMap.put("ISO8859_1", Boolean.TRUE); // ISO-8859-1
|
|
tempMap.put("UTF8", Boolean.TRUE); // UTF-8
|
|
tempMap.put("UnicodeBigUnmarked", Boolean.TRUE); // UTF-16BE
|
|
tempMap.put("UnicodeLittleUnmarked", Boolean.TRUE); // UTF-16LE
|
|
tempMap.put("UTF16", Boolean.TRUE); // UTF-16
|
|
knownEncodings = Collections.synchronizedMap(tempMap);
|
|
|
|
tempMap = new HashMap(17);
|
|
tempMap.put("sgml", Boolean.TRUE);
|
|
tempMap.put("xml", Boolean.TRUE);
|
|
tempMap.put("html", Boolean.TRUE);
|
|
tempMap.put("enriched", Boolean.TRUE);
|
|
tempMap.put("richtext", Boolean.TRUE);
|
|
tempMap.put("uri-list", Boolean.TRUE);
|
|
tempMap.put("directory", Boolean.TRUE);
|
|
tempMap.put("css", Boolean.TRUE);
|
|
tempMap.put("calendar", Boolean.TRUE);
|
|
tempMap.put("plain", Boolean.TRUE);
|
|
tempMap.put("rtf", Boolean.FALSE);
|
|
tempMap.put("tab-separated-values", Boolean.FALSE);
|
|
tempMap.put("t140", Boolean.FALSE);
|
|
tempMap.put("rfc822-headers", Boolean.FALSE);
|
|
tempMap.put("parityfec", Boolean.FALSE);
|
|
textMIMESubtypeCharsetSupport = Collections.synchronizedMap(tempMap);
|
|
}
|
|
|
|
/**
|
|
* The accessor method for the singleton DataTransferer instance. Note
|
|
* that in a headless environment, there may be no DataTransferer instance;
|
|
* instead, null will be returned.
|
|
*/
|
|
public static DataTransferer getInstance() {
|
|
if (transferer == null) {
|
|
synchronized (DataTransferer.class) {
|
|
if (transferer == null) {
|
|
final String name = SunToolkit.
|
|
getDataTransfererClassName();
|
|
if (name != null) {
|
|
PrivilegedAction action = new PrivilegedAction() {
|
|
public Object run() {
|
|
Class cls = null;
|
|
Method method = null;
|
|
Object ret = null;
|
|
|
|
try {
|
|
cls = Class.forName(name);
|
|
} catch (ClassNotFoundException e) {
|
|
ClassLoader cl = ClassLoader.
|
|
getSystemClassLoader();
|
|
if (cl != null) {
|
|
try {
|
|
cls = cl.loadClass(name);
|
|
} catch (ClassNotFoundException ee) {
|
|
ee.printStackTrace();
|
|
throw new AWTError("DataTransferer not found: " + name);
|
|
}
|
|
}
|
|
}
|
|
if (cls != null) {
|
|
try {
|
|
method = cls.getMethod
|
|
("getInstanceImpl", null);
|
|
} catch (NoSuchMethodException e) {
|
|
e.printStackTrace();
|
|
throw new AWTError("Cannot instantiate DataTransferer: " + name);
|
|
}
|
|
}
|
|
if (method != null) {
|
|
try {
|
|
ret = method.invoke(null, null);
|
|
} catch (InvocationTargetException e) {
|
|
e.printStackTrace();
|
|
throw new AWTError("Cannot instantiate DataTransferer: " + name);
|
|
} catch (IllegalAccessException e) {
|
|
e.printStackTrace();
|
|
throw new AWTError("Cannot access DataTransferer: " + name);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
};
|
|
transferer = (DataTransferer)
|
|
AccessController.doPrivileged(action);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return transferer;
|
|
}
|
|
|
|
/**
|
|
* Converts an arbitrary text encoding to its canonical name.
|
|
*/
|
|
public static String canonicalName(String encoding) {
|
|
if (encoding == null) {
|
|
return null;
|
|
}
|
|
|
|
// TODO
|
|
//
|
|
// Don't use java.nio API until alias list is equivalent to
|
|
// CharacterEncoding.aliasName(String).
|
|
//
|
|
// String canonicalName = Charset.forName(encoding).toString();
|
|
String canonicalName = sun.io.CharacterEncoding.aliasName(encoding);
|
|
if (canonicalName == null) {
|
|
canonicalName = encoding;
|
|
}
|
|
return canonicalName;
|
|
}
|
|
|
|
/**
|
|
* If the specified flavor is a text flavor which supports the "charset"
|
|
* parameter, then this method returns that parameter, or the default
|
|
* charset if no such parameter was specified at construction. For non-
|
|
* text DataFlavors, and for non-charset text flavors, this method returns
|
|
* null.
|
|
*/
|
|
public static String getTextCharset(DataFlavor flavor) {
|
|
if (!isFlavorCharsetTextType(flavor)) {
|
|
return null;
|
|
}
|
|
|
|
String encoding = flavor.getParameter("charset");
|
|
|
|
return (encoding != null) ? encoding : getDefaultTextCharset();
|
|
}
|
|
|
|
/**
|
|
* Returns the platform's default character encoding. This code is
|
|
* based on code available in sun.io.Converters.getDefaultConverterClass.
|
|
*/
|
|
public static String getDefaultTextCharset() {
|
|
if (defaultEncoding != null) {
|
|
return defaultEncoding;
|
|
}
|
|
|
|
PrivilegedAction a =
|
|
new sun.security.action.GetPropertyAction("file.encoding");
|
|
String enc = (String)AccessController.doPrivileged(a);
|
|
if (enc != null) {
|
|
defaultEncoding = DataTransferer.canonicalName(enc);
|
|
if (!isEncodingSupported(defaultEncoding)) {
|
|
// ISO8859_1 is a canonical encoding
|
|
defaultEncoding = "ISO8859_1";
|
|
}
|
|
return defaultEncoding;
|
|
} else {
|
|
/* Property not yet set, so do not set default encoding and
|
|
just use ISO8859_1 for now */
|
|
return "ISO8859_1"; // ISO8859_1 is a canonical encoding
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tests only whether the flavor's MIME type supports the charset
|
|
* parameter. Must only be called for flavors with a primary type of
|
|
* "text".
|
|
*/
|
|
public static boolean doesSubtypeSupportCharset(DataFlavor flavor) {
|
|
String subType = flavor.getSubType();
|
|
if (subType == null) {
|
|
return false;
|
|
}
|
|
|
|
Object support = textMIMESubtypeCharsetSupport.get(subType);
|
|
|
|
if (support != null) {
|
|
return (support == Boolean.TRUE);
|
|
}
|
|
|
|
boolean ret_val = (flavor.getParameter("charset") != null);
|
|
textMIMESubtypeCharsetSupport.put
|
|
(subType, (ret_val) ? Boolean.TRUE : Boolean.FALSE);
|
|
return ret_val;
|
|
}
|
|
public static boolean doesSubtypeSupportCharset(String subType,
|
|
String charset)
|
|
{
|
|
Object support = textMIMESubtypeCharsetSupport.get(subType);
|
|
|
|
if (support != null) {
|
|
return (support == Boolean.TRUE);
|
|
}
|
|
|
|
boolean ret_val = (charset != null);
|
|
textMIMESubtypeCharsetSupport.put
|
|
(subType, (ret_val) ? Boolean.TRUE : Boolean.FALSE);
|
|
return ret_val;
|
|
}
|
|
|
|
/**
|
|
* Returns whether this flavor is a text type which supports the
|
|
* 'charset' parameter.
|
|
*/
|
|
public static boolean isFlavorCharsetTextType(DataFlavor flavor) {
|
|
// Although stringFlavor doesn't actually support the charset
|
|
// parameter (because its primary MIME type is not "text"), it should
|
|
// be treated as though it does. stringFlavor is semantically
|
|
// equivalent to "text/plain" data.
|
|
if (DataFlavor.stringFlavor.equals(flavor)) {
|
|
return true;
|
|
}
|
|
|
|
if (!"text".equals(flavor.getPrimaryType()) ||
|
|
!doesSubtypeSupportCharset(flavor))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Class rep_class = flavor.getRepresentationClass();
|
|
|
|
if (flavor.isRepresentationClassReader() ||
|
|
String.class.equals(rep_class) ||
|
|
flavor.isRepresentationClassCharBuffer() ||
|
|
DataTransferer.charArrayClass.equals(rep_class))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (!(flavor.isRepresentationClassInputStream() ||
|
|
flavor.isRepresentationClassByteBuffer() ||
|
|
DataTransferer.byteArrayClass.equals(rep_class))) {
|
|
return false;
|
|
}
|
|
|
|
String charset = flavor.getParameter("charset");
|
|
|
|
return (charset != null)
|
|
? DataTransferer.isEncodingSupported(charset)
|
|
: true; // null equals default encoding which is always supported
|
|
}
|
|
|
|
/**
|
|
* Returns whether this flavor is a text type which does not support the
|
|
* 'charset' parameter.
|
|
*/
|
|
public static boolean isFlavorNoncharsetTextType(DataFlavor flavor) {
|
|
if (!"text".equals(flavor.getPrimaryType()) ||
|
|
doesSubtypeSupportCharset(flavor))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return (flavor.isRepresentationClassInputStream() ||
|
|
flavor.isRepresentationClassByteBuffer() ||
|
|
DataTransferer.byteArrayClass.
|
|
equals(flavor.getRepresentationClass()));
|
|
}
|
|
|
|
private static boolean isFileInWebstartedCache(File file) {
|
|
if (deploymentCacheDirectoryList.isEmpty()) {
|
|
for (int i = 0; i < DEPLOYMENT_CACHE_PROPERTIES.length; i++) {
|
|
String s = DEPLOYMENT_CACHE_PROPERTIES[i];
|
|
String s1 = System.getProperty(s);
|
|
if (s1 == null)
|
|
continue;
|
|
try {
|
|
File file3 = (new File(s1)).getCanonicalFile();
|
|
if (file3 != null)
|
|
deploymentCacheDirectoryList.add(file3);
|
|
} catch (IOException e) {
|
|
}
|
|
}
|
|
}
|
|
|
|
for (Iterator it = deploymentCacheDirectoryList.iterator();
|
|
it.hasNext(); ) {
|
|
File file1 = (File)it.next();
|
|
File file2 = file;
|
|
while (file2 != null) {
|
|
if (file2.equals(file1))
|
|
return true;
|
|
file2 = file2.getParentFile();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Determines whether this JRE can both encode and decode text in the
|
|
* specified encoding.
|
|
*/
|
|
// TODO
|
|
//
|
|
// Eliminate this method and replace with calls to
|
|
// Charset.isSupported(String)
|
|
public static boolean isEncodingSupported(String encoding) {
|
|
if (encoding == null) {
|
|
return false;
|
|
}
|
|
String canonicalName = DataTransferer.canonicalName(encoding);
|
|
|
|
Boolean bool = (Boolean)knownEncodings.get(canonicalName);
|
|
|
|
if (bool == null) {
|
|
// keep looking - try to encode something and see if io complains
|
|
try {
|
|
new String("abc".getBytes(canonicalName), canonicalName);
|
|
bool = Boolean.TRUE;
|
|
} catch (UnsupportedEncodingException encodingException) {
|
|
bool = Boolean.FALSE;
|
|
}
|
|
|
|
knownEncodings.put(canonicalName, bool);
|
|
}
|
|
|
|
return (bool == Boolean.TRUE);
|
|
}
|
|
|
|
/**
|
|
* Returns an Iterator which traverses a SortedSet of Strings which are
|
|
* a total order of the standard character sets supported by the JRE. The
|
|
* ordering follows the same principles as DataFlavor.selectBestTextFlavor.
|
|
* So as to avoid loading all available character converters, optional,
|
|
* non-standard, character sets are not included.
|
|
*/
|
|
public static Iterator standardEncodings() {
|
|
if (standardEncodings == null) {
|
|
TreeSet tempSet = new TreeSet(defaultCharsetComparator);
|
|
tempSet.add("US-ASCII");
|
|
tempSet.add("ISO-8859-1");
|
|
tempSet.add("UTF-8");
|
|
tempSet.add("UTF-16BE");
|
|
tempSet.add("UTF-16LE");
|
|
tempSet.add("UTF-16");
|
|
tempSet.add(getDefaultTextCharset());
|
|
standardEncodings = Collections.unmodifiableSortedSet(tempSet);
|
|
}
|
|
return standardEncodings.iterator();
|
|
}
|
|
|
|
/**
|
|
* Converts a FlavorMap to a FlavorTable.
|
|
*/
|
|
public static FlavorTable adaptFlavorMap(final FlavorMap map) {
|
|
if (map instanceof FlavorTable) {
|
|
return (FlavorTable)map;
|
|
}
|
|
|
|
return new FlavorTable() {
|
|
public Map getNativesForFlavors(DataFlavor[] flavors) {
|
|
return map.getNativesForFlavors(flavors);
|
|
}
|
|
public Map getFlavorsForNatives(String[] natives) {
|
|
return map.getFlavorsForNatives(natives);
|
|
}
|
|
public List getNativesForFlavor(DataFlavor flav) {
|
|
Map natives =
|
|
getNativesForFlavors(new DataFlavor[] { flav } );
|
|
String nat = (String)natives.get(flav);
|
|
if (nat != null) {
|
|
List list = new ArrayList(1);
|
|
list.add(nat);
|
|
return list;
|
|
} else {
|
|
return Collections.EMPTY_LIST;
|
|
}
|
|
}
|
|
public List getFlavorsForNative(String nat) {
|
|
Map flavors =
|
|
getFlavorsForNatives(new String[] { nat } );
|
|
DataFlavor flavor = (DataFlavor)flavors.get(nat);
|
|
if (flavor != null) {
|
|
List list = new ArrayList(1);
|
|
list.add(flavor);
|
|
return list;
|
|
} else {
|
|
return Collections.EMPTY_LIST;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns the default Unicode encoding for the platform. The encoding
|
|
* need not be canonical. This method is only used by the archaic function
|
|
* DataFlavor.getTextPlainUnicodeFlavor().
|
|
*/
|
|
public abstract String getDefaultUnicodeEncoding();
|
|
|
|
/**
|
|
* This method is called for text flavor mappings established while parsing
|
|
* the flavormap.properties file. It stores the "eoln" and "terminators"
|
|
* parameters which are not officially part of the MIME type. They are
|
|
* MIME parameters specific to the flavormap.properties file format.
|
|
*/
|
|
public void registerTextFlavorProperties(String nat, String charset,
|
|
String eoln, String terminators) {
|
|
Long format = getFormatForNativeAsLong(nat);
|
|
|
|
textNatives.add(format);
|
|
nativeCharsets.put(format, (charset != null && charset.length() != 0)
|
|
? charset : getDefaultTextCharset());
|
|
if (eoln != null && eoln.length() != 0 && !eoln.equals("\n")) {
|
|
nativeEOLNs.put(format, eoln);
|
|
}
|
|
if (terminators != null && terminators.length() != 0) {
|
|
Integer iTerminators = Integer.valueOf(terminators);
|
|
if (iTerminators.intValue() > 0) {
|
|
nativeTerminators.put(format, iTerminators);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines whether the native corresponding to the specified long format
|
|
* was listed in the flavormap.properties file.
|
|
*/
|
|
private boolean isTextFormat(long format) {
|
|
return textNatives.contains(new Long(format));
|
|
}
|
|
|
|
/**
|
|
* Specifies whether text imported from the native system in the specified
|
|
* format is locale-dependent. If so, when decoding such text,
|
|
* 'nativeCharsets' should be ignored, and instead, the Transferable should
|
|
* be queried for its javaTextEncodingFlavor data for the correct encoding.
|
|
*/
|
|
public abstract boolean isLocaleDependentTextFormat(long format);
|
|
|
|
/**
|
|
* Determines whether the DataFlavor corresponding to the specified long
|
|
* format is DataFlavor.javaFileListFlavor.
|
|
*/
|
|
public abstract boolean isFileFormat(long format);
|
|
|
|
/**
|
|
* Determines whether the DataFlavor corresponding to the specified long
|
|
* format is DataFlavor.imageFlavor.
|
|
*/
|
|
public abstract boolean isImageFormat(long format);
|
|
|
|
/**
|
|
* Returns a Map whose keys are all of the possible formats into which the
|
|
* Transferable's transfer data flavors can be translated. The value of
|
|
* each key is the DataFlavor in which the Transferable's data should be
|
|
* requested when converting to the format.
|
|
*/
|
|
public Map getFormatsForTransferable(Transferable contents,
|
|
FlavorTable map) {
|
|
DataFlavor[] flavors = contents.getTransferDataFlavors();
|
|
if (flavors == null) {
|
|
return new HashMap(0);
|
|
}
|
|
return getFormatsForFlavors(flavors, map);
|
|
}
|
|
|
|
/**
|
|
* Returns a Map whose keys are all of the possible formats into which data
|
|
* in the specified DataFlavor can be translated. The value of each key
|
|
* is the DataFlavor in which a Transferable's data should be requested
|
|
* when converting to the format.
|
|
*/
|
|
public Map getFormatsForFlavor(DataFlavor flavor, FlavorTable map) {
|
|
return getFormatsForFlavors(new DataFlavor[] { flavor },
|
|
map);
|
|
}
|
|
|
|
/**
|
|
* Returns a Map whose keys are all of the possible formats into which data
|
|
* in the specified DataFlavors can be translated. The value of each key
|
|
* is the DataFlavor in which the Transferable's data should be requested
|
|
* when converting to the format.
|
|
*
|
|
* @param flavors the data flavors
|
|
* @param map the FlavorTable which contains mappings between
|
|
* DataFlavors and data formats
|
|
* @throws NullPointerException if flavors or map is <code>null</code>
|
|
*/
|
|
public Map getFormatsForFlavors(DataFlavor[] flavors, FlavorTable map) {
|
|
Map formatMap = new HashMap(flavors.length);
|
|
Map textPlainMap = new HashMap(flavors.length);
|
|
|
|
// Iterate backwards so that preferred DataFlavors are used over
|
|
// other DataFlavors. (See javadoc for
|
|
// Transferable.getTransferDataFlavors.)
|
|
for (int i = flavors.length - 1; i >= 0; i--) {
|
|
DataFlavor flavor = flavors[i];
|
|
if (flavor == null) continue;
|
|
|
|
// Don't explicitly test for String, since it is just a special
|
|
// case of Serializable
|
|
if (flavor.isFlavorTextType() ||
|
|
flavor.isFlavorJavaFileListType() ||
|
|
DataFlavor.imageFlavor.equals(flavor) ||
|
|
/* flavor.isRepresentationClassSerializable() || */
|
|
flavor.isRepresentationClassInputStream()
|
|
/* || flavor.isRepresentationClassRemote() */)
|
|
{
|
|
List natives = map.getNativesForFlavor(flavor);
|
|
|
|
for (Iterator iter = natives.iterator(); iter.hasNext(); ) {
|
|
Long lFormat =
|
|
getFormatForNativeAsLong((String)iter.next());
|
|
|
|
formatMap.put(lFormat, flavor);
|
|
|
|
// SystemFlavorMap.getNativesForFlavor will return
|
|
// text/plain natives for all text/*. While this is good
|
|
// for a single text/* flavor, we would prefer that
|
|
// text/plain native data come from a text/plain flavor.
|
|
if (("text".equals(flavor.getPrimaryType()) &&
|
|
"plain".equals(flavor.getSubType())) ||
|
|
flavor.equals(DataFlavor.stringFlavor))
|
|
{
|
|
textPlainMap.put(lFormat, flavor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
formatMap.putAll(textPlainMap);
|
|
|
|
return formatMap;
|
|
}
|
|
|
|
/**
|
|
* Reduces the Map output for the root function to an array of the
|
|
* Map's keys.
|
|
*/
|
|
public long[] getFormatsForTransferableAsArray(Transferable contents,
|
|
FlavorTable map) {
|
|
return keysToLongArray(getFormatsForTransferable(contents, map));
|
|
}
|
|
public long[] getFormatsForFlavorAsArray(DataFlavor flavor,
|
|
FlavorTable map) {
|
|
return keysToLongArray(getFormatsForFlavor(flavor, map));
|
|
}
|
|
public long[] getFormatsForFlavorsAsArray(DataFlavor[] flavors,
|
|
FlavorTable map) {
|
|
return keysToLongArray(getFormatsForFlavors(flavors, map));
|
|
}
|
|
|
|
/**
|
|
* Returns a Map whose keys are all of the possible DataFlavors into which
|
|
* data in the specified format can be translated. The value of each key
|
|
* is the format in which the Clipboard or dropped data should be requested
|
|
* when converting to the DataFlavor.
|
|
*/
|
|
public Map getFlavorsForFormat(long format, FlavorTable map) {
|
|
return getFlavorsForFormats(new long[] { format }, map);
|
|
}
|
|
|
|
/**
|
|
* Returns a Map whose keys are all of the possible DataFlavors into which
|
|
* data in the specified formats can be translated. The value of each key
|
|
* is the format in which the Clipboard or dropped data should be requested
|
|
* when converting to the DataFlavor.
|
|
*/
|
|
public Map getFlavorsForFormats(long[] formats, FlavorTable map) {
|
|
Map flavorMap = new HashMap(formats.length);
|
|
Set mappingSet = new HashSet(formats.length);
|
|
Set flavorSet = new HashSet(formats.length);
|
|
|
|
// First step: build flavorSet, mappingSet and initial flavorMap
|
|
// flavorSet - the set of all the DataFlavors into which
|
|
// data in the specified formats can be translated;
|
|
// mappingSet - the set of all the mappings from the specified formats
|
|
// into any DataFlavor;
|
|
// flavorMap - after this step, this map maps each of the DataFlavors
|
|
// from flavorSet to any of the specified formats.
|
|
for (int i = 0; i < formats.length; i++) {
|
|
long format = formats[i];
|
|
String nat = getNativeForFormat(format);
|
|
List flavors = map.getFlavorsForNative(nat);
|
|
|
|
for (Iterator iter = flavors.iterator(); iter.hasNext(); ) {
|
|
DataFlavor flavor = (DataFlavor)iter.next();
|
|
|
|
// Don't explicitly test for String, since it is just a special
|
|
// case of Serializable
|
|
if (flavor.isFlavorTextType() ||
|
|
flavor.isFlavorJavaFileListType() ||
|
|
DataFlavor.imageFlavor.equals(flavor) ||
|
|
/* flavor.isRepresentationClassSerializable() || */
|
|
flavor.isRepresentationClassInputStream()
|
|
/* || flavor.isRepresentationClassRemote() */)
|
|
{
|
|
Long lFormat = new Long(format);
|
|
Object mapping =
|
|
DataTransferer.createMapping(lFormat, flavor);
|
|
flavorMap.put(flavor, lFormat);
|
|
mappingSet.add(mapping);
|
|
flavorSet.add(flavor);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Second step: for each DataFlavor try to figure out which of the
|
|
// specified formats is the best to translate to this flavor.
|
|
// Then map each flavor to the best format.
|
|
// For the given flavor, FlavorTable indicates which native will
|
|
// best reflect data in the specified flavor to the underlying native
|
|
// platform. We assume that this native is the best to translate
|
|
// to this flavor.
|
|
// Note: FlavorTable allows one-way mappings, so we can occasionally
|
|
// map a flavor to the format for which the corresponding
|
|
// format-to-flavor mapping doesn't exist. For this reason we have built
|
|
// a mappingSet of all format-to-flavor mappings for the specified formats
|
|
// and check if the format-to-flavor mapping exists for the
|
|
// (flavor,format) pair being added.
|
|
for (Iterator flavorIter = flavorSet.iterator();
|
|
flavorIter.hasNext(); ) {
|
|
DataFlavor flavor = (DataFlavor)flavorIter.next();
|
|
|
|
List natives = map.getNativesForFlavor(flavor);
|
|
|
|
for (Iterator nativeIter = natives.iterator();
|
|
nativeIter.hasNext(); ) {
|
|
Long lFormat =
|
|
getFormatForNativeAsLong((String)nativeIter.next());
|
|
Object mapping = DataTransferer.createMapping(lFormat, flavor);
|
|
|
|
if (mappingSet.contains(mapping)) {
|
|
flavorMap.put(flavor, lFormat);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return flavorMap;
|
|
}
|
|
|
|
/**
|
|
* Returns a Set of all DataFlavors for which
|
|
* 1) a mapping from at least one of the specified formats exists in the
|
|
* specified map and
|
|
* 2) the data translation for this mapping can be performed by the data
|
|
* transfer subsystem.
|
|
*
|
|
* @param formats the data formats
|
|
* @param map the FlavorTable which contains mappings between
|
|
* DataFlavors and data formats
|
|
* @throws NullPointerException if formats or map is <code>null</code>
|
|
*/
|
|
public Set getFlavorsForFormatsAsSet(long[] formats, FlavorTable map) {
|
|
Set flavorSet = new HashSet(formats.length);
|
|
|
|
for (int i = 0; i < formats.length; i++) {
|
|
String nat = getNativeForFormat(formats[i]);
|
|
List flavors = map.getFlavorsForNative(nat);
|
|
|
|
for (Iterator iter = flavors.iterator(); iter.hasNext(); ) {
|
|
DataFlavor flavor = (DataFlavor)iter.next();
|
|
|
|
// Don't explicitly test for String, since it is just a special
|
|
// case of Serializable
|
|
if (flavor.isFlavorTextType() ||
|
|
flavor.isFlavorJavaFileListType() ||
|
|
DataFlavor.imageFlavor.equals(flavor) ||
|
|
/* flavor.isRepresentationClassSerializable() || */
|
|
flavor.isRepresentationClassInputStream()
|
|
/* || flavor.isRepresentationClassRemote() */)
|
|
{
|
|
flavorSet.add(flavor);
|
|
}
|
|
}
|
|
}
|
|
|
|
return flavorSet;
|
|
}
|
|
|
|
/**
|
|
* Returns an array of all DataFlavors for which
|
|
* 1) a mapping from the specified format exists in the specified map and
|
|
* 2) the data translation for this mapping can be performed by the data
|
|
* transfer subsystem.
|
|
* The array will be sorted according to a
|
|
* <code>DataFlavorComparator</code> created with the specified
|
|
* map as an argument.
|
|
*
|
|
* @param format the data format
|
|
* @param map the FlavorTable which contains mappings between
|
|
* DataFlavors and data formats
|
|
* @throws NullPointerException if map is <code>null</code>
|
|
*/
|
|
public DataFlavor[] getFlavorsForFormatAsArray(long format,
|
|
FlavorTable map) {
|
|
return getFlavorsForFormatsAsArray(new long[] { format }, map);
|
|
}
|
|
|
|
/**
|
|
* Returns an array of all DataFlavors for which
|
|
* 1) a mapping from at least one of the specified formats exists in the
|
|
* specified map and
|
|
* 2) the data translation for this mapping can be performed by the data
|
|
* transfer subsystem.
|
|
* The array will be sorted according to a
|
|
* <code>DataFlavorComparator</code> created with the specified
|
|
* map as an argument.
|
|
*
|
|
* @param formats the data formats
|
|
* @param map the FlavorTable which contains mappings between
|
|
* DataFlavors and data formats
|
|
* @throws NullPointerException if formats or map is <code>null</code>
|
|
*/
|
|
public DataFlavor[] getFlavorsForFormatsAsArray(long[] formats,
|
|
FlavorTable map) {
|
|
// getFlavorsForFormatsAsSet() is less expensive than
|
|
// getFlavorsForFormats().
|
|
return setToSortedDataFlavorArray(getFlavorsForFormatsAsSet(formats, map));
|
|
}
|
|
|
|
/**
|
|
* Returns an object that represents a mapping between the specified
|
|
* key and value. <tt>null</tt> values and the <tt>null</tt> keys are
|
|
* permitted. The internal representation of the mapping object is
|
|
* irrelevant. The only requrement is that the two mapping objects are equal
|
|
* if and only if their keys are equal and their values are equal.
|
|
* More formally, the two mapping objects are equal if and only if
|
|
* <tt>(value1 == null ? value2 == null : value1.equals(value2))
|
|
* && (key1 == null ? key2 == null : key1.equals(key2))</tt>.
|
|
*/
|
|
private static Object createMapping(Object key, Object value) {
|
|
// NOTE: Should be updated to use AbstractMap.SimpleEntry as
|
|
// soon as it is made public.
|
|
return Arrays.asList(new Object[] { key, value });
|
|
}
|
|
|
|
/**
|
|
* Looks-up or registers the String native with the native data transfer
|
|
* system and returns a long format corresponding to that native.
|
|
*/
|
|
protected abstract Long getFormatForNativeAsLong(String str);
|
|
|
|
/**
|
|
* Looks-up the String native corresponding to the specified long format in
|
|
* the native data transfer system.
|
|
*/
|
|
protected abstract String getNativeForFormat(long format);
|
|
|
|
/**
|
|
* Primary translation function for translating a Transferable into
|
|
* a byte array, given a source DataFlavor and target format.
|
|
*/
|
|
public byte[] translateTransferable(Transferable contents,
|
|
DataFlavor flavor,
|
|
long format) throws IOException
|
|
{
|
|
// Obtain the transfer data in the source DataFlavor.
|
|
//
|
|
// Note that we special case DataFlavor.plainTextFlavor because
|
|
// StringSelection supports this flavor incorrectly -- instead of
|
|
// returning an InputStream as the DataFlavor representation class
|
|
// states, it returns a Reader. Instead of using this broken
|
|
// functionality, we request the data in stringFlavor (the other
|
|
// DataFlavor which StringSelection supports) and use the String
|
|
// translator.
|
|
Object obj;
|
|
boolean stringSelectionHack;
|
|
try {
|
|
obj = contents.getTransferData(flavor);
|
|
if (obj == null) {
|
|
return null;
|
|
}
|
|
if (flavor.equals(DataFlavor.plainTextFlavor) &&
|
|
!(obj instanceof InputStream))
|
|
{
|
|
obj = contents.getTransferData(DataFlavor.stringFlavor);
|
|
if (obj == null) {
|
|
return null;
|
|
}
|
|
stringSelectionHack = true;
|
|
} else {
|
|
stringSelectionHack = false;
|
|
}
|
|
} catch (UnsupportedFlavorException e) {
|
|
throw new IOException(e.getMessage());
|
|
}
|
|
|
|
// Source data is a String. Search-and-replace EOLN. Encode into the
|
|
// target format. Append terminating NUL bytes.
|
|
if (stringSelectionHack ||
|
|
(String.class.equals(flavor.getRepresentationClass()) &&
|
|
isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
|
|
|
|
String str = removeSuspectedData(flavor, contents, (String)obj);
|
|
|
|
Long lFormat = new Long(format);
|
|
String charset = (String)nativeCharsets.get(lFormat);
|
|
if (charset == null) {
|
|
// Only happens when we have a custom text type
|
|
charset = getDefaultTextCharset();
|
|
}
|
|
String eoln = (String)nativeEOLNs.get(lFormat);
|
|
Integer terminators = (Integer)nativeTerminators.get(lFormat);
|
|
|
|
// Search and replace EOLN. Note that if EOLN is "\n", then we
|
|
// never added an entry to nativeEOLNs anyway, so we'll skip this
|
|
// code altogether.
|
|
if (eoln != null) {
|
|
int length = str.length();
|
|
StringBuffer buffer =
|
|
new StringBuffer(length * 2); // 2 is a heuristic
|
|
for (int i = 0; i < length; i++) {
|
|
char c = str.charAt(i);
|
|
if (c == '\n') {
|
|
buffer.append(eoln);
|
|
} else {
|
|
buffer.append(c);
|
|
}
|
|
}
|
|
str = buffer.toString();
|
|
}
|
|
|
|
// Encode text in target format.
|
|
byte[] bytes = str.getBytes(charset);
|
|
|
|
// Append terminating NUL bytes. Note that if terminators is 0,
|
|
// the we never added an entry to nativeTerminators anyway, so
|
|
// we'll skip code altogether.
|
|
if (terminators != null) {
|
|
int numTerminators = terminators.intValue();
|
|
byte[] terminatedBytes =
|
|
new byte[bytes.length + numTerminators];
|
|
System.arraycopy(bytes, 0, terminatedBytes, 0, bytes.length);
|
|
for (int i = bytes.length; i < terminatedBytes.length; i++) {
|
|
terminatedBytes[i] = 0x0;
|
|
}
|
|
bytes = terminatedBytes;
|
|
}
|
|
|
|
return bytes;
|
|
|
|
// Source data is a Reader. Convert to a String and recur. In the
|
|
// future, we may want to rewrite this so that we encode on demand.
|
|
} else if (flavor.isRepresentationClassReader()) {
|
|
if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
|
|
throw new IOException
|
|
("cannot transfer non-text data as Reader");
|
|
}
|
|
|
|
Reader r = (Reader)obj;
|
|
StringBuffer buf = new StringBuffer();
|
|
int c;
|
|
while ((c = r.read()) != -1) {
|
|
buf.append((char)c);
|
|
}
|
|
r.close();
|
|
|
|
return translateTransferable(new StringSelection(
|
|
buf.toString()), DataFlavor.plainTextFlavor, format);
|
|
|
|
// Source data is a CharBuffer. Convert to a String and recur.
|
|
} else if (flavor.isRepresentationClassCharBuffer()) {
|
|
if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
|
|
throw new IOException
|
|
("cannot transfer non-text data as CharBuffer");
|
|
}
|
|
|
|
CharBuffer buffer = (CharBuffer)obj;
|
|
int size = buffer.remaining();
|
|
char[] chars = new char[size];
|
|
buffer.get(chars, 0, size);
|
|
return translateTransferable(new StringSelection(
|
|
new String(chars)), DataFlavor.plainTextFlavor, format);
|
|
|
|
// Source data is a char array. Convert to a String and recur.
|
|
} else if (charArrayClass.equals(flavor.getRepresentationClass())) {
|
|
if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
|
|
throw new IOException
|
|
("cannot transfer non-text data as char array");
|
|
}
|
|
|
|
return translateTransferable(new StringSelection(
|
|
new String((char[])obj)),
|
|
DataFlavor.plainTextFlavor, format);
|
|
|
|
// Source data is a ByteBuffer. For arbitrary flavors, simply return
|
|
// the array. For text flavors, decode back to a String and recur to
|
|
// reencode according to the requested format.
|
|
} else if (flavor.isRepresentationClassByteBuffer()) {
|
|
ByteBuffer buffer = (ByteBuffer)obj;
|
|
int size = buffer.remaining();
|
|
byte[] bytes = new byte[size];
|
|
buffer.get(bytes, 0, size);
|
|
|
|
if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
|
|
String sourceEncoding = DataTransferer.getTextCharset(flavor);
|
|
return translateTransferable(new StringSelection(
|
|
new String(bytes, sourceEncoding)),
|
|
DataFlavor.plainTextFlavor, format);
|
|
} else {
|
|
return bytes;
|
|
}
|
|
|
|
// Source data is a byte array. For arbitrary flavors, simply return
|
|
// the array. For text flavors, decode back to a String and recur to
|
|
// reencode according to the requested format.
|
|
} else if (byteArrayClass.equals(flavor.getRepresentationClass())) {
|
|
byte[] bytes = (byte[])obj;
|
|
|
|
if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
|
|
String sourceEncoding = DataTransferer.getTextCharset(flavor);
|
|
return translateTransferable(new StringSelection(
|
|
new String(bytes, sourceEncoding)),
|
|
DataFlavor.plainTextFlavor, format);
|
|
} else {
|
|
return bytes;
|
|
}
|
|
// Source data is Image
|
|
} else if (DataFlavor.imageFlavor.equals(flavor)) {
|
|
if (!isImageFormat(format)) {
|
|
throw new IOException("Data translation failed: " +
|
|
"not an image format");
|
|
}
|
|
|
|
Image image = (Image)obj;
|
|
byte[] bytes = imageToPlatformBytes(image, format);
|
|
|
|
if (bytes == null) {
|
|
throw new IOException("Data translation failed: " +
|
|
"cannot convert java image to native format");
|
|
}
|
|
return bytes;
|
|
}
|
|
|
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
|
|
// Target data is a file list. Source data must be a
|
|
// java.util.List which contains java.io.File or String instances.
|
|
if (isFileFormat(format)) {
|
|
if (!DataFlavor.javaFileListFlavor.equals(flavor)) {
|
|
throw new IOException("data translation failed");
|
|
}
|
|
final List list = (List)obj;
|
|
final ArrayList fileList = new ArrayList();
|
|
final ProtectionDomain userProtectionDomain =
|
|
getUserProtactionDomain(contents);
|
|
int nFiles = 0;
|
|
for (int i = 0; i < list.size(); i++) {
|
|
Object o = list.get(i);
|
|
if (o instanceof File || o instanceof String) {
|
|
nFiles++;
|
|
}
|
|
}
|
|
|
|
try {
|
|
AccessController.doPrivileged(new PrivilegedExceptionAction() {
|
|
public Object run() throws IOException {
|
|
for (int i = 0; i < list.size(); i++) {
|
|
File file = castToFile(list.get(i));
|
|
if (System.getSecurityManager() == null ||
|
|
(!DataTransferer.isFileInWebstartedCache(file) &&
|
|
!isForbiddenToRead(file, userProtectionDomain))) {
|
|
fileList.add(file.getCanonicalPath());
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
});
|
|
} catch (PrivilegedActionException pae) {
|
|
throw new IOException(pae.getMessage());
|
|
}
|
|
|
|
for (int i = 0; i < fileList.size(); i++) {
|
|
byte[] bytes = ((String) fileList.get(i)).getBytes();
|
|
bos.write(bytes, 0, bytes.length);
|
|
bos.write(0);
|
|
}
|
|
bos.write(0);
|
|
|
|
// Source data is an InputStream. For arbitrary flavors, just grab the
|
|
// bytes and dump them into a byte array. For text flavors, decode back
|
|
// to a String and recur to reencode according to the requested format.
|
|
} else if (flavor.isRepresentationClassInputStream()) {
|
|
InputStream is = (InputStream)obj;
|
|
boolean eof = false;
|
|
int avail = is.available();
|
|
byte[] tmp = new byte[avail > 8192 ? avail : 8192];
|
|
do {
|
|
int ret;
|
|
if (!(eof = (ret = is.read(tmp, 0, tmp.length)) == -1)) {
|
|
bos.write(tmp, 0, ret);
|
|
}
|
|
} while (!eof);
|
|
is.close();
|
|
|
|
if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
|
|
byte[] bytes = bos.toByteArray();
|
|
bos.close();
|
|
String sourceEncoding = DataTransferer.getTextCharset(flavor);
|
|
return translateTransferable(new StringSelection(
|
|
new String(bytes, sourceEncoding)),
|
|
DataFlavor.plainTextFlavor, format);
|
|
}
|
|
|
|
// Source data is an RMI object
|
|
} /* else if (flavor.isRepresentationClassRemote()) {
|
|
MarshalledObject mo = new MarshalledObject(obj);
|
|
ObjectOutputStream oos = new ObjectOutputStream(bos);
|
|
oos.writeObject(mo);
|
|
oos.close();
|
|
|
|
// Source data is Serializable
|
|
} else if (flavor.isRepresentationClassSerializable()) {
|
|
ObjectOutputStream oos = new ObjectOutputStream(bos);
|
|
oos.writeObject(obj);
|
|
oos.close();
|
|
|
|
} */ else {
|
|
throw new IOException("data translation failed");
|
|
}
|
|
|
|
byte[] ret = bos.toByteArray();
|
|
bos.close();
|
|
return ret;
|
|
}
|
|
|
|
private File castToFile(Object obj) throws IOException {
|
|
String name = null;
|
|
if (obj instanceof File) {
|
|
name = ((File)obj).getCanonicalPath();
|
|
} else {
|
|
if (obj instanceof String)
|
|
name = (String)obj;
|
|
}
|
|
return new File(name);
|
|
}
|
|
|
|
private boolean isForbiddenToRead(File file, ProtectionDomain domain) {
|
|
if (domain == null)
|
|
return false;
|
|
try {
|
|
if (domain.implies(new FilePermission(file.getCanonicalPath(),
|
|
"read, delete")))
|
|
return false;
|
|
} catch (IOException e) {
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static ProtectionDomain getUserProtactionDomain(Transferable contents) {
|
|
return contents.getClass().getProtectionDomain();
|
|
}
|
|
|
|
private String removeSuspectedData(DataFlavor dataflavor,
|
|
Transferable transferable, final String str)
|
|
throws IOException {
|
|
if (System.getSecurityManager() == null ||
|
|
!dataflavor.isMimeTypeEqual("text/uri-list"))
|
|
return str;
|
|
String s = "";
|
|
final ProtectionDomain userProtectionDomain =
|
|
getUserProtactionDomain(transferable);
|
|
try {
|
|
s = (String)AccessController.doPrivileged(new PrivilegedExceptionAction() {
|
|
|
|
public Object run() {
|
|
StringBuffer sb = new StringBuffer(str.length());
|
|
/* for (int i = 0; i < str.split("(\\s)+").length; i++) */
|
|
for (int i = 0; i < str.length(); ) {
|
|
/* String s1 = str.split("(\\s)+")[i]; */
|
|
int j = i;
|
|
for (; str.length() > i && str.charAt(i) > ' '; i++);
|
|
|
|
String s1 = str.substring(j, i);
|
|
for (; str.length() > i && str.charAt(i) <= ' '; i++);
|
|
|
|
File file = new File(s1);
|
|
if (file.exists() &&
|
|
(DataTransferer.isFileInWebstartedCache(file) ||
|
|
isForbiddenToRead(file, userProtectionDomain)))
|
|
continue;
|
|
if (0 != sb.length())
|
|
sb.append("\\r\\n");
|
|
sb.append(s1);
|
|
}
|
|
return sb.toString();
|
|
}
|
|
});
|
|
} catch (PrivilegedActionException e) {
|
|
throw new IOException(e.getMessage());
|
|
}
|
|
return s;
|
|
}
|
|
|
|
public Object translateBytes(byte[] bytes, DataFlavor flavor,
|
|
long format, Transferable localeTransferable)
|
|
throws IOException
|
|
{
|
|
return translateBytesOrStream(null, bytes, flavor, format,
|
|
localeTransferable);
|
|
}
|
|
|
|
public Object translateStream(InputStream str, DataFlavor flavor,
|
|
long format, Transferable localeTransferable)
|
|
throws IOException
|
|
{
|
|
return translateBytesOrStream(str, null, flavor, format,
|
|
localeTransferable);
|
|
}
|
|
|
|
/**
|
|
* Primary translation function for translating either a byte array or
|
|
* an InputStream into an Object, given a source format and a target
|
|
* DataFlavor.
|
|
*
|
|
* One of str/bytes is non-null; the other is null.
|
|
* The conversion from byte[] to InputStream is cheap, so do that
|
|
* immediately if necessary. The opposite conversion is expensive,
|
|
* so avoid it if possible.
|
|
*/
|
|
protected Object translateBytesOrStream(InputStream str, byte[] bytes,
|
|
DataFlavor flavor, long format,
|
|
Transferable localeTransferable)
|
|
throws IOException
|
|
{
|
|
|
|
if (str == null) {
|
|
str = new ByteArrayInputStream(bytes);
|
|
}
|
|
|
|
// Source data is a file list. Use the dragQueryFile native function to
|
|
// do most of the decoding. Then wrap File objects around the String
|
|
// filenames and return a List.
|
|
if (isFileFormat(format)) {
|
|
if (!DataFlavor.javaFileListFlavor.equals(flavor)) {
|
|
throw new IOException("data translation failed");
|
|
}
|
|
if (bytes == null) {
|
|
bytes = inputStreamToByteArray(str);
|
|
}
|
|
String[] filenames = dragQueryFile(bytes);
|
|
if (filenames == null) {
|
|
str.close();
|
|
return null;
|
|
}
|
|
|
|
// Convert the strings to File objects
|
|
File[] files = new File[filenames.length];
|
|
for (int i = 0; i < filenames.length; i++) {
|
|
files[i] = new File(filenames[i]);
|
|
}
|
|
str.close();
|
|
|
|
// Turn the list of Files into a List and return
|
|
return Arrays.asList(files);
|
|
|
|
// Target data is a String. Strip terminating NUL bytes. Decode bytes
|
|
// into characters. Search-and-replace EOLN.
|
|
} else if (String.class.equals(flavor.getRepresentationClass()) &&
|
|
isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
|
|
|
|
// A String holds all of its data in memory at one time, so
|
|
// we can't avoid reading the entire InputStream at this point.
|
|
if (bytes == null) {
|
|
bytes = inputStreamToByteArray(str);
|
|
}
|
|
str.close();
|
|
|
|
Long lFormat = new Long(format);
|
|
String charset = null;
|
|
if (isLocaleDependentTextFormat(format) &&
|
|
localeTransferable != null &&
|
|
localeTransferable.
|
|
isDataFlavorSupported(javaTextEncodingFlavor))
|
|
{
|
|
try {
|
|
charset = new String((byte[])localeTransferable.
|
|
getTransferData(javaTextEncodingFlavor),
|
|
"UTF-8");
|
|
} catch (UnsupportedFlavorException cannotHappen) {
|
|
}
|
|
} else {
|
|
charset = (String)nativeCharsets.get(lFormat);
|
|
}
|
|
|
|
if (charset == null) {
|
|
// Only happens when we have a custom text type.
|
|
charset = getDefaultTextCharset();
|
|
}
|
|
String eoln = (String)nativeEOLNs.get(lFormat);
|
|
Integer terminators = (Integer)nativeTerminators.get(lFormat);
|
|
|
|
int count;
|
|
|
|
// Locate terminating NUL bytes. Note that if terminators is 0,
|
|
// the we never added an entry to nativeTerminators anyway, so
|
|
// we'll skip code altogether.
|
|
if (terminators != null) {
|
|
int numTerminators = terminators.intValue();
|
|
search: for (count = 0;
|
|
count < (bytes.length - numTerminators + 1);
|
|
count += numTerminators)
|
|
{
|
|
for (int i = count; i < count + numTerminators; i++) {
|
|
if (bytes[i] != 0x0) {
|
|
continue search;
|
|
}
|
|
}
|
|
|
|
// found terminators
|
|
break search;
|
|
}
|
|
} else {
|
|
count = bytes.length;
|
|
}
|
|
|
|
// Decode text to chars. Don't include any terminators.
|
|
String converted = new String(bytes, 0, count, charset);
|
|
|
|
// Search and replace EOLN. Note that if EOLN is "\n", then we
|
|
// never added an entry to nativeEOLNs anyway, so we'll skip this
|
|
// code altogether.
|
|
if (eoln != null) {
|
|
|
|
/* Fix for 4463560: replace EOLNs symbol-by-symbol instead
|
|
* of using buf.replace()
|
|
*/
|
|
|
|
char[] buf = converted.toCharArray();
|
|
char[] eoln_arr = eoln.toCharArray();
|
|
converted = null;
|
|
int j = 0;
|
|
boolean match;
|
|
|
|
for (int i = 0; i < buf.length; ) {
|
|
// Catch last few bytes
|
|
if (i + eoln_arr.length > buf.length) {
|
|
buf[j++] = buf[i++];
|
|
continue;
|
|
}
|
|
|
|
match = true;
|
|
for (int k = 0, l = i; k < eoln_arr.length; k++, l++) {
|
|
if (eoln_arr[k] != buf[l]) {
|
|
match = false;
|
|
break;
|
|
}
|
|
}
|
|
if (match) {
|
|
buf[j++] = '\n';
|
|
i += eoln_arr.length;
|
|
} else {
|
|
buf[j++] = buf[i++];
|
|
}
|
|
}
|
|
converted = new String(buf, 0, j);
|
|
}
|
|
|
|
return converted;
|
|
|
|
// Special hack to maintain backwards-compatibility with the brokenness
|
|
// of StringSelection. Return a StringReader instead of an InputStream.
|
|
// Recur to obtain String and encapsulate.
|
|
} else if (DataFlavor.plainTextFlavor.equals(flavor)) {
|
|
return new StringReader
|
|
((String)translateBytesOrStream(str, bytes,
|
|
plainTextStringFlavor,
|
|
format, localeTransferable));
|
|
|
|
// Target data is an InputStream. For arbitrary flavors, just return
|
|
// the raw bytes. For text flavors, decode to strip terminators and
|
|
// search-and-replace EOLN, then reencode according to the requested
|
|
// flavor.
|
|
} else if (flavor.isRepresentationClassInputStream()) {
|
|
return translateBytesOrStreamToInputStream(str, flavor, format,
|
|
localeTransferable);
|
|
|
|
// Target data is a Reader. Obtain data in InputStream format, encoded
|
|
// as "Unicode" (utf-16be). Then use an InputStreamReader to decode
|
|
// back to chars on demand.
|
|
} else if (flavor.isRepresentationClassReader()) {
|
|
if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
|
|
throw new IOException
|
|
("cannot transfer non-text data as Reader");
|
|
}
|
|
|
|
InputStream is = (InputStream)
|
|
translateBytesOrStreamToInputStream
|
|
(str, DataFlavor.plainTextFlavor, format,
|
|
localeTransferable);
|
|
String unicode =
|
|
DataTransferer.getTextCharset(DataFlavor.plainTextFlavor);
|
|
Reader reader = new InputStreamReader(is, unicode);
|
|
|
|
return constructFlavoredObject(reader, flavor, Reader.class);
|
|
|
|
// Target data is a CharBuffer. Recur to obtain String and wrap.
|
|
} else if (flavor.isRepresentationClassCharBuffer()) {
|
|
if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
|
|
throw new IOException
|
|
("cannot transfer non-text data as CharBuffer");
|
|
}
|
|
|
|
CharBuffer buffer = CharBuffer.wrap
|
|
((String)translateBytesOrStream(str, bytes,
|
|
plainTextStringFlavor,
|
|
format, localeTransferable));
|
|
return constructFlavoredObject(buffer, flavor, CharBuffer.class);
|
|
|
|
// Target data is a char array. Recur to obtain String and convert to
|
|
// char array.
|
|
} else if (charArrayClass.equals(flavor.getRepresentationClass())) {
|
|
if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
|
|
throw new IOException
|
|
("cannot transfer non-text data as char array");
|
|
}
|
|
|
|
return ((String)translateBytesOrStream
|
|
(str, bytes, plainTextStringFlavor, format,
|
|
localeTransferable)).toCharArray();
|
|
|
|
// Target data is a ByteBuffer. For arbitrary flavors, just return
|
|
// the raw bytes. For text flavors, convert to a String to strip
|
|
// terminators and search-and-replace EOLN, then reencode according to
|
|
// the requested flavor.
|
|
} else if (flavor.isRepresentationClassByteBuffer()) {
|
|
if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
|
|
bytes = ((String)translateBytesOrStream
|
|
(str, bytes, plainTextStringFlavor, format,
|
|
localeTransferable)).getBytes
|
|
(DataTransferer.getTextCharset(flavor));
|
|
} else {
|
|
if (bytes == null) {
|
|
bytes = inputStreamToByteArray(str);
|
|
}
|
|
}
|
|
|
|
ByteBuffer buffer = ByteBuffer.wrap(bytes);
|
|
return constructFlavoredObject(buffer, flavor, ByteBuffer.class);
|
|
|
|
// Target data is a byte array. For arbitrary flavors, just return
|
|
// the raw bytes. For text flavors, convert to a String to strip
|
|
// terminators and search-and-replace EOLN, then reencode according to
|
|
// the requested flavor.
|
|
} else if (byteArrayClass.equals(flavor.getRepresentationClass())) {
|
|
if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
|
|
return ((String)translateBytesOrStream
|
|
(str, bytes, plainTextStringFlavor, format,
|
|
localeTransferable)).getBytes
|
|
(DataTransferer.getTextCharset(flavor));
|
|
} else {
|
|
return (bytes != null) ? bytes : inputStreamToByteArray(str);
|
|
}
|
|
|
|
// Target data is an RMI object
|
|
} /* else if (flavor.isRepresentationClassRemote()) {
|
|
try {
|
|
byte[] ba = inputStreamToByteArray(str);
|
|
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(ba));
|
|
Object ret = ((MarshalledObject)(ois.readObject())).get();
|
|
ois.close();
|
|
str.close();
|
|
return ret;
|
|
} catch (Exception e) {
|
|
throw new IOException(e.getMessage());
|
|
}
|
|
|
|
// Target data is Serializable
|
|
} else if (flavor.isRepresentationClassSerializable()) {
|
|
try {
|
|
byte[] ba = inputStreamToByteArray(str);
|
|
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(ba));
|
|
Object ret = ois.readObject();
|
|
ois.close();
|
|
str.close();
|
|
return ret;
|
|
} catch (Exception e) {
|
|
throw new IOException(e.getMessage());
|
|
}
|
|
|
|
// Target data is Image
|
|
} */ else if (DataFlavor.imageFlavor.equals(flavor)) {
|
|
if (!isImageFormat(format)) {
|
|
throw new IOException("data translation failed");
|
|
}
|
|
|
|
Image image = platformImageBytesOrStreamToImage(str, bytes, format);
|
|
str.close();
|
|
return image;
|
|
}
|
|
|
|
throw new IOException("data translation failed");
|
|
}
|
|
|
|
/**
|
|
* For arbitrary flavors, just use the raw InputStream. For text flavors,
|
|
* ReencodingInputStream will decode and reencode the InputStream on demand
|
|
* so that we can strip terminators and search-and-replace EOLN.
|
|
*/
|
|
private Object translateBytesOrStreamToInputStream
|
|
(InputStream str, DataFlavor flavor, long format,
|
|
Transferable localeTransferable) throws IOException
|
|
{
|
|
if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
|
|
str = new ReencodingInputStream
|
|
(str, format, DataTransferer.getTextCharset(flavor),
|
|
localeTransferable);
|
|
}
|
|
|
|
return constructFlavoredObject(str, flavor, InputStream.class);
|
|
}
|
|
|
|
/**
|
|
* We support representations which are exactly of the specified Class,
|
|
* and also arbitrary Objects which have a constructor which takes an
|
|
* instance of the Class as its sole parameter.
|
|
*/
|
|
private Object constructFlavoredObject(Object arg, DataFlavor flavor,
|
|
Class clazz)
|
|
throws IOException
|
|
{
|
|
final Class dfrc = flavor.getRepresentationClass();
|
|
|
|
if (clazz.equals(dfrc)) {
|
|
return arg; // simple case
|
|
} else {
|
|
Constructor[] constructors = null;
|
|
|
|
try {
|
|
constructors = (Constructor[])
|
|
AccessController.doPrivileged(new PrivilegedAction() {
|
|
public Object run() {
|
|
return dfrc.getConstructors();
|
|
}
|
|
});
|
|
} catch (SecurityException se) {
|
|
throw new IOException(se.getMessage());
|
|
}
|
|
|
|
Constructor constructor = null;
|
|
|
|
for (int j = 0; j < constructors.length; j++) {
|
|
if (!Modifier.isPublic(constructors[j].getModifiers())) {
|
|
continue;
|
|
}
|
|
|
|
Class[] ptypes = constructors[j].getParameterTypes();
|
|
|
|
if (ptypes != null && ptypes.length == 1 &&
|
|
clazz.equals(ptypes[0])) {
|
|
constructor = constructors[j];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (constructor == null) {
|
|
throw new IOException("can't find <init>(L"+ clazz +
|
|
";)V for class: " + dfrc.getName());
|
|
}
|
|
|
|
try {
|
|
return constructor.newInstance(new Object[] { arg } );
|
|
} catch (Exception e) {
|
|
throw new IOException(e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Used for decoding and reencoding an InputStream on demand so that we
|
|
* can strip NUL terminators and perform EOLN search-and-replace.
|
|
*/
|
|
public class ReencodingInputStream extends InputStream {
|
|
protected BufferedReader wrapped;
|
|
protected CharToByteConverter converter; // TODO remove
|
|
protected final char[] in = new char[1];
|
|
protected byte[] out;
|
|
|
|
protected CharsetEncoder encoder;
|
|
protected CharBuffer inBuf;
|
|
protected ByteBuffer outBuf;
|
|
|
|
protected char[] eoln;
|
|
protected int numTerminators;
|
|
|
|
protected boolean eos;
|
|
protected int index, limit;
|
|
|
|
public ReencodingInputStream(InputStream bytestream, long format,
|
|
String targetEncoding,
|
|
Transferable localeTransferable)
|
|
throws IOException
|
|
{
|
|
Long lFormat = new Long(format);
|
|
|
|
String sourceEncoding = null;
|
|
if (isLocaleDependentTextFormat(format) &&
|
|
localeTransferable != null &&
|
|
localeTransferable.
|
|
isDataFlavorSupported(javaTextEncodingFlavor))
|
|
{
|
|
try {
|
|
sourceEncoding = new String((byte[])localeTransferable.
|
|
getTransferData(javaTextEncodingFlavor),
|
|
"UTF-8");
|
|
} catch (UnsupportedFlavorException cannotHappen) {
|
|
}
|
|
} else {
|
|
sourceEncoding = (String)nativeCharsets.get(lFormat);
|
|
}
|
|
|
|
if (sourceEncoding == null) {
|
|
// Only happens when we have a custom text type.
|
|
sourceEncoding = getDefaultTextCharset();
|
|
}
|
|
wrapped = new BufferedReader
|
|
(new InputStreamReader(bytestream, sourceEncoding));
|
|
|
|
// TODO
|
|
//
|
|
// Remove converter. Use encoder only.
|
|
try {
|
|
converter = CharToByteConverter.getConverter(targetEncoding);
|
|
out = new byte[converter.getMaxBytesPerChar()];
|
|
} catch (UnsupportedEncodingException e) {
|
|
try {
|
|
encoder = Charset.forName(targetEncoding).newEncoder();
|
|
out = new byte[(int)(encoder.maxBytesPerChar() + 0.5)];
|
|
inBuf = CharBuffer.wrap(in);
|
|
outBuf = ByteBuffer.wrap(out);
|
|
} catch (IllegalCharsetNameException ee) {
|
|
throw e;
|
|
} catch (UnsupportedCharsetException ee) {
|
|
throw e;
|
|
} catch (UnsupportedOperationException ee) {
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
String sEoln = (String)nativeEOLNs.get(lFormat);
|
|
if (sEoln != null) {
|
|
eoln = sEoln.toCharArray();
|
|
}
|
|
|
|
// A hope and a prayer that this works generically. This will
|
|
// definitely work on Win32.
|
|
Integer terminators = (Integer)nativeTerminators.get(lFormat);
|
|
if (terminators != null) {
|
|
numTerminators = terminators.intValue();
|
|
}
|
|
}
|
|
|
|
public int read() throws IOException {
|
|
if (eos) {
|
|
return -1;
|
|
}
|
|
|
|
if (index >= limit) {
|
|
int c = wrapped.read();
|
|
|
|
if (c == -1) { // -1 is EOS
|
|
eos = true;
|
|
return -1;
|
|
}
|
|
|
|
// "c == 0" is not quite correct, but good enough on Windows.
|
|
if (numTerminators > 0 && c == 0) {
|
|
eos = true;
|
|
return -1;
|
|
} else if (eoln != null && matchCharArray(eoln, c)) {
|
|
c = '\n' & 0xFFFF;
|
|
}
|
|
|
|
in[0] = (char)c;
|
|
|
|
// TODO
|
|
//
|
|
// Remove converter. Use encoder only.
|
|
if (converter != null) {
|
|
limit = converter.convert(in, 0, 1, out, 0, out.length);
|
|
} else /* if (encoder != null) */ {
|
|
inBuf.rewind();
|
|
outBuf.rewind();
|
|
encoder.encode(inBuf, outBuf, false);
|
|
limit = outBuf.limit();
|
|
}
|
|
|
|
index = 0;
|
|
|
|
return read();
|
|
} else {
|
|
return out[index++] & 0xFF;
|
|
}
|
|
}
|
|
|
|
public int available() throws IOException {
|
|
return ((eos) ? 0 : (limit - index));
|
|
}
|
|
|
|
public void close() throws IOException {
|
|
wrapped.close();
|
|
}
|
|
|
|
/**
|
|
* Checks to see if the next array.length characters in wrapped
|
|
* match array. The first character is provided as c. Subsequent
|
|
* characters are read from wrapped itself. When this method returns,
|
|
* the wrapped index may be different from what it was when this
|
|
* method was called.
|
|
*/
|
|
private boolean matchCharArray(char[] array, int c)
|
|
throws IOException
|
|
{
|
|
wrapped.mark(array.length); // BufferedReader supports mark
|
|
|
|
int count = 0;
|
|
if ((char)c == array[0]) {
|
|
for (count = 1; count < array.length; count++) {
|
|
c = wrapped.read();
|
|
if (c == -1 || ((char)c) != array[count]) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count == array.length) {
|
|
return true;
|
|
} else {
|
|
wrapped.reset();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decodes a byte array into a set of String filenames.
|
|
*/
|
|
private native String[] dragQueryFile(byte[] bytes);
|
|
|
|
/**
|
|
* Translates either a byte array or an input stream which contain
|
|
* platform-specific image data in the given format into an Image.
|
|
*/
|
|
protected abstract Image platformImageBytesOrStreamToImage(InputStream str,
|
|
byte[] bytes,
|
|
long format)
|
|
throws IOException;
|
|
|
|
/**
|
|
* Translates either a byte array or an input stream which contain
|
|
* an image data in the given standard format into an Image.
|
|
*
|
|
* @param mimeType image MIME type, such as: image/png, image/jpeg, image/gif
|
|
*/
|
|
protected Image standardImageBytesOrStreamToImage(InputStream inputStream,
|
|
byte[] bytes,
|
|
String mimeType)
|
|
throws IOException {
|
|
if (inputStream == null) {
|
|
inputStream = new ByteArrayInputStream(bytes);
|
|
}
|
|
|
|
Iterator readerIterator = ImageIO.getImageReadersByMIMEType(mimeType);
|
|
|
|
if (!readerIterator.hasNext()) {
|
|
throw new IOException("No registered service provider can decode " +
|
|
" an image from " + mimeType);
|
|
}
|
|
|
|
IOException ioe = null;
|
|
|
|
while (readerIterator.hasNext()) {
|
|
ImageReader imageReader = (ImageReader)readerIterator.next();
|
|
try {
|
|
ImageInputStream imageInputStream =
|
|
ImageIO.createImageInputStream(inputStream);
|
|
|
|
try {
|
|
ImageReadParam param = imageReader.getDefaultReadParam();
|
|
imageReader.setInput(imageInputStream, true, true);
|
|
BufferedImage bufferedImage =
|
|
imageReader.read(imageReader.getMinIndex(), param);
|
|
if (bufferedImage != null) {
|
|
return bufferedImage;
|
|
}
|
|
} finally {
|
|
imageInputStream.close();
|
|
imageReader.dispose();
|
|
}
|
|
} catch (IOException e) {
|
|
ioe = e;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (ioe == null) {
|
|
ioe = new IOException("Registered service providers failed to decode"
|
|
+ " an image from " + mimeType);
|
|
}
|
|
|
|
throw ioe;
|
|
}
|
|
|
|
/**
|
|
* Translates a Java Image into a byte array which contains platform-
|
|
* specific image data in the given format.
|
|
*/
|
|
protected abstract byte[] imageToPlatformBytes(Image image, long format)
|
|
throws IOException;
|
|
|
|
/**
|
|
* Translates a Java Image into a byte array which contains
|
|
* an image data in the given standard format.
|
|
*
|
|
* @param mimeType image MIME type, such as: image/png, image/jpeg
|
|
*/
|
|
protected byte[] imageToStandardBytes(Image image, String mimeType)
|
|
throws IOException {
|
|
RenderedImage renderedImage = null;
|
|
|
|
Iterator writerIterator = ImageIO.getImageWritersByMIMEType(mimeType);
|
|
|
|
if (!writerIterator.hasNext()) {
|
|
throw new IOException("No registered service provider can encode " +
|
|
" an image to " + mimeType);
|
|
}
|
|
|
|
if (image instanceof RenderedImage) {
|
|
renderedImage = (RenderedImage)image;
|
|
} else {
|
|
int width = 0;
|
|
int height = 0;
|
|
if (image instanceof sun.awt.image.Image) {
|
|
ImageRepresentation ir = ((sun.awt.image.Image)image).getImageRep();
|
|
ir.reconstruct(ImageObserver.ALLBITS);
|
|
width = ir.getWidth();
|
|
height = ir.getHeight();
|
|
} else {
|
|
width = image.getWidth(null);
|
|
height = image.getHeight(null);
|
|
}
|
|
|
|
ColorModel model = ColorModel.getRGBdefault();
|
|
WritableRaster raster =
|
|
model.createCompatibleWritableRaster(width, height);
|
|
|
|
BufferedImage bufferedImage =
|
|
new BufferedImage(model, raster, model.isAlphaPremultiplied(),
|
|
null);
|
|
|
|
Graphics g = bufferedImage.getGraphics();
|
|
try {
|
|
g.drawImage(image, 0, 0, width, height, null);
|
|
} finally {
|
|
g.dispose();
|
|
}
|
|
|
|
renderedImage = bufferedImage;
|
|
}
|
|
|
|
ImageTypeSpecifier typeSpecifier =
|
|
new ImageTypeSpecifier(renderedImage);
|
|
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
IOException ioe = null;
|
|
|
|
while (writerIterator.hasNext()) {
|
|
ImageWriter imageWriter = (ImageWriter)writerIterator.next();
|
|
ImageWriterSpi writerSpi = imageWriter.getOriginatingProvider();
|
|
|
|
if (!writerSpi.canEncodeImage(typeSpecifier)) {
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
ImageOutputStream imageOutputStream =
|
|
ImageIO.createImageOutputStream(baos);
|
|
try {
|
|
imageWriter.setOutput(imageOutputStream);
|
|
imageWriter.write(renderedImage);
|
|
imageOutputStream.flush();
|
|
} finally {
|
|
imageOutputStream.close();
|
|
}
|
|
} catch (IOException e) {
|
|
imageWriter.dispose();
|
|
baos.reset();
|
|
ioe = e;
|
|
continue;
|
|
}
|
|
|
|
imageWriter.dispose();
|
|
baos.close();
|
|
return baos.toByteArray();
|
|
}
|
|
|
|
baos.close();
|
|
|
|
if (ioe == null) {
|
|
ioe = new IOException("Registered service providers failed to encode "
|
|
+ renderedImage + " to " + mimeType);
|
|
}
|
|
|
|
throw ioe;
|
|
}
|
|
|
|
/**
|
|
* Concatenates the data represented by two objects. Objects can be either
|
|
* byte arrays or instances of <code>InputStream</code>. If both arguments
|
|
* are byte arrays byte array will be returned. Otherwise an
|
|
* <code>InputStream</code> will be returned.
|
|
* <p>
|
|
* Currently is only called from native code to prepend palette data to
|
|
* platform-specific image data during image transfer on Win32.
|
|
*
|
|
* @param obj1 the first object to be concatenated.
|
|
* @param obj2 the second object to be concatenated.
|
|
* @return a byte array or an <code>InputStream</code> which represents
|
|
* a logical concatenation of the two arguments.
|
|
* @throws NullPointerException is either of the arguments is
|
|
* <code>null</code>
|
|
* @throws ClassCastException is either of the arguments is
|
|
* neither byte array nor an instance of <code>InputStream</code>.
|
|
*/
|
|
private Object concatData(Object obj1, Object obj2) {
|
|
InputStream str1 = null;
|
|
InputStream str2 = null;
|
|
|
|
if (obj1 instanceof byte[]) {
|
|
byte[] arr1 = (byte[])obj1;
|
|
if (obj2 instanceof byte[]) {
|
|
byte[] arr2 = (byte[])obj2;
|
|
byte[] ret = new byte[arr1.length + arr2.length];
|
|
System.arraycopy(arr1, 0, ret, 0, arr1.length);
|
|
System.arraycopy(arr2, 0, ret, arr1.length, arr2.length);
|
|
return ret;
|
|
} else {
|
|
str1 = new ByteArrayInputStream(arr1);
|
|
str2 = (InputStream)obj2;
|
|
}
|
|
} else {
|
|
str1 = (InputStream)obj1;
|
|
if (obj2 instanceof byte[]) {
|
|
str2 = new ByteArrayInputStream((byte[])obj2);
|
|
} else {
|
|
str2 = (InputStream)obj2;
|
|
}
|
|
}
|
|
|
|
return new SequenceInputStream(str1, str2);
|
|
}
|
|
|
|
public byte[] convertData(final Object source,
|
|
final Transferable contents,
|
|
final long format,
|
|
final Map formatMap,
|
|
final boolean isToolkitThread)
|
|
throws IOException
|
|
{
|
|
byte[] ret = null;
|
|
|
|
/*
|
|
* If the current thread is the Toolkit thread we should post a
|
|
* Runnable to the event dispatch thread associated with source Object,
|
|
* since translateTransferable() calls Transferable.getTransferData()
|
|
* that may contain client code.
|
|
*/
|
|
if (isToolkitThread) try {
|
|
final Stack stack = new Stack();
|
|
final Runnable dataConverter = new Runnable() {
|
|
// Guard against multiple executions.
|
|
private boolean done = false;
|
|
public void run() {
|
|
if (done) {
|
|
return;
|
|
}
|
|
byte[] data = null;
|
|
try {
|
|
DataFlavor flavor = (DataFlavor)formatMap.get(new Long(format));
|
|
if (flavor != null) {
|
|
data = translateTransferable(contents, flavor, format);
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
data = null;
|
|
}
|
|
try {
|
|
getToolkitThreadBlockedHandler().lock();
|
|
stack.push(data);
|
|
getToolkitThreadBlockedHandler().exit();
|
|
} finally {
|
|
getToolkitThreadBlockedHandler().unlock();
|
|
done = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
final AppContext appContext = SunToolkit.targetToAppContext(source);
|
|
|
|
getToolkitThreadBlockedHandler().lock();
|
|
|
|
if (appContext != null) {
|
|
appContext.put(DATA_CONVERTER_KEY, dataConverter);
|
|
}
|
|
|
|
SunToolkit.executeOnEventHandlerThread(source, dataConverter);
|
|
|
|
while (stack.empty()) {
|
|
getToolkitThreadBlockedHandler().enter();
|
|
}
|
|
|
|
if (appContext != null) {
|
|
appContext.remove(DATA_CONVERTER_KEY);
|
|
}
|
|
|
|
ret = (byte[])stack.pop();
|
|
} finally {
|
|
getToolkitThreadBlockedHandler().unlock();
|
|
} else {
|
|
DataFlavor flavor = (DataFlavor)
|
|
formatMap.get(new Long(format));
|
|
if (flavor != null) {
|
|
ret = translateTransferable(contents, flavor, format);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
public void processDataConversionRequests() {
|
|
if (EventQueue.isDispatchThread()) {
|
|
AppContext appContext = AppContext.getAppContext();
|
|
getToolkitThreadBlockedHandler().lock();
|
|
try {
|
|
Runnable dataConverter =
|
|
(Runnable)appContext.get(DATA_CONVERTER_KEY);
|
|
if (dataConverter != null) {
|
|
dataConverter.run();
|
|
appContext.remove(DATA_CONVERTER_KEY);
|
|
}
|
|
} finally {
|
|
getToolkitThreadBlockedHandler().unlock();
|
|
}
|
|
}
|
|
}
|
|
|
|
public abstract ToolkitThreadBlockedHandler
|
|
getToolkitThreadBlockedHandler();
|
|
|
|
/**
|
|
* Helper function to reduce a Map with Long keys to a long array.
|
|
*/
|
|
public static long[] keysToLongArray(Map map) {
|
|
Set keySet = map.keySet();
|
|
long[] retval = new long[keySet.size()];
|
|
int i = 0;
|
|
for (Iterator iter = keySet.iterator(); iter.hasNext(); i++) {
|
|
retval[i] = ((Long)iter.next()).longValue();
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* Helper function to reduce a Map with DataFlavor keys to a DataFlavor
|
|
* array. The array will be sorted according to
|
|
* <code>DataFlavorComparator</code>.
|
|
*/
|
|
public static DataFlavor[] keysToDataFlavorArray(Map map) {
|
|
return setToSortedDataFlavorArray(map.keySet(), map);
|
|
}
|
|
|
|
/**
|
|
* Helper function to convert a Set of DataFlavors to a sorted array.
|
|
* The array will be sorted according to <code>DataFlavorComparator</code>.
|
|
*/
|
|
public static DataFlavor[] setToSortedDataFlavorArray(Set flavorsSet) {
|
|
DataFlavor[] flavors = new DataFlavor[flavorsSet.size()];
|
|
flavorsSet.toArray(flavors);
|
|
Arrays.sort(flavors, defaultFlavorComparator);
|
|
return flavors;
|
|
}
|
|
|
|
/**
|
|
* Helper function to convert a Set of DataFlavors to a sorted array.
|
|
* The array will be sorted according to a
|
|
* <code>DataFlavorComparator</code> created with the specified
|
|
* flavor-to-native map as an argument.
|
|
*/
|
|
public static DataFlavor[] setToSortedDataFlavorArray
|
|
(Set flavorsSet, Map flavorToNativeMap)
|
|
{
|
|
DataFlavor[] flavors = new DataFlavor[flavorsSet.size()];
|
|
flavorsSet.toArray(flavors);
|
|
Comparator comparator =
|
|
new DataFlavorComparator(flavorToNativeMap,
|
|
IndexedComparator.SELECT_WORST);
|
|
Arrays.sort(flavors, comparator);
|
|
return flavors;
|
|
}
|
|
|
|
/**
|
|
* Helper function to convert an InputStream to a byte[] array.
|
|
*/
|
|
protected static byte[] inputStreamToByteArray(InputStream str)
|
|
throws IOException
|
|
{
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
int len = 0;
|
|
byte[] buf = new byte[8192];
|
|
|
|
while ((len = str.read(buf)) != -1) {
|
|
baos.write(buf, 0, len);
|
|
}
|
|
|
|
return baos.toByteArray();
|
|
}
|
|
|
|
private static final CharsetComparator defaultCharsetComparator =
|
|
new CharsetComparator(IndexedComparator.SELECT_WORST);
|
|
private static final DataFlavorComparator defaultFlavorComparator =
|
|
new DataFlavorComparator(IndexedComparator.SELECT_WORST);
|
|
|
|
/**
|
|
* A Comparator which includes a helper function for comparing two Objects
|
|
* which are likely to be keys in the specified Map.
|
|
*/
|
|
public abstract static class IndexedComparator implements Comparator {
|
|
|
|
/**
|
|
* The best Object (e.g., DataFlavor) will be the last in sequence.
|
|
*/
|
|
public static final boolean SELECT_BEST = true;
|
|
|
|
/**
|
|
* The best Object (e.g., DataFlavor) will be the first in sequence.
|
|
*/
|
|
public static final boolean SELECT_WORST = false;
|
|
|
|
protected final boolean order;
|
|
|
|
public IndexedComparator() {
|
|
this(SELECT_BEST);
|
|
}
|
|
|
|
public IndexedComparator(boolean order) {
|
|
this.order = order;
|
|
}
|
|
|
|
/**
|
|
* Helper method to compare two objects by their Integer indices in the
|
|
* given map. If the map doesn't contain an entry for either of the
|
|
* objects, the fallback index will be used for the object instead.
|
|
*
|
|
* @param indexMap the map which maps objects into Integer indexes.
|
|
* @param obj1 the first object to be compared.
|
|
* @param obj2 the second object to be compared.
|
|
* @param fallbackIndex the Integer to be used as a fallback index.
|
|
* @return a negative integer, zero, or a positive integer as the
|
|
* first object is mapped to a less, equal to, or greater
|
|
* index than the second.
|
|
*/
|
|
protected static int compareIndices(Map indexMap,
|
|
Object obj1, Object obj2,
|
|
Integer fallbackIndex) {
|
|
Integer index1 = (Integer)indexMap.get(obj1);
|
|
Integer index2 = (Integer)indexMap.get(obj2);
|
|
|
|
if (index1 == null) {
|
|
index1 = fallbackIndex;
|
|
}
|
|
if (index2 == null) {
|
|
index2 = fallbackIndex;
|
|
}
|
|
|
|
return index1.compareTo(index2);
|
|
}
|
|
|
|
/**
|
|
* Helper method to compare two objects by their Long indices in the
|
|
* given map. If the map doesn't contain an entry for either of the
|
|
* objects, the fallback index will be used for the object instead.
|
|
*
|
|
* @param indexMap the map which maps objects into Long indexes.
|
|
* @param obj1 the first object to be compared.
|
|
* @param obj2 the second object to be compared.
|
|
* @param fallbackIndex the Long to be used as a fallback index.
|
|
* @return a negative integer, zero, or a positive integer as the
|
|
* first object is mapped to a less, equal to, or greater
|
|
* index than the second.
|
|
*/
|
|
protected static int compareLongs(Map indexMap,
|
|
Object obj1, Object obj2,
|
|
Long fallbackIndex) {
|
|
Long index1 = (Long)indexMap.get(obj1);
|
|
Long index2 = (Long)indexMap.get(obj2);
|
|
|
|
if (index1 == null) {
|
|
index1 = fallbackIndex;
|
|
}
|
|
if (index2 == null) {
|
|
index2 = fallbackIndex;
|
|
}
|
|
|
|
return index1.compareTo(index2);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An IndexedComparator which compares two String charsets. The comparison
|
|
* follows the rules outlined in DataFlavor.selectBestTextFlavor. In order
|
|
* to ensure that non-Unicode, non-ASCII, non-default charsets are sorted
|
|
* in alphabetical order, charsets are not automatically converted to their
|
|
* canonical forms.
|
|
*/
|
|
public static class CharsetComparator extends IndexedComparator {
|
|
private static final Map charsets;
|
|
private static String defaultEncoding;
|
|
|
|
private static final Integer DEFAULT_CHARSET_INDEX = new Integer(2);
|
|
private static final Integer OTHER_CHARSET_INDEX = new Integer(1);
|
|
private static final Integer WORST_CHARSET_INDEX = new Integer(0);
|
|
private static final Integer UNSUPPORTED_CHARSET_INDEX =
|
|
new Integer(Integer.MIN_VALUE);
|
|
|
|
private static final String UNSUPPORTED_CHARSET = "UNSUPPORTED";
|
|
|
|
static {
|
|
HashMap charsetsMap = new HashMap(8, 1.0f);
|
|
|
|
// we prefer Unicode charsets
|
|
//
|
|
// TODO
|
|
//
|
|
// Canonical names will change when we switch to java.nio
|
|
charsetsMap.put("Unicode", new Integer(3));
|
|
charsetsMap.put("UnicodeLittleUnmarked", new Integer(4));
|
|
charsetsMap.put("UnicodeBigUnmarked", new Integer(5));
|
|
charsetsMap.put("UTF8", new Integer(6));
|
|
charsetsMap.put("UTF16", new Integer(7));
|
|
|
|
// ASCII is the worst charset supported
|
|
charsetsMap.put("ASCII", WORST_CHARSET_INDEX);
|
|
|
|
String defEncoding = DataTransferer.canonicalName
|
|
(DataTransferer.getDefaultTextCharset());
|
|
|
|
if (charsetsMap.get(defaultEncoding) == null) {
|
|
charsetsMap.put(defaultEncoding, DEFAULT_CHARSET_INDEX);
|
|
}
|
|
charsetsMap.put(UNSUPPORTED_CHARSET, UNSUPPORTED_CHARSET_INDEX);
|
|
|
|
charsets = Collections.unmodifiableMap(charsetsMap);
|
|
}
|
|
|
|
public CharsetComparator() {
|
|
this(SELECT_BEST);
|
|
}
|
|
|
|
public CharsetComparator(boolean order) {
|
|
super(order);
|
|
}
|
|
|
|
/**
|
|
* Compares two String objects. Returns a negative integer, zero,
|
|
* or a positive integer as the first charset is worse than, equal to,
|
|
* or better than the second.
|
|
*
|
|
* @param obj1 the first charset to be compared
|
|
* @param obj2 the second charset to be compared
|
|
* @return a negative integer, zero, or a positive integer as the
|
|
* first argument is worse, equal to, or better than the
|
|
* second.
|
|
* @throws ClassCastException if either of the arguments is not
|
|
* instance of String
|
|
* @throws NullPointerException if either of the arguments is
|
|
* <code>null</code>.
|
|
*/
|
|
public int compare(Object obj1, Object obj2) {
|
|
String charset1 = null;
|
|
String charset2 = null;
|
|
if (order == SELECT_BEST) {
|
|
charset1 = (String)obj1;
|
|
charset2 = (String)obj2;
|
|
} else {
|
|
charset1 = (String)obj2;
|
|
charset2 = (String)obj1;
|
|
}
|
|
|
|
return compareCharsets(charset1, charset2);
|
|
}
|
|
|
|
/**
|
|
* Compares charsets. Returns a negative integer, zero, or a positive
|
|
* integer as the first charset is worse than, equal to, or better than
|
|
* the second.
|
|
* <p>
|
|
* Charsets are ordered according to the following rules:
|
|
* <ul>
|
|
* <li>All unsupported charsets are equal.
|
|
* <li>Any unsupported charset is worse than any supported charset.
|
|
* <li>Unicode charsets, such as "UTF-16", "UTF-8", "UTF-16BE" and
|
|
* "UTF-16LE", are considered best.
|
|
* <li>After them, platform default charset is selected.
|
|
* <li>"US-ASCII" is the worst of supported charsets.
|
|
* <li>For all other supported charsets, the lexicographically less
|
|
* one is considered the better.
|
|
* </ul>
|
|
*
|
|
* @param charset1 the first charset to be compared
|
|
* @param charset2 the second charset to be compared.
|
|
* @return a negative integer, zero, or a positive integer as the
|
|
* first argument is worse, equal to, or better than the
|
|
* second.
|
|
*/
|
|
protected int compareCharsets(String charset1, String charset2) {
|
|
charset1 = getEncoding(charset1);
|
|
charset2 = getEncoding(charset2);
|
|
|
|
int comp = compareIndices(charsets, charset1, charset2,
|
|
OTHER_CHARSET_INDEX);
|
|
|
|
if (comp == 0) {
|
|
return charset2.compareTo(charset1);
|
|
}
|
|
|
|
return comp;
|
|
}
|
|
|
|
/**
|
|
* Returns encoding for the specified charset according to the
|
|
* following rules:
|
|
* <ul>
|
|
* <li>If the charset is <code>null</code>, then <code>null</code> will
|
|
* be returned.
|
|
* <li>Iff the charset specifies an encoding unsupported by this JRE,
|
|
* <code>UNSUPPORTED_CHARSET</code> will be returned.
|
|
* <li>If the charset specifies an alias name, the corresponding
|
|
* canonical name will be returned iff the charset is a known
|
|
* Unicode, ASCII, or default charset.
|
|
* </ul>
|
|
*
|
|
* @param charset the charset.
|
|
* @return an encoding for this charset.
|
|
*/
|
|
protected static String getEncoding(String charset) {
|
|
if (charset == null) {
|
|
return null;
|
|
} else if (!DataTransferer.isEncodingSupported(charset)) {
|
|
return UNSUPPORTED_CHARSET;
|
|
} else {
|
|
// Only convert to canonical form if the charset is one
|
|
// of the charsets explicitly listed in the known charsets
|
|
// map. This will happen only for Unicode, ASCII, or default
|
|
// charsets.
|
|
String canonicalName = DataTransferer.canonicalName(charset);
|
|
return (charsets.containsKey(canonicalName))
|
|
? canonicalName
|
|
: charset;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An IndexedComparator which compares two DataFlavors. For text flavors,
|
|
* the comparison follows the rules outlined in
|
|
* DataFlavor.selectBestTextFlavor. For non-text flavors, unknown
|
|
* application MIME types are preferred, followed by known
|
|
* application/x-java-* MIME types. Unknown application types are preferred
|
|
* because if the user provides his own data flavor, it will likely be the
|
|
* most descriptive one. For flavors which are otherwise equal, the
|
|
* flavors' native formats are compared, with greater long values
|
|
* taking precedence.
|
|
*/
|
|
public static class DataFlavorComparator extends IndexedComparator {
|
|
protected final Map flavorToFormatMap;
|
|
|
|
private final CharsetComparator charsetComparator;
|
|
|
|
private static final Map exactTypes;
|
|
private static final Map primaryTypes;
|
|
private static final Map nonTextRepresentations;
|
|
private static final Map textTypes;
|
|
private static final Map decodedTextRepresentations;
|
|
private static final Map encodedTextRepresentations;
|
|
|
|
private static final Integer UNKNOWN_OBJECT_LOSES =
|
|
new Integer(Integer.MIN_VALUE);
|
|
private static final Integer UNKNOWN_OBJECT_WINS =
|
|
new Integer(Integer.MAX_VALUE);
|
|
|
|
private static final Long UNKNOWN_OBJECT_LOSES_L =
|
|
new Long(Long.MIN_VALUE);
|
|
private static final Long UNKNOWN_OBJECT_WINS_L =
|
|
new Long(Long.MAX_VALUE);
|
|
|
|
static {
|
|
{
|
|
HashMap exactTypesMap = new HashMap(4, 1.0f);
|
|
|
|
// application/x-java-* MIME types
|
|
exactTypesMap.put("application/x-java-file-list",
|
|
new Integer(0));
|
|
exactTypesMap.put("application/x-java-serialized-object",
|
|
new Integer(1));
|
|
exactTypesMap.put("application/x-java-jvm-local-objectref",
|
|
new Integer(2));
|
|
exactTypesMap.put("application/x-java-remote-object",
|
|
new Integer(3));
|
|
|
|
exactTypes = Collections.unmodifiableMap(exactTypesMap);
|
|
}
|
|
|
|
{
|
|
HashMap primaryTypesMap = new HashMap(1, 1.0f);
|
|
|
|
primaryTypesMap.put("application", new Integer(0));
|
|
|
|
primaryTypes = Collections.unmodifiableMap(primaryTypesMap);
|
|
}
|
|
|
|
{
|
|
HashMap nonTextRepresentationsMap = new HashMap(3, 1.0f);
|
|
|
|
nonTextRepresentationsMap.put(java.io.InputStream.class,
|
|
new Integer(0));
|
|
nonTextRepresentationsMap.put(java.io.Serializable.class,
|
|
new Integer(1));
|
|
nonTextRepresentationsMap.put(java.rmi.Remote.class,
|
|
new Integer(2));
|
|
|
|
nonTextRepresentations =
|
|
Collections.unmodifiableMap(nonTextRepresentationsMap);
|
|
}
|
|
|
|
{
|
|
HashMap textTypesMap = new HashMap(16, 1.0f);
|
|
|
|
// plain text
|
|
textTypesMap.put("text/plain", new Integer(0));
|
|
|
|
// stringFlavor
|
|
textTypesMap.put("application/x-java-serialized-object",
|
|
new Integer(1));
|
|
|
|
// misc
|
|
textTypesMap.put("text/calendar", new Integer(2));
|
|
textTypesMap.put("text/css", new Integer(3));
|
|
textTypesMap.put("text/directory", new Integer(4));
|
|
textTypesMap.put("text/parityfec", new Integer(5));
|
|
textTypesMap.put("text/rfc822-headers", new Integer(6));
|
|
textTypesMap.put("text/t140", new Integer(7));
|
|
textTypesMap.put("text/tab-separated-values", new Integer(8));
|
|
textTypesMap.put("text/uri-list", new Integer(9));
|
|
|
|
// enriched
|
|
textTypesMap.put("text/richtext", new Integer(10));
|
|
textTypesMap.put("text/enriched", new Integer(11));
|
|
textTypesMap.put("text/rtf", new Integer(12));
|
|
|
|
// markup
|
|
textTypesMap.put("text/html", new Integer(13));
|
|
textTypesMap.put("text/xml", new Integer(14));
|
|
textTypesMap.put("text/sgml", new Integer(15));
|
|
|
|
textTypes = Collections.unmodifiableMap(textTypesMap);
|
|
}
|
|
|
|
{
|
|
HashMap decodedTextRepresentationsMap = new HashMap(4, 1.0f);
|
|
|
|
decodedTextRepresentationsMap.put
|
|
(DataTransferer.charArrayClass, new Integer(0));
|
|
decodedTextRepresentationsMap.put
|
|
(java.nio.CharBuffer.class, new Integer(1));
|
|
decodedTextRepresentationsMap.put
|
|
(java.lang.String.class, new Integer(2));
|
|
decodedTextRepresentationsMap.put
|
|
(java.io.Reader.class, new Integer(3));
|
|
|
|
decodedTextRepresentations =
|
|
Collections.unmodifiableMap(decodedTextRepresentationsMap);
|
|
}
|
|
|
|
{
|
|
HashMap encodedTextRepresentationsMap = new HashMap(3, 1.0f);
|
|
|
|
encodedTextRepresentationsMap.put
|
|
(DataTransferer.byteArrayClass, new Integer(0));
|
|
encodedTextRepresentationsMap.put
|
|
(java.nio.ByteBuffer.class, new Integer(1));
|
|
encodedTextRepresentationsMap.put
|
|
(java.io.InputStream.class, new Integer(2));
|
|
|
|
encodedTextRepresentations =
|
|
Collections.unmodifiableMap(encodedTextRepresentationsMap);
|
|
}
|
|
}
|
|
|
|
public DataFlavorComparator() {
|
|
this(SELECT_BEST);
|
|
}
|
|
|
|
public DataFlavorComparator(boolean order) {
|
|
super(order);
|
|
|
|
charsetComparator = new CharsetComparator(order);
|
|
flavorToFormatMap = Collections.EMPTY_MAP;
|
|
}
|
|
|
|
public DataFlavorComparator(Map map) {
|
|
this(map, SELECT_BEST);
|
|
}
|
|
|
|
public DataFlavorComparator(Map map, boolean order) {
|
|
super(order);
|
|
|
|
charsetComparator = new CharsetComparator(order);
|
|
HashMap hashMap = new HashMap(map.size());
|
|
hashMap.putAll(map);
|
|
flavorToFormatMap = Collections.unmodifiableMap(hashMap);
|
|
}
|
|
|
|
public int compare(Object obj1, Object obj2) {
|
|
DataFlavor flavor1 = null;
|
|
DataFlavor flavor2 = null;
|
|
if (order == SELECT_BEST) {
|
|
flavor1 = (DataFlavor)obj1;
|
|
flavor2 = (DataFlavor)obj2;
|
|
} else {
|
|
flavor1 = (DataFlavor)obj2;
|
|
flavor2 = (DataFlavor)obj1;
|
|
}
|
|
|
|
if (flavor1.equals(flavor2)) {
|
|
return 0;
|
|
}
|
|
|
|
int comp = 0;
|
|
|
|
String primaryType1 = flavor1.getPrimaryType();
|
|
String subType1 = flavor1.getSubType();
|
|
String mimeType1 = primaryType1 + "/" + subType1;
|
|
Class class1 = flavor1.getRepresentationClass();
|
|
|
|
String primaryType2 = flavor2.getPrimaryType();
|
|
String subType2 = flavor2.getSubType();
|
|
String mimeType2 = primaryType2 + "/" + subType2;
|
|
Class class2 = flavor2.getRepresentationClass();
|
|
|
|
if (flavor1.isFlavorTextType() && flavor2.isFlavorTextType()) {
|
|
// First, compare MIME types
|
|
comp = compareIndices(textTypes, mimeType1, mimeType2,
|
|
UNKNOWN_OBJECT_LOSES);
|
|
if (comp != 0) {
|
|
return comp;
|
|
}
|
|
|
|
// Only need to test one flavor because they both have the
|
|
// same MIME type. Also don't need to worry about accidentally
|
|
// passing stringFlavor because either
|
|
// 1. Both flavors are stringFlavor, in which case the
|
|
// equality test at the top of the function succeeded.
|
|
// 2. Only one flavor is stringFlavor, in which case the MIME
|
|
// type comparison returned a non-zero value.
|
|
if (doesSubtypeSupportCharset(flavor1)) {
|
|
// Next, prefer the decoded text representations of Reader,
|
|
// String, CharBuffer, and [C, in that order.
|
|
comp = compareIndices(decodedTextRepresentations, class1,
|
|
class2, UNKNOWN_OBJECT_LOSES);
|
|
if (comp != 0) {
|
|
return comp;
|
|
}
|
|
|
|
// Next, compare charsets
|
|
comp = charsetComparator.compareCharsets
|
|
(DataTransferer.getTextCharset(flavor1),
|
|
DataTransferer.getTextCharset(flavor2));
|
|
if (comp != 0) {
|
|
return comp;
|
|
}
|
|
}
|
|
|
|
// Finally, prefer the encoded text representations of
|
|
// InputStream, ByteBuffer, and [B, in that order.
|
|
comp = compareIndices(encodedTextRepresentations, class1,
|
|
class2, UNKNOWN_OBJECT_LOSES);
|
|
if (comp != 0) {
|
|
return comp;
|
|
}
|
|
} else {
|
|
// First, prefer application types.
|
|
comp = compareIndices(primaryTypes, primaryType1, primaryType2,
|
|
UNKNOWN_OBJECT_LOSES);
|
|
if (comp != 0) {
|
|
return comp;
|
|
}
|
|
|
|
// Next, look for application/x-java-* types. Prefer unknown
|
|
// MIME types because if the user provides his own data flavor,
|
|
// it will likely be the most descriptive one.
|
|
comp = compareIndices(exactTypes, mimeType1, mimeType2,
|
|
UNKNOWN_OBJECT_WINS);
|
|
if (comp != 0) {
|
|
return comp;
|
|
}
|
|
|
|
// Finally, prefer the representation classes of Remote,
|
|
// Serializable, and InputStream, in that order.
|
|
comp = compareIndices(nonTextRepresentations, class1, class2,
|
|
UNKNOWN_OBJECT_LOSES);
|
|
if (comp != 0) {
|
|
return comp;
|
|
}
|
|
}
|
|
|
|
// As a last resort, take the DataFlavor with the greater integer
|
|
// format.
|
|
return compareLongs(flavorToFormatMap, flavor1, flavor2,
|
|
UNKNOWN_OBJECT_LOSES_L);
|
|
}
|
|
}
|
|
}
|