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 javax.print; 27 28 import java.io.Serializable; 29 30 import java.util.AbstractMap; 31 import java.util.AbstractSet; 32 import java.util.Iterator; 33 import java.util.Map; 34 import java.util.NoSuchElementException; 35 import java.util.Set; 36 import java.util.Vector; 37 38 /** 39 * Class MimeType encapsulates a Multipurpose Internet Mail Extensions (MIME) 40 * media type as defined in <A HREF="http://www.ietf.org/rfc/rfc2045.txt">RFC 41 * 2045</A> and <A HREF="http://www.ietf.org/rfc/rfc2046.txt">RFC 2046</A>. A 42 * MIME type object is part of a {@link DocFlavor DocFlavor} object and 43 * specifies the format of the print data. 44 * <P> 45 * Class MimeType is similar to the like-named 46 * class in package {@link java.awt.datatransfer java.awt.datatransfer}. Class 47 * java.awt.datatransfer.MimeType is not used in the Jini Print Service API 48 * for two reasons: 49 * <OL TYPE=1> 50 * <LI> 51 * Since not all Java profiles include the AWT, the Jini Print Service should 52 * not depend on an AWT class. 53 * <LI> 54 * The implementation of class java.awt.datatransfer.MimeType does not 55 * guarantee 56 * that equivalent MIME types will have the same serialized representation. 57 * Thus, since the Jini Lookup Service (JLUS) matches service attributes based 58 * on equality of serialized representations, JLUS searches involving MIME 59 * types encapsulated in class java.awt.datatransfer.MimeType may incorrectly 60 * fail to match. 61 * </OL> 62 * <P> 63 * Class MimeType's serialized representation is based on the following 64 * canonical form of a MIME type string. Thus, two MIME types that are not 65 * identical but that are equivalent (that have the same canonical form) will 66 * be considered equal by the JLUS's matching algorithm. 67 * <UL> 68 * <LI> The media type, media subtype, and parameters are retained, but all 69 * comments and whitespace characters are discarded. 70 * <LI> The media type, media subtype, and parameter names are converted to 71 * lowercase. 72 * <LI> The parameter values retain their original case, except a charset 73 * parameter value for a text media type is converted to lowercase. 74 * <LI> Quote characters surrounding parameter values are removed. 75 * <LI> Quoting backslash characters inside parameter values are removed. 76 * <LI> The parameters are arranged in ascending order of parameter name. 77 * </UL> 78 * 79 * @author Alan Kaminsky 80 */ 81 class MimeType implements Serializable, Cloneable { 82 83 private static final long serialVersionUID = -2785720609362367683L; 84 85 /** 86 * Array of strings that hold pieces of this MIME type's canonical form. 87 * If the MIME type has <I>n</I> parameters, <I>n</I> >= 0, then the 88 * strings in the array are: 89 * <BR>Index 0 -- Media type. 90 * <BR>Index 1 -- Media subtype. 91 * <BR>Index 2<I>i</I>+2 -- Name of parameter <I>i</I>, 92 * <I>i</I>=0,1,...,<I>n</I>-1. 93 * <BR>Index 2<I>i</I>+3 -- Value of parameter <I>i</I>, 94 * <I>i</I>=0,1,...,<I>n</I>-1. 95 * <BR>Parameters are arranged in ascending order of parameter name. 96 * @serial 97 */ 98 private String[] myPieces; 99 100 /** 101 * String value for this MIME type. Computed when needed and cached. 102 */ 103 private transient String myStringValue = null; 104 105 /** 106 * Parameter map entry set. Computed when needed and cached. 107 */ 108 private transient ParameterMapEntrySet myEntrySet = null; 109 110 /** 111 * Parameter map. Computed when needed and cached. 112 */ 113 private transient ParameterMap myParameterMap = null; 114 115 /** 116 * Parameter map entry. 117 */ 118 private class ParameterMapEntry implements Map.Entry<String, String> { 119 private int myIndex; 120 public ParameterMapEntry(int theIndex) { 121 myIndex = theIndex; 122 } 123 public String getKey(){ 124 return myPieces[myIndex]; 125 } 126 public String getValue(){ 127 return myPieces[myIndex+1]; 128 } 129 public String setValue (String value) { 130 throw new UnsupportedOperationException(); 131 } 132 public boolean equals(Object o) { 133 return (o != null && 134 o instanceof Map.Entry && 135 getKey().equals (((Map.Entry) o).getKey()) && 136 getValue().equals(((Map.Entry) o).getValue())); 137 } 138 public int hashCode() { 139 return getKey().hashCode() ^ getValue().hashCode(); 140 } 141 } 142 143 /** 144 * Parameter map entry set iterator. 145 */ 146 private class ParameterMapEntrySetIterator implements Iterator<Map.Entry<String, String>> { 147 private int myIndex = 2; 148 public boolean hasNext() { 149 return myIndex < myPieces.length; 150 } 151 public Map.Entry<String, String> next() { 152 if (hasNext()) { 153 ParameterMapEntry result = new ParameterMapEntry (myIndex); 154 myIndex += 2; 155 return result; 156 } else { 157 throw new NoSuchElementException(); 158 } 159 } 160 public void remove() { 161 throw new UnsupportedOperationException(); 162 } 163 } 164 165 /** 166 * Parameter map entry set. 170 return new ParameterMapEntrySetIterator(); 171 } 172 public int size() { 173 return (myPieces.length - 2) / 2; 174 } 175 } 176 177 /** 178 * Parameter map. 179 */ 180 private class ParameterMap extends AbstractMap<String, String> { 181 public Set<Map.Entry<String, String>> entrySet() { 182 if (myEntrySet == null) { 183 myEntrySet = new ParameterMapEntrySet(); 184 } 185 return myEntrySet; 186 } 187 } 188 189 /** 190 * Construct a new MIME type object from the given string. The given 191 * string is converted into canonical form and stored internally. 192 * 193 * @param s MIME media type string. 194 * 195 * @exception NullPointerException 196 * (unchecked exception) Thrown if {@code s} is null. 197 * @exception IllegalArgumentException 198 * (unchecked exception) Thrown if {@code s} does not obey the 199 * syntax for a MIME media type string. 200 */ 201 public MimeType(String s) { 202 parse (s); 203 } 204 205 /** 206 * Returns this MIME type object's MIME type string based on the canonical 207 * form. Each parameter value is enclosed in quotes. 208 */ 209 public String getMimeType() { 210 return getStringValue(); 211 } 212 213 /** 214 * Returns this MIME type object's media type. 215 */ 216 public String getMediaType() { 217 return myPieces[0]; 218 } 219 220 /** 221 * Returns this MIME type object's media subtype. 222 */ 223 public String getMediaSubtype() { 224 return myPieces[1]; 225 } 226 227 /** 228 * Returns an unmodifiable map view of the parameters in this MIME type 229 * object. Each entry in the parameter map view consists of a parameter 230 * name String (key) mapping to a parameter value String. If this MIME 231 * type object has no parameters, an empty map is returned. 232 * 233 * @return Parameter map for this MIME type object. 234 */ 235 public Map<String, String> getParameterMap() { 236 if (myParameterMap == null) { 237 myParameterMap = new ParameterMap(); 238 } 239 return myParameterMap; 240 } 241 242 /** 243 * Converts this MIME type object to a string. 244 * 245 * @return MIME type string based on the canonical form. Each parameter 246 * value is enclosed in quotes. 247 */ 248 public String toString() { 249 return getStringValue(); 250 } 251 252 /** 253 * Returns a hash code for this MIME type object. 254 */ 255 public int hashCode() { 256 return getStringValue().hashCode(); 257 } 258 259 /** 260 * Determine if this MIME type object is equal to the given object. The two 261 * are equal if the given object is not null, is an instance of class 262 * net.jini.print.data.MimeType, and has the same canonical form as this 263 * MIME type object (that is, has the same type, subtype, and parameters). 264 * Thus, if two MIME type objects are the same except for comments, they are 265 * considered equal. However, "text/plain" and "text/plain; 266 * charset=us-ascii" are not considered equal, even though they represent 267 * the same media type (because the default character set for plain text is 268 * US-ASCII). 269 * 270 * @param obj Object to test. 271 * 272 * @return True if this MIME type object equals {@code obj}, false 273 * otherwise. 274 */ 275 public boolean equals (Object obj) { 276 return(obj != null && 277 obj instanceof MimeType && 278 getStringValue().equals(((MimeType) obj).getStringValue())); 279 } 280 281 /** 282 * Returns this MIME type's string value in canonical form. 283 */ 284 private String getStringValue() { 285 if (myStringValue == null) { 286 StringBuilder result = new StringBuilder(); 287 result.append (myPieces[0]); 288 result.append ('/'); 289 result.append (myPieces[1]); 290 int n = myPieces.length; 291 for (int i = 2; i < n; i += 2) { 292 result.append(';'); 293 result.append(' '); 294 result.append(myPieces[i]); 295 result.append('='); 296 result.append(addQuotes (myPieces[i+1])); 297 } 298 myStringValue = result.toString(); 299 } 300 return myStringValue; 301 } 302 303 // Hidden classes, constants, and operations for parsing a MIME media type 304 // string. 305 306 // Lexeme types. 307 private static final int TOKEN_LEXEME = 0; 308 private static final int QUOTED_STRING_LEXEME = 1; 309 private static final int TSPECIAL_LEXEME = 2; 310 private static final int EOF_LEXEME = 3; 311 private static final int ILLEGAL_LEXEME = 4; 312 313 // Class for a lexical analyzer. 314 private static class LexicalAnalyzer { 315 protected String mySource; 316 protected int mySourceLength; 317 protected int myCurrentIndex; 318 protected int myLexemeType; 319 protected int myLexemeBeginIndex; 320 protected int myLexemeEndIndex; 321 322 public LexicalAnalyzer(String theSource) { 323 mySource = theSource; 324 mySourceLength = theSource.length(); 325 myCurrentIndex = 0; 326 nextLexeme(); 327 } 328 329 public int getLexemeType() { 330 return myLexemeType; 331 } 332 333 public String getLexeme() { 442 myLexemeEndIndex = myCurrentIndex; 443 state = -1; 444 } else if (Character.isWhitespace 445 (c = mySource.charAt (myCurrentIndex ++))) { 446 myLexemeEndIndex = myCurrentIndex - 1; 447 state = -1; 448 } else if (c == '\"' || c == '(' || c == '/' || 449 c == ';' || c == '=' || c == ')' || 450 c == '<' || c == '>' || c == '@' || 451 c == ',' || c == ':' || c == '\\' || 452 c == '[' || c == ']' || c == '?') { 453 -- myCurrentIndex; 454 myLexemeEndIndex = myCurrentIndex; 455 state = -1; 456 } else { 457 state = 5; 458 } 459 break; 460 } 461 } 462 463 } 464 465 } 466 467 /** 468 * Returns a lowercase version of the given string. The lowercase version 469 * is constructed by applying Character.toLowerCase() to each character of 470 * the given string, which maps characters to lowercase using the rules of 471 * Unicode. This mapping is the same regardless of locale, whereas the 472 * mapping of String.toLowerCase() may be different depending on the 473 * default locale. 474 */ 475 private static String toUnicodeLowerCase(String s) { 476 int n = s.length(); 477 char[] result = new char [n]; 478 for (int i = 0; i < n; ++ i) { 479 result[i] = Character.toLowerCase (s.charAt (i)); 480 } 481 return new String (result); 482 } 483 484 /** 485 * Returns a version of the given string with backslashes removed. 486 */ 487 private static String removeBackslashes(String s) { 488 int n = s.length(); 489 char[] result = new char [n]; 490 int i; 491 int j = 0; 492 char c; 493 for (i = 0; i < n; ++ i) { 494 c = s.charAt (i); 495 if (c == '\\') { 496 c = s.charAt (++ i); 497 } 498 result[j++] = c; 499 } 500 return new String (result, 0, j); 501 } 502 503 /** 504 * Returns a version of the string surrounded by quotes and with interior 505 * quotes preceded by a backslash. 506 */ 507 private static String addQuotes(String s) { 508 int n = s.length(); 509 int i; 510 char c; 511 StringBuilder result = new StringBuilder (n+2); 512 result.append ('\"'); 513 for (i = 0; i < n; ++ i) { 514 c = s.charAt (i); 515 if (c == '\"') { 516 result.append ('\\'); 517 } 518 result.append (c); 519 } 520 result.append ('\"'); 521 return result.toString(); 522 } 523 524 /** 525 * Parses the given string into canonical pieces and stores the pieces in 526 * {@link #myPieces myPieces}. 527 * <P> 528 * Special rules applied: 529 * <UL> 530 * <LI> If the media type is text, the value of a charset parameter is 531 * converted to lowercase. 532 * </UL> 533 * 534 * @param s MIME media type string. 535 * 536 * @exception NullPointerException 537 * (unchecked exception) Thrown if {@code s} is null. 538 * @exception IllegalArgumentException 539 * (unchecked exception) Thrown if {@code s} does not obey the 540 * syntax for a MIME media type string. 541 */ 542 private void parse(String s) { 543 // Initialize. 544 if (s == null) { 545 throw new NullPointerException(); 546 } 547 LexicalAnalyzer theLexer = new LexicalAnalyzer (s); 548 int theLexemeType; 549 Vector<String> thePieces = new Vector<>(); 550 boolean mediaTypeIsText = false; 551 boolean parameterNameIsCharset = false; 552 553 // Parse media type. 554 if (theLexer.getLexemeType() == TOKEN_LEXEME) { 555 String mt = toUnicodeLowerCase (theLexer.getLexeme()); 556 thePieces.add (mt); 557 theLexer.nextLexeme(); 558 mediaTypeIsText = mt.equals ("text"); 559 } else { 560 throw new IllegalArgumentException(); | 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 javax.print; 27 28 import java.io.Serializable; 29 import java.util.AbstractMap; 30 import java.util.AbstractSet; 31 import java.util.Iterator; 32 import java.util.Map; 33 import java.util.NoSuchElementException; 34 import java.util.Set; 35 import java.util.Vector; 36 37 /** 38 * Class {@code MimeType} encapsulates a Multipurpose Internet Mail Extensions 39 * (MIME) media type as defined in 40 * <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a> and 41 * <a href="http://www.ietf.org/rfc/rfc2046.txt">RFC 2046</a>. A MIME type 42 * object is part of a {@link DocFlavor DocFlavor} object and specifies the 43 * format of the print data. 44 * <p> 45 * Class {@code MimeType} is similar to the like-named class in package 46 * {@link java.awt.datatransfer java.awt.datatransfer}. Class 47 * {@link java.awt.datatransfer.MimeType} is not used in the Jini Print Service 48 * API for two reasons: 49 * <ol type=1> 50 * <li>Since not all Java profiles include the AWT, the Jini Print Service 51 * should not depend on an AWT class. 52 * <li>The implementation of class {@code java.awt.datatransfer.MimeType} does 53 * not guarantee that equivalent MIME types will have the same serialized 54 * representation. Thus, since the Jini Lookup Service (JLUS) matches service 55 * attributes based on equality of serialized representations, JLUS searches 56 * involving MIME types encapsulated in class 57 * {@code java.awt.datatransfer.MimeType} may incorrectly fail to match. 58 * </ol> 59 * Class MimeType's serialized representation is based on the following 60 * canonical form of a MIME type string. Thus, two MIME types that are not 61 * identical but that are equivalent (that have the same canonical form) will be 62 * considered equal by the JLUS's matching algorithm. 63 * <ul> 64 * <li>The media type, media subtype, and parameters are retained, but all 65 * comments and whitespace characters are discarded. 66 * <li>The media type, media subtype, and parameter names are converted to 67 * lowercase. 68 * <li>The parameter values retain their original case, except a charset 69 * parameter value for a text media type is converted to lowercase. 70 * <li>Quote characters surrounding parameter values are removed. 71 * <li>Quoting backslash characters inside parameter values are removed. 72 * <li>The parameters are arranged in ascending order of parameter name. 73 * </ul> 74 * 75 * @author Alan Kaminsky 76 */ 77 class MimeType implements Serializable, Cloneable { 78 79 /** 80 * Use serialVersionUID from JDK 1.4 for interoperability. 81 */ 82 private static final long serialVersionUID = -2785720609362367683L; 83 84 /** 85 * Array of strings that hold pieces of this MIME type's canonical form. If 86 * the MIME type has <i>n</i> parameters, <i>n</i> >= 0, then the 87 * strings in the array are: 88 * <br>Index 0 -- Media type. 89 * <br>Index 1 -- Media subtype. 90 * <br>Index 2<i>i</i>+2 -- Name of parameter <i>i</i>, 91 * <i>i</i>=0,1,...,<i>n</i>-1. 92 * <br>Index 2<i>i</i>+3 -- Value of parameter <i>i</i>, 93 * <i>i</i>=0,1,...,<i>n</i>-1. 94 * <br>Parameters are arranged in ascending order of parameter name. 95 * @serial 96 */ 97 private String[] myPieces; 98 99 /** 100 * String value for this MIME type. Computed when needed and cached. 101 */ 102 private transient String myStringValue = null; 103 104 /** 105 * Parameter map entry set. Computed when needed and cached. 106 */ 107 private transient ParameterMapEntrySet myEntrySet = null; 108 109 /** 110 * Parameter map. Computed when needed and cached. 111 */ 112 private transient ParameterMap myParameterMap = null; 113 114 /** 115 * Parameter map entry. 116 */ 117 private class ParameterMapEntry implements Map.Entry<String, String> { 118 119 /** 120 * The index of the entry. 121 */ 122 private int myIndex; 123 124 /** 125 * Constructs a new parameter map entry. 126 * 127 * @param theIndex the index of the entry 128 */ 129 public ParameterMapEntry(int theIndex) { 130 myIndex = theIndex; 131 } 132 public String getKey(){ 133 return myPieces[myIndex]; 134 } 135 public String getValue(){ 136 return myPieces[myIndex+1]; 137 } 138 public String setValue (String value) { 139 throw new UnsupportedOperationException(); 140 } 141 public boolean equals(Object o) { 142 return (o != null && 143 o instanceof Map.Entry && 144 getKey().equals (((Map.Entry) o).getKey()) && 145 getValue().equals(((Map.Entry) o).getValue())); 146 } 147 public int hashCode() { 148 return getKey().hashCode() ^ getValue().hashCode(); 149 } 150 } 151 152 /** 153 * Parameter map entry set iterator. 154 */ 155 private class ParameterMapEntrySetIterator implements Iterator<Map.Entry<String, String>> { 156 157 /** 158 * The current index of the iterator. 159 */ 160 private int myIndex = 2; 161 public boolean hasNext() { 162 return myIndex < myPieces.length; 163 } 164 public Map.Entry<String, String> next() { 165 if (hasNext()) { 166 ParameterMapEntry result = new ParameterMapEntry (myIndex); 167 myIndex += 2; 168 return result; 169 } else { 170 throw new NoSuchElementException(); 171 } 172 } 173 public void remove() { 174 throw new UnsupportedOperationException(); 175 } 176 } 177 178 /** 179 * Parameter map entry set. 183 return new ParameterMapEntrySetIterator(); 184 } 185 public int size() { 186 return (myPieces.length - 2) / 2; 187 } 188 } 189 190 /** 191 * Parameter map. 192 */ 193 private class ParameterMap extends AbstractMap<String, String> { 194 public Set<Map.Entry<String, String>> entrySet() { 195 if (myEntrySet == null) { 196 myEntrySet = new ParameterMapEntrySet(); 197 } 198 return myEntrySet; 199 } 200 } 201 202 /** 203 * Construct a new MIME type object from the given string. The given string 204 * is converted into canonical form and stored internally. 205 * 206 * @param s MIME media type string 207 * @throws NullPointerException if {@code s} is {@code null} 208 * @throws IllegalArgumentException if {@code s} does not obey the syntax 209 * for a MIME media type string 210 */ 211 public MimeType(String s) { 212 parse (s); 213 } 214 215 /** 216 * Returns this MIME type object's MIME type string based on the canonical 217 * form. Each parameter value is enclosed in quotes. 218 * 219 * @return the mime type 220 */ 221 public String getMimeType() { 222 return getStringValue(); 223 } 224 225 /** 226 * Returns this MIME type object's media type. 227 * 228 * @return the media type 229 */ 230 public String getMediaType() { 231 return myPieces[0]; 232 } 233 234 /** 235 * Returns this MIME type object's media subtype. 236 * 237 * @return the media subtype 238 */ 239 public String getMediaSubtype() { 240 return myPieces[1]; 241 } 242 243 /** 244 * Returns an unmodifiable map view of the parameters in this MIME type 245 * object. Each entry in the parameter map view consists of a parameter name 246 * {@code String} (key) mapping to a parameter value {@code String}. If this 247 * MIME type object has no parameters, an empty map is returned. 248 * 249 * @return parameter map for this MIME type object 250 */ 251 public Map<String, String> getParameterMap() { 252 if (myParameterMap == null) { 253 myParameterMap = new ParameterMap(); 254 } 255 return myParameterMap; 256 } 257 258 /** 259 * Converts this MIME type object to a string. 260 * 261 * @return MIME type string based on the canonical form. Each parameter 262 * value is enclosed in quotes. 263 */ 264 public String toString() { 265 return getStringValue(); 266 } 267 268 /** 269 * Returns a hash code for this MIME type object. 270 */ 271 public int hashCode() { 272 return getStringValue().hashCode(); 273 } 274 275 /** 276 * Determine if this MIME type object is equal to the given object. The two 277 * are equal if the given object is not {@code null}, is an instance of 278 * class {@code javax.print.data.MimeType}, and has the same canonical form 279 * as this MIME type object (that is, has the same type, subtype, and 280 * parameters). Thus, if two MIME type objects are the same except for 281 * comments, they are considered equal. However, "text/plain" and 282 * "text/plain; charset=us-ascii" are not considered equal, even though they 283 * represent the same media type (because the default character set for 284 * plain text is US-ASCII). 285 * 286 * @param obj {@code object} to test 287 * @return {@code true} if this MIME type object equals {@code obj}, 288 * {@code false} otherwise 289 */ 290 public boolean equals (Object obj) { 291 return(obj != null && 292 obj instanceof MimeType && 293 getStringValue().equals(((MimeType) obj).getStringValue())); 294 } 295 296 /** 297 * Returns this MIME type's string value in canonical form. 298 * 299 * @return the MIME type's string value in canonical form 300 */ 301 private String getStringValue() { 302 if (myStringValue == null) { 303 StringBuilder result = new StringBuilder(); 304 result.append (myPieces[0]); 305 result.append ('/'); 306 result.append (myPieces[1]); 307 int n = myPieces.length; 308 for (int i = 2; i < n; i += 2) { 309 result.append(';'); 310 result.append(' '); 311 result.append(myPieces[i]); 312 result.append('='); 313 result.append(addQuotes (myPieces[i+1])); 314 } 315 myStringValue = result.toString(); 316 } 317 return myStringValue; 318 } 319 320 // Hidden classes, constants, and operations for parsing a MIME media type 321 // string. 322 323 // Lexeme types. 324 private static final int TOKEN_LEXEME = 0; 325 private static final int QUOTED_STRING_LEXEME = 1; 326 private static final int TSPECIAL_LEXEME = 2; 327 private static final int EOF_LEXEME = 3; 328 private static final int ILLEGAL_LEXEME = 4; 329 330 /** 331 *Class for a lexical analyzer. 332 */ 333 private static class LexicalAnalyzer { 334 protected String mySource; 335 protected int mySourceLength; 336 protected int myCurrentIndex; 337 protected int myLexemeType; 338 protected int myLexemeBeginIndex; 339 protected int myLexemeEndIndex; 340 341 public LexicalAnalyzer(String theSource) { 342 mySource = theSource; 343 mySourceLength = theSource.length(); 344 myCurrentIndex = 0; 345 nextLexeme(); 346 } 347 348 public int getLexemeType() { 349 return myLexemeType; 350 } 351 352 public String getLexeme() { 461 myLexemeEndIndex = myCurrentIndex; 462 state = -1; 463 } else if (Character.isWhitespace 464 (c = mySource.charAt (myCurrentIndex ++))) { 465 myLexemeEndIndex = myCurrentIndex - 1; 466 state = -1; 467 } else if (c == '\"' || c == '(' || c == '/' || 468 c == ';' || c == '=' || c == ')' || 469 c == '<' || c == '>' || c == '@' || 470 c == ',' || c == ':' || c == '\\' || 471 c == '[' || c == ']' || c == '?') { 472 -- myCurrentIndex; 473 myLexemeEndIndex = myCurrentIndex; 474 state = -1; 475 } else { 476 state = 5; 477 } 478 break; 479 } 480 } 481 } 482 } 483 484 /** 485 * Returns a lowercase version of the given string. The lowercase version is 486 * constructed by applying {@code Character.toLowerCase()} to each character 487 * of the given string, which maps characters to lowercase using the rules 488 * of Unicode. This mapping is the same regardless of locale, whereas the 489 * mapping of {@code String.toLowerCase()} may be different depending on the 490 * default locale. 491 * 492 * @param s the string 493 * @return the lowercase version of the string 494 */ 495 private static String toUnicodeLowerCase(String s) { 496 int n = s.length(); 497 char[] result = new char [n]; 498 for (int i = 0; i < n; ++ i) { 499 result[i] = Character.toLowerCase (s.charAt (i)); 500 } 501 return new String (result); 502 } 503 504 /** 505 * Returns a version of the given string with backslashes removed. 506 * 507 * @param s the string 508 * @return the string with backslashes removed 509 */ 510 private static String removeBackslashes(String s) { 511 int n = s.length(); 512 char[] result = new char [n]; 513 int i; 514 int j = 0; 515 char c; 516 for (i = 0; i < n; ++ i) { 517 c = s.charAt (i); 518 if (c == '\\') { 519 c = s.charAt (++ i); 520 } 521 result[j++] = c; 522 } 523 return new String (result, 0, j); 524 } 525 526 /** 527 * Returns a version of the string surrounded by quotes and with interior 528 * quotes preceded by a backslash. 529 * 530 * @param s the string 531 * @return the string surrounded by quotes and with interior quotes preceded 532 * by a backslash 533 */ 534 private static String addQuotes(String s) { 535 int n = s.length(); 536 int i; 537 char c; 538 StringBuilder result = new StringBuilder (n+2); 539 result.append ('\"'); 540 for (i = 0; i < n; ++ i) { 541 c = s.charAt (i); 542 if (c == '\"') { 543 result.append ('\\'); 544 } 545 result.append (c); 546 } 547 result.append ('\"'); 548 return result.toString(); 549 } 550 551 /** 552 * Parses the given string into canonical pieces and stores the pieces in 553 * {@link #myPieces myPieces}. 554 * <p> 555 * Special rules applied: 556 * <ul> 557 * <li>If the media type is text, the value of a charset parameter is 558 * converted to lowercase. 559 * </ul> 560 * 561 * @param s MIME media type string 562 * @throws NullPointerException if {@code s} is {@code null} 563 * @throws IllegalArgumentException if {@code s} does not obey the syntax 564 * for a MIME media type string 565 */ 566 private void parse(String s) { 567 // Initialize. 568 if (s == null) { 569 throw new NullPointerException(); 570 } 571 LexicalAnalyzer theLexer = new LexicalAnalyzer (s); 572 int theLexemeType; 573 Vector<String> thePieces = new Vector<>(); 574 boolean mediaTypeIsText = false; 575 boolean parameterNameIsCharset = false; 576 577 // Parse media type. 578 if (theLexer.getLexemeType() == TOKEN_LEXEME) { 579 String mt = toUnicodeLowerCase (theLexer.getLexeme()); 580 thePieces.add (mt); 581 theLexer.nextLexeme(); 582 mediaTypeIsText = mt.equals ("text"); 583 } else { 584 throw new IllegalArgumentException(); |