/* * @(#)DataFlavor.java 1.22 06/10/10 * * Copyright 1990-2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. * */ package java.awt.datatransfer; import java.awt.Toolkit; import java.io.*; import java.util.*; import sun.awt.SunToolkit; /** * Each instance represents the opaque concept of a data format as would * appear on a clipboard, during drag and drop, or in a file system. * <p> * <code>DataFlavor</code> objects are constant and never change once * instantiated. * </p> * * @version 1.53, 02/09/01 * @author Blake Sullivan * @author Laurence P. G. Cable * @author Jeff Dunn */ public class DataFlavor implements Externalizable, Cloneable { private static final long serialVersionUID = 8367026044764648243L; private static final Class ioInputStreamClass = java.io.InputStream.class; /** * tried to load a class from: the bootstrap loader, the system loader, * the context loader (if one is present) and finally the loader specified * * @param fallback the fallback loader * * @throws ClassNotFoundException */ private final static Class tryToLoadClass(String className, ClassLoader fallback) throws ClassNotFoundException { ClassLoader systemClassLoader = (ClassLoader) java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public Object run() { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return (cl != null) ? cl : ClassLoader.getSystemClassLoader(); } } ); try { return Class.forName(className, true, systemClassLoader); } catch (ClassNotFoundException e2) { if (fallback != null) { return Class.forName(className, true, fallback); } else { throw new ClassNotFoundException(className); } } } /* * private initializer */ static private DataFlavor createConstant(Class rc, String prn) { try { return new DataFlavor(rc, prn); } catch (Exception e) { return null; } } /* * private initializer */ static private DataFlavor createConstant(String mt, String prn) { try { return new DataFlavor(mt, prn); } catch (Exception e) { return null; } } /** * The DataFlavor representing a Java Unicode String class, where: * <p> * representationClass = java.lang.String<br> * mimeType = "application/x-java-serialized-object" * <p> */ public static final DataFlavor stringFlavor = createConstant(java.lang.String.class, "Unicode String"); /** * The DataFlavor representing plain text with unicode encoding, where: * <p> * representationClass = InputStream<br> * mimeType = "text/plain; charset=unicode" * <p> * This DataFlavor has been <b>deprecated</b> because (1) Its * representation is an InputStream, an 8-bit based representation, * while Unicode is a 16-bit character set; and (2) The charset "unicode" * is not well-defined. "unicode" implies a particular platform's * implementation of Unicode, not a cross-platform implementation. * * @deprecated as of 1.3. Use <code>DataFlavor.getReaderForText( * Transferable)</code> instead of <code>Transferable. * getTransferData(DataFlavor.plainTextFlavor)</code>. */ public static final DataFlavor plainTextFlavor = createConstant("text/plain; charset=unicode; class=java.io.InputStream", "Plain Text"); /** * Constructs a new DataFlavor. This constructor is provided only for * the purpose of supporting the Externalizable interface. It is not * intended for public (client) use. * * @since 1.2 */ public DataFlavor() { super(); } /** * Cloning constructor. Package-private. */ DataFlavor(DataFlavor that) { this.mimeType = null; if (that.mimeType != null) { try { this.mimeType = (MimeType)that.mimeType.clone(); } catch (CloneNotSupportedException e) { } } this.representationClass = that.representationClass; this.humanPresentableName = that.humanPresentableName; this.atom = that.atom; } // DataFlavor() /** * Construct a fully specified DataFlavor */ private DataFlavor(String primaryType, String subType, MimeTypeParameterList params, Class representationClass, String humanPresentableName) { super(); if (params == null) params = new MimeTypeParameterList(); params.set("class", representationClass.getName()); if (humanPresentableName == null) { humanPresentableName = (String)params.get("humanPresentableName"); if (humanPresentableName == null) humanPresentableName = primaryType + "/" + subType; } try { mimeType = new MimeType(primaryType, subType, params); } catch (MimeTypeParseException mtpe) { throw new IllegalArgumentException("MimeType Parse Exception: " + mtpe.getMessage()); } this.representationClass = representationClass; this.humanPresentableName = humanPresentableName; mimeType.removeParameter("humanPresentableName"); } /** * Construct a DataFlavor that represents a Java class * <p> * The returned DataFlavor will have the following characteristics * <p> * representationClass = representationClass<br> * mimeType = application/x-java-serialized-object * <p> * @param representationClass the class used to transfer data in this flavor * @param humanPresentableName the human-readable string used to identify * this flavor. * If this parameter is null then the value of the * the MIME Content Type is used. */ public DataFlavor(Class representationClass, String humanPresentableName) { this("application", "x-java-serialized-object", null, representationClass, humanPresentableName); } /** * Construct a DataFlavor that represents a MimeType * <p> * The returned DataFlavor will have the following characteristics: * <p> * If the mimeType is * "application/x-java-serialized-object; class=<representation class>", * the result is the same as calling * new DataFlavor(Class:forName(<representation class>) as above * <p> * otherwise: * <p> * representationClass = InputStream<br> * mimeType = mimeType * <p> * @param mimeType the string used to identify the MIME type for this flavor. * If the the mimeType does not specify a * "class=" parameter, or if the class is not successfully * loaded, then an IllegalArgumentException is thrown. * @param humanPresentableName the human-readable string used to identify * this flavor. * If this parameter is null then the value of the * the MIME Content Type is used. */ public DataFlavor(String mimeType, String humanPresentableName) { super(); try { initialize(mimeType, humanPresentableName, this.getClass().getClassLoader()); } catch (MimeTypeParseException mtpe) { throw new IllegalArgumentException("failed to parse:" + mimeType); } catch (ClassNotFoundException cnfe) { throw new IllegalArgumentException("cant find specified class: " + cnfe.getMessage()); } } /** * common initialization code called from various constructors. * * @param mimeType The MIME Content Type (must have a class= param) * @param humanPresentableName The human Presentable Name or null * @param classLoader The fallback class loader to resolve against * * @throws MimeTypeParseException * @throws ClassNotFoundException * * @see tryToLoadClass */ private void initialize(String mimeType, String humanPresentableName, ClassLoader classLoader) throws MimeTypeParseException, ClassNotFoundException { this.mimeType = new MimeType(mimeType); // throws String rcn = getParameter("class"); if (rcn == null) { if ("application/x-java-serialized-object".equals(this.mimeType.getBaseType())) throw new IllegalArgumentException("no representation class specified for:" + mimeType); else representationClass = java.io.InputStream.class; // default } else { // got a class name representationClass = DataFlavor.tryToLoadClass(rcn, classLoader); } this.mimeType.setParameter("class", representationClass.getName()); if (humanPresentableName == null) { humanPresentableName = this.mimeType.getParameter("humanPresentableName"); if (humanPresentableName == null) humanPresentableName = this.mimeType.getPrimaryType() + "/" + this.mimeType.getSubType(); } this.humanPresentableName = humanPresentableName; // set it. this.mimeType.removeParameter("humanPresentableName"); // just in case } /** * used by clone implementation */ private DataFlavor(MimeType mt, Class rc, String hrn, int a) { super(); mimeType = mt; representationClass = rc; humanPresentableName = hrn; atom = a; } /** * String representation of this <code>DataFlavor</code> * and its parameters. The result String contains name of * <code>DataFlavor</code> class, representation class * and Mime type of this Flavor. * * @return string representation of this <code>DataFlavor</code> */ public String toString() { String string = getClass().getName(); string += "["+paramString()+"]"; return string; } private String paramString() { String params = ""; params += "representationclass="; if (representationClass == null) { params += "null"; } else { params += representationClass.getName(); } params += ";mimetype="; if (mimeType == null) { params += "null"; } else { params += mimeType.getBaseType(); } return params; } /** * Returns the MIME type string for this DataFlavor */ public String getMimeType() { return mimeType.toString(); } /** * Returns the Class which objects supporting this DataFlavor * will return when this DataFlavor is requested. */ public Class getRepresentationClass() { return representationClass; } /** * Returns the human presentable name for the data foramt that this * DataFlavor represents. This name would be localized for different * countries */ public String getHumanPresentableName() { return humanPresentableName; } /** * @return the value of the name parameter */ private String getParameter(String paramName) { return paramName.equals("humanPresentableName") ? humanPresentableName : mimeType.getParameter(paramName); } /** * Sets the human presentable name for the data format that this * DataFlavor represents. This name would be localized for different * countries */ public void setHumanPresentableName(String humanPresentableName) { this.humanPresentableName = humanPresentableName; } /** * If the object is an instance of DataFlavor, representationClass * and MIME type will be compared. * This method does not use equals(String) method, so it does not * return <code>true</code> for the objects of String type. * * @return if the objects are equal */ public boolean equals(Object o) { return ((o instanceof DataFlavor) && equals((DataFlavor)o)); } /** * Two DataFlavors are considered equal if and only if their * MIME primary type and subtype and representation class are * equal. Additionally, if the primary type is "text", the * charset parameter must also be equal. If * either DataFlavor is of primary type "text", but no charset * is specified, the platform default charset is assumed for * that DataFlavor. * * @return if the DataFlavors represent exactly the same type. */ public boolean equals(DataFlavor that) { if (that == null) { return false; } if (this == that) { return true; } if (representationClass == null) { if (that.getRepresentationClass() != null) { return false; } } else { if (!representationClass.equals(that.getRepresentationClass())) { return false; } } if (mimeType == null) { if (that.mimeType != null) { return false; } } else { if (!mimeType.match(that.mimeType)) { return false; } } return true; } /** * Returns hash code for this <code>DataFlavor</code>. * For two equal DataFlavors, hash codes are equal. For the String * that matches <code>DataFlavor.equals(String)</code>, it is not * guaranteed that DataFlavor's hash code is equal to the hash code * of the String. * * @return a hash code for this DataFlavor */ public int hashCode() { int representationClassPortion = 0, mimeTypePortion = 0, charsetPortion = 0; if (representationClass != null) { representationClassPortion = representationClass.hashCode(); } if (mimeType != null) { String primaryType = mimeType.getPrimaryType(); if (primaryType != null) { mimeTypePortion = primaryType.hashCode(); } } int total = representationClassPortion + mimeTypePortion + charsetPortion; return (total != 0) ? total : 25431009; } /** * Returns the primary MIME type for this <code>DataFlavor</code>. * @return the primary MIME type of this <code>DataFlavor</code> */ public String getPrimaryType() { return (mimeType != null) ? mimeType.getPrimaryType() : null; } /** * Returns the sub MIME type of this <code>DataFlavor</code>. * @return the Sub MIME type of this <code>DataFlavor</code> */ public String getSubType() { return (mimeType != null) ? mimeType.getSubType() : null; } /* * returns the charset parameter for flavors with "text" as the * primary type. If the primary type is text, and no charset * is defined, the default charset for the platform is returned. * * If the primary type is not "text", always returns null. */ private String getTextCharset() { String charset = null; if ("text".equals(getPrimaryType())) { charset = getParameter("charset"); if (charset == null) { Toolkit toolkit = Toolkit.getDefaultToolkit(); if (toolkit instanceof SunToolkit) { charset = ((SunToolkit)toolkit). getDefaultCharacterEncoding(); } } } return charset; } // getTextCharset() /** * Two DataFlavors match if their primary types, subtypes, * and representation classes are all equal. Additionally, if * the primary type is "text", the charset parameter is also * considered. If either DataFlavor is of primary type "text", * but no charset is specified, the platform default charset * is assumed for that DataFlavor. */ public boolean match(DataFlavor that) { if (that == null) { return false; } if ((this.mimeType == null) || (that.mimeType == null)) { return false; } String thisPrimaryType = this.getPrimaryType(); String thatPrimaryType = that.getPrimaryType(); if ((thisPrimaryType == null) || (thatPrimaryType == null) || (!thisPrimaryType.equals(thatPrimaryType))) { return false; } String thisSubType = this.getSubType(); String thatSubType = that.getSubType(); if ((thisSubType == null) || (thatSubType == null) || (!thisSubType.equals(thatSubType))) { return false; } Class thisRepresentationClass = this.getRepresentationClass(); Class thatRepresentationClass = that.getRepresentationClass(); if ((thisRepresentationClass == null) || (thatRepresentationClass == null) || (!thisRepresentationClass.equals(thatRepresentationClass))) { return false; } if (thisPrimaryType.equals("text")) { String thisCharset = this.getTextCharset(); String thatCharset = that.getTextCharset(); if ((thisCharset == null) || (thatCharset == null) || (!thisCharset.equals(thatCharset))) { return false; } } return true; } // match() /** * Returns whether the string representation of the MIME type passed in * is equivalent to the MIME type of this <code>DataFlavor</code>. * Parameters are not incuded in the comparison. The comparison may involve * adding default attributes for some MIME types (such as adding * <code>charset=US-ASCII</code> to text/plain MIME types that have * no <code>charset</code> parameter specified). * * @param mimeType the string representation of the MIME type * @return true if the string representation of the MIME type passed in is * equivalent to the MIME type of this <code>DataFlavor</code>; * false otherwise. * @throws NullPointerException if mimeType is <code>null</code> */ public boolean isMimeTypeEqual(String mimeType) { // JCK Test DataFlavor0117: if 'mimeType' is null, throw NPE if (mimeType == null) { throw new NullPointerException("mimeType"); } if (this.mimeType == null) { return false; } try { return this.mimeType.match(new MimeType(mimeType)); } catch (MimeTypeParseException mtpe) { return false; } } /** * Compare the mimeType of two DataFlavor objects * no parameters are considered * * @return if the MimeTypes are equal */ public final boolean isMimeTypeEqual(DataFlavor dataFlavor) { return isMimeTypeEqual(dataFlavor.mimeType); } /** * Compare the mimeType of two DataFlavor objects * no parameters are considered * * @return if the MimeTypes are equal */ private boolean isMimeTypeEqual(MimeType mtype) { return mimeType.match(mtype); } /** * Serialize this DataFlavor */ public synchronized void writeExternal(ObjectOutput os) throws IOException { mimeType.setParameter("humanPresentableName", humanPresentableName); os.writeObject(mimeType); mimeType.removeParameter("humanPresentableName"); } /** * restore this DataFlavor from an Serialized state */ public synchronized void readExternal(ObjectInput is) throws IOException , ClassNotFoundException { mimeType = (MimeType)is.readObject(); humanPresentableName = mimeType.getParameter("humanPresentableName"); mimeType.removeParameter("humanPresentableName"); String rcn = mimeType.getParameter("class"); if (rcn == null) throw new IOException("no class parameter specified in: " + mimeType); representationClass = DataFlavor.tryToLoadClass(rcn, this.getClass().getClassLoader()); } /** * @return a clone of this DataFlavor */ public Object clone() throws CloneNotSupportedException { DataFlavor clonedFlavor = new DataFlavor(this); return clonedFlavor; } // clone() /** * Called on DataFlavor for every MIME Type parameter to allow DataFlavor * subclasses to handle special parameters like the text/plain charset * parameters, whose values are case insensitive. (MIME type parameter * values are supposed to be case sensitive. * <p> * This method is called for each parameter name/value pair and should * return the normalized representation of the parameterValue * * This method is never invoked by this implementation from 1.1 onwards * * @deprecated */ protected String normalizeMimeTypeParameter(String parameterName, String parameterValue) { return parameterValue; } /** * Called for each MIME type string to give DataFlavor subtypes the * opportunity to change how the normalization of MIME types is accomplished. * One possible use would be to add default parameter/value pairs in cases * where none are present in the MIME type string passed in * * This method is never invoked by this implementation from 1.1 onwards * * @deprecated */ protected String normalizeMimeType(String mimeType) { return mimeType; } //DEBUG void debugTestMimeEquals(DataFlavor that) { //DEBUG String areThey = "?????"; //DEBUG if ((this.mimeType != null) && (that.mimeType != null)) { //DEBUG if (this.mimeType.equals(that.mimeType)) { //DEBUG areThey = " TRUE"; //DEBUG } else { //DEBUG areThey = "FALSE"; //DEBUG } //DEBUG } //DEBUG System.out.println(areThey + ": " + this.mimeType); //DEBUG System.out.println(" "+ ": " + that.mimeType); //DEBUG } /* * fields */ /* placeholder for caching any platform-specific data for flavor */ transient int atom; /* Mime Type of DataFlavor */ MimeType mimeType; private String humanPresentableName; /** Java class of objects this DataFlavor represents **/ private Class representationClass; } // class DataFlavor