/* * 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); } } }