001    /* DataFlavor.java -- A type of data to transfer via the clipboard.
002       Copyright (C) 1999, 2001, 2004, 2005, 2006 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package java.awt.datatransfer;
040    
041    import java.io.ByteArrayInputStream;
042    import java.io.IOException;
043    import java.io.InputStream;
044    import java.io.InputStreamReader;
045    import java.io.ObjectInput;
046    import java.io.ObjectOutput;
047    import java.io.OptionalDataException;
048    import java.io.Reader;
049    import java.io.Serializable;
050    import java.io.StringReader;
051    import java.io.UnsupportedEncodingException;
052    import java.nio.ByteBuffer;
053    import java.nio.CharBuffer;
054    import java.nio.charset.Charset;
055    import java.rmi.Remote;
056    
057    /**
058     * This class represents a particular data format used for transferring
059     * data via the clipboard.
060     *
061     * @author Aaron M. Renn (arenn@urbanophile.com)
062     */
063    public class DataFlavor implements java.io.Externalizable, Cloneable
064    {
065      static final long serialVersionUID = 8367026044764648243L;
066    
067      // FIXME: Serialization: Need to write methods for.
068    
069      /**
070       * This is the data flavor used for tranferring plain text.  The MIME
071       * type is "text/plain; charset=unicode".  The representation class
072       * is <code>java.io.InputStream</code>.
073       *
074       * @deprecated The charset unicode is platform specific and InputStream
075       * deals with bytes not chars. Use <code>getRederForText()</code>.
076       */
077      public static final DataFlavor plainTextFlavor =
078        new DataFlavor("text/plain; charset=unicode; class=java.io.InputStream",
079                       "plain unicode text");
080    
081      /**
082       * This is the data flavor used for transferring Java strings.  The
083       * MIME type is "application/x-java-serialized-object" and the
084       * representation class is <code>java.lang.String</code>.
085       */
086      public static final DataFlavor stringFlavor =
087        new DataFlavor(java.lang.String.class, "Java Unicode String");
088    
089      /**
090       * This is a data flavor used for transferring lists of files.  The
091       * representation type is a <code>java.util.List</code>, with each
092       * element of the list being a <code>java.io.File</code>.
093       */
094      public static final DataFlavor javaFileListFlavor =
095        new DataFlavor("application/x-java-file-list; class=java.util.List",
096                       "Java File List");
097    
098      /**
099       * This is an image flavor used for transferring images.  The
100       * representation type is a <code>java.awt.Image</code>.
101       */
102      public static final DataFlavor imageFlavor =
103        new DataFlavor(java.awt.Image.class, "Java Image");
104    
105      /**
106       * This is the MIME type used for transferring a serialized object.
107       * The representation class is the type of object be deserialized.
108       */
109      public static final String javaSerializedObjectMimeType =
110        "application/x-java-serialized-object";
111    
112      /**
113       * This is the MIME type used to transfer a Java object reference within
114       * the same JVM.  The representation class is the class of the object
115       * being transferred.
116       */
117      public static final String javaJVMLocalObjectMimeType =
118        "application/x-java-jvm-local-objectref";
119    
120      /**
121       * This is the MIME type used to transfer a link to a remote object.
122       * The representation class is the type of object being linked to.
123       */
124      public static final String javaRemoteObjectMimeType =
125        "application/x-java-remote-object";
126    
127      /*
128       * Instance Variables
129       */
130    
131      // The MIME type for this flavor
132      private MimeType mimeType;
133    
134      // The representation class for this flavor
135      private Class<?> representationClass;
136    
137      // The human readable name of this flavor
138      private String humanPresentableName;
139    
140      /*
141       * Static Methods
142       */
143    
144      /**
145       * This method attempts to load the named class.  The following class
146       * loaders are searched in order: the bootstrap class loader, the
147       * system class loader, the context class loader (if it exists), and
148       * the specified fallback class loader.
149       *
150       * @param className The name of the class to load.
151       * @param classLoader The class loader to use if all others fail, which
152       * may be <code>null</code>.
153       *
154       * @exception ClassNotFoundException If the class cannot be loaded.
155       */
156      protected static final Class<?> tryToLoadClass(String className,
157                                                     ClassLoader classLoader)
158        throws ClassNotFoundException
159      {
160        // Bootstrap
161        try
162          {
163            return Class.forName(className);
164          }
165        catch(ClassNotFoundException cnfe)
166          {
167            // Ignored.
168          }
169    
170        // System
171        try
172          {
173            ClassLoader loader = ClassLoader.getSystemClassLoader();
174            return Class.forName(className, true, loader);
175          }
176        catch(ClassNotFoundException cnfe)
177          {
178            // Ignored.
179          }
180    
181        // Context
182        try
183          {
184            ClassLoader loader = Thread.currentThread().getContextClassLoader();
185            return Class.forName(className, true, loader);
186          }
187        catch(ClassNotFoundException cnfe)
188          {
189            // Ignored.
190          }
191    
192        if (classLoader != null)
193          return Class.forName(className, true, classLoader);
194    
195        throw new ClassNotFoundException(className);
196      }
197    
198      /**
199       * XXX - Currently returns <code>plainTextFlavor</code>.
200       */
201      public static final DataFlavor getTextPlainUnicodeFlavor()
202      {
203        return plainTextFlavor;
204      }
205    
206      /**
207       * Selects the best supported text flavor on this implementation.
208       * Returns <code>null</code> when none of the given flavors is liked.
209       *
210       * The <code>DataFlavor</code> returned the first data flavor in the
211       * array that has either a representation class which is (a subclass of)
212       * <code>Reader</code> or <code>String</code>, or has a representation
213       * class which is (a subclass of) <code>InputStream</code> and has a
214       * primary MIME type of "text" and has an supported encoding.
215       */
216      public static final DataFlavor
217        selectBestTextFlavor(DataFlavor[] availableFlavors)
218      {
219        for(int i = 0; i < availableFlavors.length; i++)
220          {
221            DataFlavor df = availableFlavors[i];
222            Class c = df.representationClass;
223    
224            // A Reader or String is good.
225            if ((Reader.class.isAssignableFrom(c))
226               || (String.class.isAssignableFrom(c)))
227          return df;
228    
229            // A InputStream is good if the mime primary type is "text"
230            if ((InputStream.class.isAssignableFrom(c))
231               && ("text".equals(df.getPrimaryType())))
232              {
233                String encoding = availableFlavors[i].getParameter("charset");
234                if (encoding == null)
235                  encoding = "us-ascii";
236                Reader r = null;
237                try
238                  {
239                    // Try to construct a dummy reader with the found encoding
240                    r = new InputStreamReader
241                          (new ByteArrayInputStream(new byte[0]), encoding);
242                  }
243                catch(UnsupportedEncodingException uee) { /* ignore */ }
244    
245                if (r != null)
246                  return df;
247              }
248          }
249    
250        // Nothing found
251        return null;
252      }
253    
254    
255      /*
256       * Constructors
257       */
258    
259      /**
260       * Empty public constructor needed for externalization.
261       * Should not be used for normal instantiation.
262       */
263      public DataFlavor()
264      {
265        // Used for deserialization only, nothing to do here.
266      }
267    
268      /**
269       * Initializes a new instance of <code>DataFlavor</code>.  The class
270       * and human readable name are specified, the MIME type will be
271       * "application/x-java-serialized-object". If the human readable name
272       * is not specified (<code>null</code>) then the human readable name
273       * will be the same as the MIME type.
274       *
275       * @param representationClass The representation class for this object.
276       * @param humanPresentableName The display name of the object.
277       */
278      public DataFlavor(Class<?> representationClass, String humanPresentableName)
279      {
280        if (representationClass == null)
281          throw new NullPointerException("representationClass must not be null");
282        try
283          {
284            mimeType = new MimeType(javaSerializedObjectMimeType);
285          }
286        catch (MimeTypeParseException ex)
287          {
288            // Must not happen as we use a constant string.
289            assert false;
290          }
291        if (humanPresentableName == null)
292          humanPresentableName = javaSerializedObjectMimeType;
293        this.humanPresentableName = humanPresentableName;
294        this.representationClass = representationClass;
295      }
296    
297      /**
298       * Initializes a new instance of <code>DataFlavor</code> with the
299       * specified MIME type and description.  If the MIME type has a
300       * "class=&lt;rep class&gt;" parameter then the representation class will
301       * be the class name specified. Otherwise the class defaults to
302       * <code>java.io.InputStream</code>. If the human readable name
303       * is not specified (<code>null</code>) then the human readable name
304       * will be the same as the MIME type.
305       *
306       * @param mimeType The MIME type for this flavor.
307       * @param humanPresentableName The display name of this flavor.
308       * @param classLoader The class loader for finding classes if the default
309       * class loaders do not work.
310       *
311       * @exception IllegalArgumentException If the representation class
312       * specified cannot be loaded.
313       * @exception ClassNotFoundException If the class is not loaded.
314       */
315      public DataFlavor(String mimeType, String humanPresentableName,
316                       ClassLoader classLoader)
317        throws ClassNotFoundException
318      {
319        init(mimeType, humanPresentableName, classLoader);
320      }
321    
322      /**
323       * Initializes a new instance of <code>DataFlavor</code> with the
324       * specified MIME type and description.  If the MIME type has a
325       * "class=&lt;rep class&gt;" parameter then the representation class will
326       * be the class name specified. Otherwise the class defaults to
327       * <code>java.io.InputStream</code>. If the human readable name
328       * is not specified (<code>null</code>) then the human readable name
329       * will be the same as the MIME type. This is the same as calling
330       * <code>new DataFlavor(mimeType, humanPresentableName, null)</code>.
331       *
332       * @param mimeType The MIME type for this flavor.
333       * @param humanPresentableName The display name of this flavor.
334       *
335       * @exception IllegalArgumentException If the representation class
336       * specified cannot be loaded.
337       */
338      public DataFlavor(String mimeType, String humanPresentableName)
339      {
340        try
341          {
342            init(mimeType, humanPresentableName, getClass().getClassLoader());
343          }
344        catch (ClassNotFoundException ex)
345          {
346            IllegalArgumentException iae =
347              new IllegalArgumentException("Class not found: " + ex.getMessage());
348            iae.initCause(ex);
349            throw iae;
350          }
351      }
352    
353      /**
354       * Initializes a new instance of <code>DataFlavor</code> with the specified
355       * MIME type.  This type can have a "class=" parameter to specify the
356       * representation class, and then the class must exist or an exception will
357       * be thrown. If there is no "class=" parameter then the representation class
358       * will be <code>java.io.InputStream</code>. This is the same as calling
359       * <code>new DataFlavor(mimeType, null)</code>.
360       *
361       * @param mimeType The MIME type for this flavor.
362       *
363       * @exception IllegalArgumentException If a class is not specified in
364       * the MIME type.
365       * @exception ClassNotFoundException If the class cannot be loaded.
366       */
367      public DataFlavor(String mimeType) throws ClassNotFoundException
368      {
369        init(mimeType, null, getClass().getClassLoader());
370      }
371    
372      /**
373       * Called by various constructors to initialize this object.
374       *
375       * @param mime the mime string
376       * @param humanPresentableName the human presentable name
377       * @param loader the class loader to use for loading the representation
378       *        class
379       */
380      private void init(String mime, String humanPresentableName,
381                        ClassLoader loader)
382        throws ClassNotFoundException
383      {
384        if (mime == null)
385          throw new NullPointerException("The mime type must not be null");
386        try
387          {
388            mimeType = new MimeType(mime);
389          }
390        catch (MimeTypeParseException ex)
391          {
392            IllegalArgumentException iae =
393              new IllegalArgumentException("Invalid mime type");
394            iae.initCause(ex);
395            throw iae;
396          }
397        String className = mimeType.getParameter("class");
398        if (className == null)
399          {
400            if (mimeType.getBaseType().equals(javaSerializedObjectMimeType))
401              throw new IllegalArgumentException("Serialized object type must have"
402                                            + " a representation class parameter");
403            else
404              representationClass = java.io.InputStream.class;
405          }
406        else
407          representationClass = tryToLoadClass(className, loader);
408        mimeType.addParameter("class", representationClass.getName());
409    
410        if (humanPresentableName == null)
411          {
412            humanPresentableName = mimeType.getParameter("humanPresentableName");
413            if (humanPresentableName == null)
414              humanPresentableName = mimeType.getBaseType();
415          }
416        this.humanPresentableName = humanPresentableName;
417      }
418    
419      /**
420       * Returns the MIME type of this flavor.
421       *
422       * @return The MIME type for this flavor.
423       */
424      public String getMimeType()
425      {
426        return(mimeType.toString());
427      }
428    
429      /**
430       * Returns the representation class for this flavor.
431       *
432       * @return The representation class for this flavor.
433       */
434      public Class<?> getRepresentationClass()
435      {
436        return(representationClass);
437      }
438    
439      /**
440       * Returns the human presentable name for this flavor.
441       *
442       * @return The human presentable name for this flavor.
443       */
444      public String getHumanPresentableName()
445      {
446        return(humanPresentableName);
447      }
448    
449      /**
450       * Returns the primary MIME type for this flavor.
451       *
452       * @return The primary MIME type for this flavor.
453       */
454      public String getPrimaryType()
455      {
456        return(mimeType.getPrimaryType());
457      }
458    
459      /**
460       * Returns the MIME subtype for this flavor.
461       *
462       * @return The MIME subtype for this flavor.
463       */
464      public String getSubType()
465      {
466        return mimeType.getSubType();
467      }
468    
469      /**
470       * Returns the value of the named MIME type parameter, or <code>null</code>
471       * if the parameter does not exist.
472       *
473       * @param paramName The name of the paramter.
474       *
475       * @return The value of the parameter.
476       */
477      public String getParameter(String paramName)
478      {
479        if ("humanPresentableName".equals(paramName))
480          return getHumanPresentableName();
481    
482        return mimeType.getParameter(paramName);
483      }
484    
485      /**
486       * Sets the human presentable name to the specified value.
487       *
488       * @param humanPresentableName The new display name.
489       */
490      public void setHumanPresentableName(String humanPresentableName)
491      {
492        this.humanPresentableName = humanPresentableName;
493      }
494    
495      /**
496       * Tests the MIME type of this object for equality against the specified
497       * MIME type. Ignores parameters.
498       *
499       * @param mimeType The MIME type to test against.
500       *
501       * @return <code>true</code> if the MIME type is equal to this object's
502       * MIME type (ignoring parameters), <code>false</code> otherwise.
503       *
504       * @exception NullPointerException If mimeType is null.
505       */
506      public boolean isMimeTypeEqual(String mimeType)
507      {
508        if (mimeType == null)
509          throw new NullPointerException("mimeType must not be null");
510        boolean equal = false;
511        try
512          {
513            if (this.mimeType != null)
514              {
515                MimeType other = new MimeType(mimeType);
516                equal = this.mimeType.matches(other);
517              }
518          }
519        catch (MimeTypeParseException ex)
520          {
521            // Return false in this case.
522          }
523        return equal;
524      }
525    
526      /**
527       * Tests the MIME type of this object for equality against the specified
528       * data flavor's MIME type
529       *
530       * @param flavor The flavor to test against.
531       *
532       * @return <code>true</code> if the flavor's MIME type is equal to this
533       * object's MIME type, <code>false</code> otherwise.
534       */
535      public final boolean isMimeTypeEqual(DataFlavor flavor)
536      {
537        return isMimeTypeEqual(flavor.getMimeType());
538      }
539    
540      /**
541       * Tests whether or not this flavor represents a serialized object.
542       *
543       * @return <code>true</code> if this flavor represents a serialized
544       * object, <code>false</code> otherwise.
545       */
546      public boolean isMimeTypeSerializedObject()
547      {
548        return isMimeTypeEqual(javaSerializedObjectMimeType);
549      }
550    
551      /**
552       * Tests whether or not this flavor has a representation class of
553       * <code>java.io.InputStream</code>.
554       *
555       * @return <code>true</code> if the representation class of this flavor
556       * is <code>java.io.InputStream</code>, <code>false</code> otherwise.
557       */
558      public boolean isRepresentationClassInputStream()
559      {
560        return InputStream.class.isAssignableFrom(representationClass);
561      }
562    
563      /**
564       * Tests whether the representation class for this flavor is
565       * serializable.
566       *
567       * @return <code>true</code> if the representation class is serializable,
568       * <code>false</code> otherwise.
569       */
570      public boolean isRepresentationClassSerializable()
571      {
572        return Serializable.class.isAssignableFrom(representationClass);
573      }
574    
575      /**
576       * Tests whether the representation class for his flavor is remote.
577       *
578       * @return <code>true</code> if the representation class is remote,
579       * <code>false</code> otherwise.
580       */
581      public boolean isRepresentationClassRemote()
582      {
583        return Remote.class.isAssignableFrom (representationClass);
584      }
585    
586      /**
587       * Tests whether or not this flavor represents a serialized object.
588       *
589       * @return <code>true</code> if this flavor represents a serialized
590       * object, <code>false</code> otherwise.
591       */
592      public boolean isFlavorSerializedObjectType()
593      {
594        return isRepresentationClassSerializable()
595               && isMimeTypeEqual(javaSerializedObjectMimeType);
596      }
597    
598      /**
599       * Tests whether or not this flavor represents a remote object.
600       *
601       * @return <code>true</code> if this flavor represents a remote object,
602       * <code>false</code> otherwise.
603       */
604      public boolean isFlavorRemoteObjectType()
605      {
606        return isRepresentationClassRemote()
607               && isRepresentationClassSerializable()
608               && isMimeTypeEqual(javaRemoteObjectMimeType);
609      }
610    
611      /**
612       * Tests whether or not this flavor represents a list of files.
613       *
614       * @return <code>true</code> if this flavor represents a list of files,
615       * <code>false</code> otherwise.
616       */
617      public boolean isFlavorJavaFileListType()
618      {
619        if (getPrimaryType().equals(javaFileListFlavor.getPrimaryType())
620            && getSubType().equals(javaFileListFlavor.getSubType())
621            && javaFileListFlavor.representationClass
622               .isAssignableFrom(representationClass))
623          return true;
624    
625        return false ;
626      }
627    
628      /**
629       * Returns a copy of this object.
630       *
631       * @return A copy of this object.
632       *
633       * @exception CloneNotSupportedException If the object's class does not support
634       * the Cloneable interface. Subclasses that override the clone method can also
635       * throw this exception to indicate that an instance cannot be cloned.
636       */
637      public Object clone () throws CloneNotSupportedException
638      {
639        // FIXME - This cannot be right.
640        try
641          {
642            return super.clone();
643          }
644        catch(Exception e)
645          {
646            return null;
647          }
648      }
649    
650      /**
651       * This method test the specified <code>DataFlavor</code> for equality
652       * against this object.  This will be true if the MIME type and
653       * representation class are the equal. If the primary type is 'text'
654       * then also the value of the charset parameter is compared. In such a
655       * case when the charset parameter isn't given then the charset is
656       * assumed to be equal to the default charset of the platform.  All
657       * other parameters are ignored.
658       *
659       * @param flavor The <code>DataFlavor</code> to test against.
660       *
661       * @return <code>true</code> if the flavor is equal to this object,
662       * <code>false</code> otherwise.
663       */
664      public boolean equals(DataFlavor flavor)
665      {
666        if (flavor == null)
667          return false;
668    
669        String primary = getPrimaryType();
670        if (! primary.equals(flavor.getPrimaryType()))
671          return false;
672    
673        String sub = getSubType();
674        if (! sub.equals(flavor.getSubType()))
675          return false;
676    
677        if (! this.representationClass.equals(flavor.representationClass))
678          return false;
679    
680        if (primary.equals("text"))
681          if (! isRepresentationClassCharBuffer()
682              && ! isRepresentationClassReader()
683              && representationClass != java.lang.String.class
684              && ! (representationClass.isArray()
685                    && representationClass.getComponentType() == Character.TYPE))
686            {
687              String charset = getParameter("charset");
688              String otherset = flavor.getParameter("charset");
689              String defaultset = Charset.defaultCharset().name();
690    
691              if (charset == null || charset.equals(defaultset))
692                return (otherset == null || otherset.equals(defaultset));
693    
694              return charset.equals(otherset);
695            }
696    
697        return true;
698      }
699    
700      /**
701       * This method test the specified <code>Object</code> for equality
702       * against this object.  This will be true if the following conditions
703       * are met:
704       * <p>
705       * <ul>
706       * <li>The object is not <code>null</code>.</li>
707       * <li>The object is an instance of <code>DataFlavor</code>.</li>
708       * <li>The object's MIME type and representation class are equal to
709       * this object's.</li>
710       * </ul>
711       *
712       * @param obj The <code>Object</code> to test against.
713       *
714       * @return <code>true</code> if the flavor is equal to this object,
715       * <code>false</code> otherwise.
716       */
717      public boolean equals(Object obj)
718      {
719        if (! (obj instanceof DataFlavor))
720          return false;
721    
722        return equals((DataFlavor) obj);
723      }
724    
725      /**
726       * Tests whether or not the specified string is equal to the MIME type
727       * of this object.
728       *
729       * @param str The string to test against.
730       *
731       * @return <code>true</code> if the string is equal to this object's MIME
732       * type, <code>false</code> otherwise.
733       *
734       * @deprecated Not compatible with <code>hashCode()</code>.
735       *             Use <code>isMimeTypeEqual()</code>
736       */
737      public boolean equals(String str)
738      {
739        return isMimeTypeEqual(str);
740      }
741    
742      /**
743       * Returns the hash code for this data flavor.
744       * The hash code is based on the (lower case) mime type and the
745       * representation class.
746       */
747      public int hashCode()
748      {
749        return mimeType.toString().hashCode() ^ representationClass.hashCode();
750      }
751    
752      /**
753       * Returns <code>true</code> when the given <code>DataFlavor</code>
754       * matches this one.
755       */
756      public boolean match(DataFlavor dataFlavor)
757      {
758        // XXX - How is this different from equals?
759        return equals(dataFlavor);
760      }
761    
762      /**
763       * This method exists for backward compatibility.  It simply returns
764       * the same name/value pair passed in.
765       *
766       * @param name The parameter name.
767       * @param value The parameter value.
768       *
769       * @return The name/value pair.
770       *
771       * @deprecated
772       */
773      protected String normalizeMimeTypeParameter(String name, String value)
774      {
775        return name + "=" + value;
776      }
777    
778      /**
779       * This method exists for backward compatibility.  It simply returns
780       * the MIME type string unchanged.
781       *
782       * @param type The MIME type.
783       *
784       * @return The MIME type.
785       *
786       * @deprecated
787       */
788      protected String normalizeMimeType(String type)
789      {
790        return type;
791      }
792    
793      /**
794       * Serialize this class.
795       *
796       * @param stream The <code>ObjectOutput</code> stream to serialize to.
797       *
798       * @exception IOException If an error occurs.
799       */
800      public void writeExternal(ObjectOutput stream)
801        throws IOException
802      {
803        if (mimeType != null)
804          {
805            mimeType.addParameter("humanPresentableName", humanPresentableName);
806            stream.writeObject(mimeType);
807            mimeType.removeParameter("humanPresentableName");
808          }
809        else
810          stream.writeObject(null);
811        stream.writeObject(representationClass);
812      }
813    
814    
815      /**
816       * De-serialize this class.
817       *
818       * @param stream The <code>ObjectInput</code> stream to deserialize from.
819       *
820       * @exception IOException If an error ocurs.
821       * @exception ClassNotFoundException If the class for an object being restored
822       * cannot be found.
823       */
824      public void readExternal(ObjectInput stream)
825        throws IOException, ClassNotFoundException
826      {
827        mimeType = (MimeType) stream.readObject();
828        String className = null;
829        if (mimeType != null)
830          {
831            humanPresentableName =
832              mimeType.getParameter("humanPresentableName");
833            mimeType.removeParameter("humanPresentableName");
834            className = mimeType.getParameter("class");
835            if (className == null)
836              throw new IOException("No class in mime type");
837          }
838        try
839          {
840            representationClass = (Class) stream.readObject();
841          }
842        catch (OptionalDataException ex)
843          {
844            if (ex.eof && ex.length == 0)
845              {
846                if (className != null)
847                  representationClass = tryToLoadClass(className,
848                                                      getClass().getClassLoader());
849              }
850            else
851              throw ex;
852          }
853      }
854    
855      /**
856       * Returns a string representation of this DataFlavor. Including the
857       * representation class name, MIME type and human presentable name.
858       */
859      public String toString()
860      {
861        return (getClass().getName()
862               + "[representationClass=" + getRepresentationClass().getName()
863               + ",mimeType=" + getMimeType()
864               + ",humanPresentableName=" + getHumanPresentableName()
865               + "]");
866      }
867    
868      /**
869       * XXX - Currently returns <code>java.io.InputStream</code>.
870       *
871       * @since 1.3
872       */
873      public final Class<?> getDefaultRepresentationClass()
874      {
875        return java.io.InputStream.class;
876      }
877    
878      /**
879       * XXX - Currently returns <code>java.io.InputStream</code>.
880       */
881      public final String getDefaultRepresentationClassAsString()
882      {
883        return getDefaultRepresentationClass().getName();
884      }
885    
886      /**
887       * Creates a <code>Reader</code> for a given <code>Transferable</code>.
888       *
889       * If the representation class is a (subclass of) <code>Reader</code>
890       * then an instance of the representation class is returned. If the
891       * representatation class is a <code>String</code> then a
892       * <code>StringReader</code> is returned. And if the representation class
893       * is a (subclass of) <code>InputStream</code> and the primary MIME type
894       * is "text" then a <code>InputStreamReader</code> for the correct charset
895       * encoding is returned.
896       *
897       * @param transferable The <code>Transferable</code> for which a text
898       *                     <code>Reader</code> is requested.
899       *
900       * @exception IllegalArgumentException If the representation class is not one
901       * of the seven listed above or the Transferable has null data.
902       * @exception NullPointerException If the Transferable is null.
903       * @exception UnsupportedFlavorException when the transferable doesn't
904       * support this <code>DataFlavor</code>. Or if the representable class
905       * isn't a (subclass of) <code>Reader</code>, <code>String</code>,
906       * <code>InputStream</code> and/or the primary MIME type isn't "text".
907       * @exception IOException when any IOException occurs.
908       * @exception UnsupportedEncodingException if the "charset" isn't supported
909       * on this platform.
910       */
911      public Reader getReaderForText(Transferable transferable)
912        throws UnsupportedFlavorException, IOException
913      {
914          if (!transferable.isDataFlavorSupported(this))
915              throw new UnsupportedFlavorException(this);
916    
917          if (Reader.class.isAssignableFrom(representationClass))
918              return (Reader)transferable.getTransferData(this);
919    
920          if (String.class.isAssignableFrom(representationClass))
921              return new StringReader((String)transferable.getTransferData(this));
922    
923          if (InputStream.class.isAssignableFrom(representationClass)
924              && "text".equals(getPrimaryType()))
925            {
926              InputStream in = (InputStream)transferable.getTransferData(this);
927              String encoding = getParameter("charset");
928              if (encoding == null)
929                  encoding = "us-ascii";
930              return new InputStreamReader(in, encoding);
931            }
932    
933          throw new UnsupportedFlavorException(this);
934      }
935    
936      /**
937       * Returns whether the representation class for this DataFlavor is
938       * @see java.nio.ByteBuffer or a subclass thereof.
939       *
940       * @since 1.4
941       */
942      public boolean isRepresentationClassByteBuffer()
943      {
944        return ByteBuffer.class.isAssignableFrom(representationClass);
945      }
946    
947      /**
948       * Returns whether the representation class for this DataFlavor is
949       * @see java.nio.CharBuffer or a subclass thereof.
950       *
951       * @since 1.4
952       */
953      public boolean isRepresentationClassCharBuffer()
954      {
955        return CharBuffer.class.isAssignableFrom(representationClass);
956      }
957    
958      /**
959       * Returns whether the representation class for this DataFlavor is
960       * @see java.io.Reader or a subclass thereof.
961       *
962       * @since 1.4
963       */
964      public boolean isRepresentationClassReader()
965      {
966        return Reader.class.isAssignableFrom(representationClass);
967      }
968    
969      /**
970       * Returns whether this <code>DataFlavor</code> is a valid text flavor for
971       * this implementation of the Java platform. Only flavors equivalent to
972       * <code>DataFlavor.stringFlavor</code> and <code>DataFlavor</code>s with
973       * a primary MIME type of "text" can be valid text flavors.
974       * <p>
975       * If this flavor supports the charset parameter, it must be equivalent to
976       * <code>DataFlavor.stringFlavor</code>, or its representation must be
977       * <code>java.io.Reader</code>, <code>java.lang.String</code>,
978       * <code>java.nio.CharBuffer</code>, <code>java.io.InputStream</code> or
979       * <code>java.nio.ByteBuffer</code>,
980       * If the representation is <code>java.io.InputStream</code> or
981       * <code>java.nio.ByteBuffer</code>, then this flavor's <code>charset</code>
982       * parameter must be supported by this implementation of the Java platform.
983       * If a charset is not specified, then the platform default charset, which
984       * is always supported, is assumed.
985       * <p>
986       * If this flavor does not support the charset parameter, its
987       * representation must be <code>java.io.InputStream</code>,
988       * <code>java.nio.ByteBuffer</code>.
989       * <p>
990       * See <code>selectBestTextFlavor</code> for a list of text flavors which
991       * support the charset parameter.
992       *
993       * @return <code>true</code> if this <code>DataFlavor</code> is a valid
994       *         text flavor as described above; <code>false</code> otherwise
995       * @see #selectBestTextFlavor
996       * @since 1.4
997       */
998      public boolean isFlavorTextType() {
999        // FIXME: I'm not 100% sure if this implementation does the same like sun's does
1000        if(equals(DataFlavor.stringFlavor) || getPrimaryType().equals("text"))
1001          {
1002            String charset = getParameter("charset");
1003            Class c = getRepresentationClass();
1004            if(charset != null)
1005              {
1006                if(Reader.class.isAssignableFrom(c)
1007                    || CharBuffer.class.isAssignableFrom(c)
1008                    || String.class.isAssignableFrom(c))
1009                  {
1010                    return true;
1011                  }
1012                else if(InputStream.class.isAssignableFrom(c)
1013                        || ByteBuffer.class.isAssignableFrom(c))
1014                  {
1015                    return Charset.isSupported(charset);
1016                  }
1017              }
1018            else if(InputStream.class.isAssignableFrom(c)
1019                || ByteBuffer.class.isAssignableFrom(c))
1020              {
1021                return true;
1022              }
1023          }
1024        return false;
1025      }
1026    } // class DataFlavor