KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > print > MimeType


1 /*
2  * @(#)MimeType.java 1.6 03/12/19
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package javax.print;
9
10 import java.io.Serializable JavaDoc;
11
12 import java.util.AbstractMap JavaDoc;
13 import java.util.AbstractSet JavaDoc;
14 import java.util.Iterator JavaDoc;
15 import java.util.Map JavaDoc;
16 import java.util.NoSuchElementException JavaDoc;
17 import java.util.Set JavaDoc;
18 import java.util.Vector JavaDoc;
19
20 /**
21  * Class MimeType encapsulates a Multipurpose Internet Mail Extensions (MIME)
22  * media type as defined in <A HREF="http://www.ietf.org/rfc/rfc2045.txt">RFC
23  * 2045</A> and <A HREF="http://www.ietf.org/rfc/rfc2046.txt">RFC 2046</A>. A
24  * MIME type object is part of a {@link DocFlavor DocFlavor} object and
25  * specifies the format of the print data.
26  * <P>
27  * Class MimeType is similar to the like-named
28  * class in package {@link java.awt.datatransfer java.awt.datatransfer}. Class
29  * java.awt.datatransfer.MimeType is not used in the Jini Print Service API
30  * for two reasons:
31  * <OL TYPE=1>
32  * <LI>
33  * Since not all Java profiles include the AWT, the Jini Print Service should
34  * not depend on an AWT class.
35  * <P>
36  * <LI>
37  * The implementation of class java.awt.datatransfer.MimeType does not
38  * guarantee
39  * that equivalent MIME types will have the same serialized representation.
40  * Thus, since the Jini Lookup Service (JLUS) matches service attributes based
41  * on equality of serialized representations, JLUS searches involving MIME
42  * types encapsulated in class java.awt.datatransfer.MimeType may incorrectly
43  * fail to match.
44  * </OL>
45  * <P>
46  * Class MimeType's serialized representation is based on the following
47  * canonical form of a MIME type string. Thus, two MIME types that are not
48  * identical but that are equivalent (that have the same canonical form) will
49  * be considered equal by the JLUS's matching algorithm.
50  * <UL>
51  * <LI> The media type, media subtype, and parameters are retained, but all
52  * comments and whitespace characters are discarded.
53  * <LI> The media type, media subtype, and parameter names are converted to
54  * lowercase.
55  * <LI> The parameter values retain their original case, except a charset
56  * parameter value for a text media type is converted to lowercase.
57  * <LI> Quote characters surrounding parameter values are removed.
58  * <LI> Quoting backslash characters inside parameter values are removed.
59  * <LI> The parameters are arranged in ascending order of parameter name.
60  * </UL>
61  * <P>
62  *
63  * @author Alan Kaminsky
64  */

