View Javadoc
1   /*
2    * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package java.awt.datatransfer;
27  
28  import java.io.Externalizable;
29  import java.io.ObjectOutput;
30  import java.io.ObjectInput;
31  import java.io.IOException;
32  import java.util.Enumeration;
33  import java.util.Locale;
34  
35  
36  /**
37   * A Multipurpose Internet Mail Extension (MIME) type, as defined
38   * in RFC 2045 and 2046.
39   *
40   * THIS IS *NOT* - REPEAT *NOT* - A PUBLIC CLASS! DataFlavor IS
41   * THE PUBLIC INTERFACE, AND THIS IS PROVIDED AS A ***PRIVATE***
42   * (THAT IS AS IN *NOT* PUBLIC) HELPER CLASS!
43   */
44  class MimeType implements Externalizable, Cloneable {
45  
46      /*
47       * serialization support
48       */
49  
50      static final long serialVersionUID = -6568722458793895906L;
51  
52      /**
53       * Constructor for externalization; this constructor should not be
54       * called directly by an application, since the result will be an
55       * uninitialized, immutable <code>MimeType</code> object.
56       */
57      public MimeType() {
58      }
59  
60      /**
61       * Builds a <code>MimeType</code> from a <code>String</code>.
62       *
63       * @param rawdata text used to initialize the <code>MimeType</code>
64       * @throws NullPointerException if <code>rawdata</code> is null
65       */
66      public MimeType(String rawdata) throws MimeTypeParseException {
67          parse(rawdata);
68      }
69  
70      /**
71       * Builds a <code>MimeType</code> with the given primary and sub
72       * type but has an empty parameter list.
73       *
74       * @param primary the primary type of this <code>MimeType</code>
75       * @param sub the subtype of this <code>MimeType</code>
76       * @throws NullPointerException if either <code>primary</code> or
77       *         <code>sub</code> is null
78       */
79      public MimeType(String primary, String sub) throws MimeTypeParseException {
80          this(primary, sub, new MimeTypeParameterList());
81      }
82  
83      /**
84       * Builds a <code>MimeType</code> with a pre-defined
85       * and valid (or empty) parameter list.
86       *
87       * @param primary the primary type of this <code>MimeType</code>
88       * @param sub the subtype of this <code>MimeType</code>
89       * @param mtpl the requested parameter list
90       * @throws NullPointerException if either <code>primary</code>,
91       *         <code>sub</code> or <code>mtpl</code> is null
92       */
93      public MimeType(String primary, String sub, MimeTypeParameterList mtpl) throws
94  MimeTypeParseException {
95          //    check to see if primary is valid
96          if(isValidToken(primary)) {
97              primaryType = primary.toLowerCase(Locale.ENGLISH);
98          } else {
99              throw new MimeTypeParseException("Primary type is invalid.");
100         }
101 
102         //    check to see if sub is valid
103         if(isValidToken(sub)) {
104             subType = sub.toLowerCase(Locale.ENGLISH);
105         } else {
106             throw new MimeTypeParseException("Sub type is invalid.");
107         }
108 
109         parameters = (MimeTypeParameterList)mtpl.clone();
110     }
111 
112     public int hashCode() {
113 
114         // We sum up the hash codes for all of the strings. This
115         // way, the order of the strings is irrelevant
116         int code = 0;
117         code += primaryType.hashCode();
118         code += subType.hashCode();
119         code += parameters.hashCode();
120         return code;
121     } // hashCode()
122 
123     /**
124      * <code>MimeType</code>s are equal if their primary types,
125      * subtypes, and  parameters are all equal. No default values
126      * are taken into account.
127      * @param thatObject the object to be evaluated as a
128      *    <code>MimeType</code>
129      * @return <code>true</code> if <code>thatObject</code> is
130      *    a <code>MimeType</code>; otherwise returns <code>false</code>
131      */
132     public boolean equals(Object thatObject) {
133         if (!(thatObject instanceof MimeType)) {
134             return false;
135         }
136         MimeType that = (MimeType)thatObject;
137         boolean isIt =
138             ((this.primaryType.equals(that.primaryType)) &&
139              (this.subType.equals(that.subType)) &&
140              (this.parameters.equals(that.parameters)));
141         return isIt;
142     } // equals()
143 
144     /**
145      * A routine for parsing the MIME type out of a String.
146      *
147      * @throws NullPointerException if <code>rawdata</code> is null
148      */
149     private void parse(String rawdata) throws MimeTypeParseException {
150         int slashIndex = rawdata.indexOf('/');
151         int semIndex = rawdata.indexOf(';');
152         if((slashIndex < 0) && (semIndex < 0)) {
153             //    neither character is present, so treat it
154             //    as an error
155             throw new MimeTypeParseException("Unable to find a sub type.");
156         } else if((slashIndex < 0) && (semIndex >= 0)) {
157             //    we have a ';' (and therefore a parameter list),
158             //    but no '/' indicating a sub type is present
159             throw new MimeTypeParseException("Unable to find a sub type.");
160         } else if((slashIndex >= 0) && (semIndex < 0)) {
161             //    we have a primary and sub type but no parameter list
162             primaryType = rawdata.substring(0,slashIndex).
163                 trim().toLowerCase(Locale.ENGLISH);
164             subType = rawdata.substring(slashIndex + 1).
165                 trim().toLowerCase(Locale.ENGLISH);
166             parameters = new MimeTypeParameterList();
167         } else if (slashIndex < semIndex) {
168             //    we have all three items in the proper sequence
169             primaryType = rawdata.substring(0, slashIndex).
170                 trim().toLowerCase(Locale.ENGLISH);
171             subType = rawdata.substring(slashIndex + 1,
172                 semIndex).trim().toLowerCase(Locale.ENGLISH);
173             parameters = new
174 MimeTypeParameterList(rawdata.substring(semIndex));
175         } else {
176             //    we have a ';' lexically before a '/' which means we have a primary type
177             //    & a parameter list but no sub type
178             throw new MimeTypeParseException("Unable to find a sub type.");
179         }
180 
181         //    now validate the primary and sub types
182 
183         //    check to see if primary is valid
184         if(!isValidToken(primaryType)) {
185             throw new MimeTypeParseException("Primary type is invalid.");
186         }
187 
188         //    check to see if sub is valid
189         if(!isValidToken(subType)) {
190             throw new MimeTypeParseException("Sub type is invalid.");
191         }
192     }
193 
194     /**
195      * Retrieve the primary type of this object.
196      */
197     public String getPrimaryType() {
198         return primaryType;
199     }
200 
201     /**
202      * Retrieve the sub type of this object.
203      */
204     public String getSubType() {
205         return subType;
206     }
207 
208     /**
209      * Retrieve a copy of this object's parameter list.
210      */
211     public MimeTypeParameterList getParameters() {
212         return (MimeTypeParameterList)parameters.clone();
213     }
214 
215     /**
216      * Retrieve the value associated with the given name, or null if there
217      * is no current association.
218      */
219     public String getParameter(String name) {
220         return parameters.get(name);
221     }
222 
223     /**
224      * Set the value to be associated with the given name, replacing
225      * any previous association.
226      *
227      * @throw IllegalArgumentException if parameter or value is illegal
228      */
229     public void setParameter(String name, String value) {
230         parameters.set(name, value);
231     }
232 
233     /**
234      * Remove any value associated with the given name.
235      *
236      * @throw IllegalArgumentExcpetion if parameter may not be deleted
237      */
238     public void removeParameter(String name) {
239         parameters.remove(name);
240     }
241 
242     /**
243      * Return the String representation of this object.
244      */
245     public String toString() {
246         return getBaseType() + parameters.toString();
247     }
248 
249     /**
250      * Return a String representation of this object
251      * without the parameter list.
252      */
253     public String getBaseType() {
254         return primaryType + "/" + subType;
255     }
256 
257     /**
258      * Returns <code>true</code> if the primary type and the
259      * subtype of this object are the same as the specified
260      * <code>type</code>; otherwise returns <code>false</code>.
261      *
262      * @param type the type to compare to <code>this</code>'s type
263      * @return <code>true</code> if the primary type and the
264      *    subtype of this object are the same as the
265      *    specified <code>type</code>; otherwise returns
266      *    <code>false</code>
267      */
268     public boolean match(MimeType type) {
269         if (type == null)
270             return false;
271         return primaryType.equals(type.getPrimaryType())
272                     && (subType.equals("*")
273                             || type.getSubType().equals("*")
274                             || (subType.equals(type.getSubType())));
275     }
276 
277     /**
278      * Returns <code>true</code> if the primary type and the
279      * subtype of this object are the same as the content type
280      * described in <code>rawdata</code>; otherwise returns
281      * <code>false</code>.
282      *
283      * @param rawdata the raw data to be examined
284      * @return <code>true</code> if the primary type and the
285      *    subtype of this object are the same as the content type
286      *    described in <code>rawdata</code>; otherwise returns
287      *    <code>false</code>; if <code>rawdata</code> is
288      *    <code>null</code>, returns <code>false</code>
289      */
290     public boolean match(String rawdata) throws MimeTypeParseException {
291         if (rawdata == null)
292             return false;
293         return match(new MimeType(rawdata));
294     }
295 
296     /**
297      * The object implements the writeExternal method to save its contents
298      * by calling the methods of DataOutput for its primitive values or
299      * calling the writeObject method of ObjectOutput for objects, strings
300      * and arrays.
301      * @exception IOException Includes any I/O exceptions that may occur
302      */
303     public void writeExternal(ObjectOutput out) throws IOException {
304         String s = toString(); // contains ASCII chars only
305         // one-to-one correspondence between ASCII char and byte in UTF string
306         if (s.length() <= 65535) { // 65535 is max length of UTF string
307             out.writeUTF(s);
308         } else {
309             out.writeByte(0);
310             out.writeByte(0);
311             out.writeInt(s.length());
312             out.write(s.getBytes());
313         }
314     }
315 
316     /**
317      * The object implements the readExternal method to restore its
318      * contents by calling the methods of DataInput for primitive
319      * types and readObject for objects, strings and arrays.  The
320      * readExternal method must read the values in the same sequence
321      * and with the same types as were written by writeExternal.
322      * @exception ClassNotFoundException If the class for an object being
323      *              restored cannot be found.
324      */
325     public void readExternal(ObjectInput in) throws IOException,
326 ClassNotFoundException {
327         String s = in.readUTF();
328         if (s == null || s.length() == 0) { // long mime type
329             byte[] ba = new byte[in.readInt()];
330             in.readFully(ba);
331             s = new String(ba);
332         }
333         try {
334             parse(s);
335         } catch(MimeTypeParseException e) {
336             throw new IOException(e.toString());
337         }
338     }
339 
340     /**
341      * Returns a clone of this object.
342      * @return a clone of this object
343      */
344 
345     public Object clone() {
346         MimeType newObj = null;
347         try {
348             newObj = (MimeType)super.clone();
349         } catch (CloneNotSupportedException cannotHappen) {
350         }
351         newObj.parameters = (MimeTypeParameterList)parameters.clone();
352         return newObj;
353     }
354 
355     private String    primaryType;
356     private String    subType;
357     private MimeTypeParameterList parameters;
358 
359     //    below here be scary parsing related things
360 
361     /**
362      * Determines whether or not a given character belongs to a legal token.
363      */
364     private static boolean isTokenChar(char c) {
365         return ((c > 040) && (c < 0177)) && (TSPECIALS.indexOf(c) < 0);
366     }
367 
368     /**
369      * Determines whether or not a given string is a legal token.
370      *
371      * @throws NullPointerException if <code>s</code> is null
372      */
373     private boolean isValidToken(String s) {
374         int len = s.length();
375         if(len > 0) {
376             for (int i = 0; i < len; ++i) {
377                 char c = s.charAt(i);
378                 if (!isTokenChar(c)) {
379                     return false;
380                 }
381             }
382             return true;
383         } else {
384             return false;
385         }
386     }
387 
388     /**
389      * A string that holds all the special chars.
390      */
391 
392     private static final String TSPECIALS = "()<>@,;:\\\"/[]?=";
393 
394 } // class MimeType