65 class MimeType implements Serializable JavaDoc, Cloneable JavaDoc {
66
67     private static final long serialVersionUID = -2785720609362367683L;
68
69     /**
70      * Array of strings that hold pieces of this MIME type's canonical form.
71      * If the MIME type has <I>n</I> parameters, <I>n</I> &gt;= 0, then the
72      * strings in the array are:
73      * <BR>Index 0 -- Media type.
74      * <BR>Index 1 -- Media subtype.
75      * <BR>Index 2<I>i</I>+2 -- Name of parameter <I>i</I>,
76      * <I>i</I>=0,1,...,<I>n</I>-1.
77      * <BR>Index 2<I>i</I>+3 -- Value of parameter <I>i</I>,
78      * <I>i</I>=0,1,...,<I>n</I>-1.
79      * <BR>Parameters are arranged in ascending order of parameter name.
80      * @serial
81      */

82     private String JavaDoc[] myPieces;
83
84     /**
85      * String value for this MIME type. Computed when needed and cached.
86      */

87     private transient String JavaDoc myStringValue = null;
88     
89     /**
90      * Parameter map entry set. Computed when needed and cached.
91      */

92     private transient ParameterMapEntrySet myEntrySet = null;
93     
94     /**
95      * Parameter map. Computed when needed and cached.
96      */

97     private transient ParameterMap myParameterMap = null;
98     
99     /**
100      * Parameter map entry.
101      */

102     private class ParameterMapEntry implements Map.Entry JavaDoc {
103     private int myIndex;
104     public ParameterMapEntry(int theIndex) {
105         myIndex = theIndex;
106     }
107     public Object JavaDoc getKey(){
108         return myPieces[myIndex];
109     }
110     public Object JavaDoc getValue(){
111         return myPieces[myIndex+1];
112     }
113     public Object JavaDoc setValue (Object JavaDoc value) {
114         throw new UnsupportedOperationException JavaDoc();
115     }
116     public boolean equals(Object JavaDoc o) {
117         return (o != null &&
118             o instanceof Map.Entry JavaDoc &&
119             getKey().equals (((Map.Entry JavaDoc) o).getKey()) &&
120             getValue().equals(((Map.Entry JavaDoc) o).getValue()));
121     }
122     public int hashCode() {
123         return getKey().hashCode() ^ getValue().hashCode();
124     }
125     }
126
127     /**
128      * Parameter map entry set iterator.
129      */

130     private class ParameterMapEntrySetIterator implements Iterator JavaDoc {
131     private int myIndex = 2;
132     public boolean hasNext() {
133         return myIndex < myPieces.length;
134     }
135     public Object JavaDoc next() {
136         if (hasNext()) {
137         ParameterMapEntry result = new ParameterMapEntry (myIndex);
138         myIndex += 2;
139         return result;
140         } else {
141         throw new NoSuchElementException JavaDoc();
142         }
143     }
144     public void remove() {
145         throw new UnsupportedOperationException JavaDoc();
146     }
147     }
148
149     /**
150      * Parameter map entry set.
151      */

152     private class ParameterMapEntrySet extends AbstractSet JavaDoc {
153     public Iterator JavaDoc iterator() {
154         return new ParameterMapEntrySetIterator();
155     }
156     public int size() {
157         return (myPieces.length - 2) / 2;
158     }
159     }
160
161     /**
162      * Parameter map.
163      */

164     private class ParameterMap extends AbstractMap JavaDoc {
165     public Set JavaDoc entrySet() {
166         if (myEntrySet == null) {
167         myEntrySet = new ParameterMapEntrySet();
168         }
169         return myEntrySet;
170     }
171     }
172
173     /**
174      * Construct a new MIME type object from the given string. The given
175      * string is converted into canonical form and stored internally.
176      *
177      * @param s MIME media type string.
178      *
179      * @exception NullPointerException
180      * (unchecked exception) Thrown if <CODE>s</CODE> is null.
181      * @exception IllegalArgumentException
182      * (unchecked exception) Thrown if <CODE>s</CODE> does not obey the
183      * syntax for a MIME media type string.
184      */

185     public MimeType(String JavaDoc s) {
186     parse (s);
187     }
188
189     /**
190      * Returns this MIME type object's MIME type string based on the canonical
191      * form. Each parameter value is enclosed in quotes.
192      */

193     public String JavaDoc getMimeType() {
194     return getStringValue();
195     }
196     
197     /**
198      * Returns this MIME type object's media type.
199      */

200     public String JavaDoc getMediaType() {
201     return myPieces[0];
202     }
203
204     /**
205      * Returns this MIME type object's media subtype.
206      */

207     public String JavaDoc getMediaSubtype() {
208     return myPieces[1];
209     }
210
211     /**
212      * Returns an unmodifiable map view of the parameters in this MIME type
213      * object. Each entry in the parameter map view consists of a parameter
214      * name String (key) mapping to a parameter value String. If this MIME
215      * type object has no parameters, an empty map is returned.
216      *
217      * @return Parameter map for this MIME type object.
218      */

219     public Map JavaDoc getParameterMap() {
220     if (myParameterMap == null) {
221         myParameterMap = new ParameterMap();
222     }
223     return myParameterMap;
224     }
225
226     /**
227      * Converts this MIME type object to a string.
228      *
229      * @return MIME type string based on the canonical form. Each parameter
230      * value is enclosed in quotes.
231      */

232     public String JavaDoc toString() {
233     return getStringValue();
234     }
235
236     /**
237      * Returns a hash code for this MIME type object.
238      */

239     public int hashCode() {
240     return getStringValue().hashCode();
241     }
242
243     /**
244      * Determine if this MIME type object is equal to the given object. The two
245      * are equal if the given object is not null, is an instance of class
246      * net.jini.print.data.MimeType, and has the same canonical form as this
247      * MIME type object (that is, has the same type, subtype, and parameters).
248      * Thus, if two MIME type objects are the same except for comments, they are
249      * considered equal. However, "text/plain" and "text/plain;
250      * charset=us-ascii" are not considered equal, even though they represent
251      * the same media type (because the default character set for plain text is
252      * US-ASCII).
253      *
254      * @param obj Object to test.
255      *
256      * @return True if this MIME type object equals <CODE>obj</CODE>, false
257      * otherwise.
258      */

259     public boolean equals (Object JavaDoc obj) {
260     return(obj != null &&
261            obj instanceof MimeType JavaDoc &&
262            getStringValue().equals(((MimeType JavaDoc) obj).getStringValue()));
263     }
264
265     /**
266      * Returns this MIME type's string value in canonical form.
267      */

268     private String JavaDoc getStringValue() {
269     if (myStringValue == null) {
270         StringBuffer JavaDoc result = new StringBuffer JavaDoc();
271         result.append (myPieces[0]);
272         result.append ('/');
273         result.append (myPieces[1]);
274         int n = myPieces.length;
275         for (int i = 2; i < n; i += 2) {
276         result.append(';');
277         result.append(' ');
278         result.append(myPieces[i]);
279         result.append('=');
280         result.append(addQuotes (myPieces[i+1]));
281         }
282         myStringValue = result.toString();
283     }
284     return myStringValue;
285     }
286     
287 // Hidden classes, constants, and operations for parsing a MIME media type
288
// string.
289

290     // Lexeme types.
291
private static final int TOKEN_LEXEME = 0;
292     private static final int QUOTED_STRING_LEXEME = 1;
293     private static final int TSPECIAL_LEXEME = 2;
294     private static final int EOF_LEXEME = 3;
295     private static final int ILLEGAL_LEXEME = 4;
296     
297     // Class for a lexical analyzer.
298
private static class LexicalAnalyzer {
299     protected String JavaDoc mySource;
300     protected int mySourceLength;
301     protected int myCurrentIndex;
302     protected int myLexemeType;
303     protected int myLexemeBeginIndex;
304     protected int myLexemeEndIndex;
305     
306     public LexicalAnalyzer(String JavaDoc theSource) {
307         mySource = theSource;
308         mySourceLength = theSource.length();
309         myCurrentIndex = 0;
310         nextLexeme();
311     }
312
313     public int getLexemeType() {
314         return myLexemeType;
315     }
316
317     public String JavaDoc getLexeme() {
318         return(myLexemeBeginIndex >= mySourceLength ?
319            null :
320            mySource.substring(myLexemeBeginIndex, myLexemeEndIndex));
321     }
322
323     public char getLexemeFirstCharacter() {
324         return(myLexemeBeginIndex >= mySourceLength ?
325            '\u0000' :
326            mySource.charAt(myLexemeBeginIndex));
327     }
328
329     public void nextLexeme() {
330         int state = 0;
331         int commentLevel = 0;
332         char c;
333         while (state >= 0) {
334         switch (state) {
335             // Looking for a token, quoted string, or tspecial
336
case 0:
337             if (myCurrentIndex >= mySourceLength) {
338             myLexemeType = EOF_LEXEME;
339             myLexemeBeginIndex = mySourceLength;
340             myLexemeEndIndex = mySourceLength;
341             state = -1;
342             } else if (Character.isWhitespace
343                    (c = mySource.charAt (myCurrentIndex ++))) {
344             state = 0;
345             } else if (c == '\"') {
346             myLexemeType = QUOTED_STRING_LEXEME;
347             myLexemeBeginIndex = myCurrentIndex;
348             state = 1;
349             } else if (c == '(') {
350             ++ commentLevel;
351             state = 3;
352             } else if (c == '/' || c == ';' || c == '=' ||
353                    c == ')' || c == '<' || c == '>' ||
354                    c == '@' || c == ',' || c == ':' ||
355                    c == '\\' || c == '[' || c == ']' ||
356                    c == '?') {
357             myLexemeType = TSPECIAL_LEXEME;
358             myLexemeBeginIndex = myCurrentIndex - 1;
359             myLexemeEndIndex = myCurrentIndex;
360             state = -1;
361             } else {
362             myLexemeType = TOKEN_LEXEME;
363             myLexemeBeginIndex = myCurrentIndex - 1;
364             state = 5;
365             }
366             break;
367             // In a quoted string
368
case 1:
369             if (myCurrentIndex >= mySourceLength) {
370             myLexemeType = ILLEGAL_LEXEME;
371             myLexemeBeginIndex = mySourceLength;
372             myLexemeEndIndex = mySourceLength;
373             state = -1;
374             } else if ((c = mySource.charAt (myCurrentIndex ++)) == '\"') {
375             myLexemeEndIndex = myCurrentIndex - 1;
376             state = -1;
377             } else if (c == '\\') {
378             state = 2;
379             } else {
380             state = 1;
381             }
382             break;
383             // In a quoted string, backslash seen
384
case 2:
385             if (myCurrentIndex >= mySourceLength) {
386             myLexemeType = ILLEGAL_LEXEME;
387             myLexemeBeginIndex = mySourceLength;
388             myLexemeEndIndex = mySourceLength;
389             state = -1;
390             } else {
391             ++ myCurrentIndex;
392             state = 1;
393             } break;
394             // In a comment
395
case 3: if (myCurrentIndex >= mySourceLength) {
396             myLexemeType = ILLEGAL_LEXEME;
397             myLexemeBeginIndex = mySourceLength;
398             myLexemeEndIndex = mySourceLength;
399             state = -1;
400         } else if ((c = mySource.charAt (myCurrentIndex ++)) == '(') {
401             ++ commentLevel;
402             state = 3;
403         } else if (c == ')') {
404             -- commentLevel;
405             state = commentLevel == 0 ? 0 : 3;
406         } else if (c == '\\') {
407             state = 4;
408         } else { state = 3;
409         }
410         break;
411         // In a comment, backslash seen
412
case 4:
413             if (myCurrentIndex >= mySourceLength) {
414             myLexemeType = ILLEGAL_LEXEME;
415             myLexemeBeginIndex = mySourceLength;
416             myLexemeEndIndex = mySourceLength;
417             state = -1;
418             } else {
419             ++ myCurrentIndex;
420             state = 3;
421             }
422             break;
423             // In a token
424
case 5:
425             if (myCurrentIndex >= mySourceLength) {
426             myLexemeEndIndex = myCurrentIndex;
427             state = -1;
428             } else if (Character.isWhitespace
429                    (c = mySource.charAt (myCurrentIndex ++))) {
430             myLexemeEndIndex = myCurrentIndex - 1;
431             state = -1;
432             } else if (c == '\"' || c == '(' || c == '/' ||
433                    c == ';' || c == '=' || c == ')' ||
434                    c == '<' || c == '>' || c == '@' ||
435                    c == ',' || c == ':' || c == '\\' ||
436                    c == '[' || c == ']' || c == '?') {
437             -- myCurrentIndex;
438             myLexemeEndIndex = myCurrentIndex;
439             state = -1;
440             } else {
441             state = 5;
442             }
443             break;
444         }
445         }
446         
447     }
448     
449     }
450
451     /**
452      * Returns a lowercase version of the given string. The lowercase version
453      * is constructed by applying Character.toLowerCase() to each character of
454      * the given string, which maps characters to lowercase using the rules of
455      * Unicode. This mapping is the same regardless of locale, whereas the
456      * mapping of String.toLowerCase() may be different depending on the
457      * default locale.
458      */

459     private static String JavaDoc toUnicodeLowerCase(String JavaDoc s) {
460     int n = s.length();
461     char[] result = new char [n];
462     for (int i = 0; i < n; ++ i) {
463         result[i] = Character.toLowerCase (s.charAt (i));
464     }
465     return new String JavaDoc (result);
466     }
467
468     /**
469      * Returns a version of the given string with backslashes removed.
470      */

471     private static String JavaDoc removeBackslashes(String JavaDoc s) {
472     int n = s.length();
473     char[] result = new char [n];
474     int i;
475     int j = 0;
476     char c;
477     for (i = 0; i < n; ++ i) {
478         c = s.charAt (i);
479         if (c == '\\') {
480         c = s.charAt (++ i);
481         }
482         result[j++] = c;
483     }
484     return new String JavaDoc (result, 0, j);
485     }
486
487     /**
488      * Returns a version of the string surrounded by quotes and with interior
489      * quotes preceded by a backslash.
490      */

491     private static String JavaDoc addQuotes(String JavaDoc s) {
492     int n = s.length();
493     int i;
494     char c;
495     StringBuffer JavaDoc result = new StringBuffer JavaDoc (n+2);
496     result.append ('\"');
497     for (i = 0; i < n; ++ i) {
498         c = s.charAt (i);
499         if (c == '\"') {
500         result.append ('\\');
501         }
502         result.append (c);
503     }
504     result.append ('\"');
505     return result.toString();
506     }
507
508     /**
509      * Parses the given string into canonical pieces and stores the pieces in
510      * {@link #myPieces <CODE>myPieces</CODE>}.
511      * <P>
512      * Special rules applied:
513      * <UL>
514      * <LI> If the media type is text, the value of a charset parameter is
515      * converted to lowercase.
516      * </UL>
517      *
518      * @param s MIME media type string.
519      *
520      * @exception NullPointerException
521      * (unchecked exception) Thrown if <CODE>s</CODE> is null.
522      * @exception IllegalArgumentException
523      * (unchecked exception) Thrown if <CODE>s</CODE> does not obey the
524      * syntax for a MIME media type string.
525      */

526     private void parse(String JavaDoc s) {
527     // Initialize.
528
if (s == null) {
529         throw new NullPointerException JavaDoc();
530     }
531     LexicalAnalyzer theLexer = new LexicalAnalyzer (s);
532     int theLexemeType;
533     Vector JavaDoc thePieces = new Vector JavaDoc();
534     boolean mediaTypeIsText = false;
535     boolean parameterNameIsCharset = false;
536
537     // Parse media type.
538
if (theLexer.getLexemeType() == TOKEN_LEXEME) {
539         String JavaDoc mt = toUnicodeLowerCase (theLexer.getLexeme());
540         thePieces.add (mt);
541         theLexer.nextLexeme();
542         mediaTypeIsText = mt.equals ("text");
543     } else {
544         throw new IllegalArgumentException JavaDoc();
545     }
546     // Parse slash.
547
if (theLexer.getLexemeType() == TSPECIAL_LEXEME &&
548           theLexer.getLexemeFirstCharacter() == '/') {
549         theLexer.nextLexeme();
550     } else {
551         throw new IllegalArgumentException JavaDoc();
552     }
553     if (theLexer.getLexemeType() == TOKEN_LEXEME) {
554         thePieces.add (toUnicodeLowerCase (theLexer.getLexeme()));
555         theLexer.nextLexeme();
556     } else {
557         throw new IllegalArgumentException JavaDoc();
558     }
559     // Parse zero or more parameters.
560
while (theLexer.getLexemeType() == TSPECIAL_LEXEME &&
561            theLexer.getLexemeFirstCharacter() == ';') {
562         // Parse semicolon.
563
theLexer.nextLexeme();
564
565         // Parse parameter name.
566
if (theLexer.getLexemeType() == TOKEN_LEXEME) {
567         String JavaDoc pn = toUnicodeLowerCase (theLexer.getLexeme());
568         thePieces.add (pn);
569         theLexer.nextLexeme();
570         parameterNameIsCharset = pn.equals ("charset");
571         } else {
572         throw new IllegalArgumentException JavaDoc();
573         }
574         
575         // Parse equals.
576
if (theLexer.getLexemeType() == TSPECIAL_LEXEME &&
577         theLexer.getLexemeFirstCharacter() == '=') {
578         theLexer.nextLexeme();
579         } else {
580         throw new IllegalArgumentException JavaDoc();
581         }
582
583         // Parse parameter value.
584
if (theLexer.getLexemeType() == TOKEN_LEXEME) {
585         String JavaDoc pv = theLexer.getLexeme();
586         thePieces.add(mediaTypeIsText && parameterNameIsCharset ?
587                   toUnicodeLowerCase (pv) :
588                   pv);
589         theLexer.nextLexeme();
590         } else if (theLexer.getLexemeType() == QUOTED_STRING_LEXEME) {
591         String JavaDoc pv = removeBackslashes (theLexer.getLexeme());
592         thePieces.add(mediaTypeIsText && parameterNameIsCharset ?
593                   toUnicodeLowerCase (pv) :
594                   pv);
595         theLexer.nextLexeme();
596         } else {
597         throw new IllegalArgumentException JavaDoc();
598         }
599     }
600
601     // Make sure we've consumed everything.
602
if (theLexer.getLexemeType() != EOF_LEXEME) {
603         throw new IllegalArgumentException JavaDoc();
604     }
605
606     // Save the pieces. Parameters are not in ascending order yet.
607
int n = thePieces.size();
608     myPieces = (String JavaDoc[]) thePieces.toArray (new String JavaDoc [n]);
609
610     // Sort the parameters into ascending order using an insertion sort.
611
int i, j;
612     String JavaDoc temp;
613     for (i = 4; i < n; i += 2) {
614         j = 2;
615         while (j < i && myPieces[j].compareTo (myPieces[i]) <= 0) {
616         j += 2;
617         }
618         while (j < i) {
619         temp = myPieces[j];
620         myPieces[j] = myPieces[i];
621         myPieces[i] = temp;
622         temp = myPieces[j+1];
623         myPieces[j+1] = myPieces[i+1];
624         myPieces[i+1] = temp;
625         j += 2;
626         }
627     }
628     }
629 }
630
Popular Tags