blob: 4a9e6bcb3b574290f8000c7733eb728c33a2a9b7 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package java.awt.datatransfer;
27
28import java.util.Enumeration;
29import java.util.Hashtable;
30import java.util.Iterator;
31import java.util.Map;
32import java.util.Set;
33
34
35/**
36 * An object that encapsualtes the parameter list of a MimeType
37 * as defined in RFC 2045 and 2046.
38 *
39 * @author jeff.dunn@eng.sun.com
40 */
41class MimeTypeParameterList implements Cloneable {
42
43 /**
44 * Default constructor.
45 */
46 public MimeTypeParameterList() {
47 parameters = new Hashtable();
48 }
49
50 public MimeTypeParameterList(String rawdata)
51 throws MimeTypeParseException
52 {
53 parameters = new Hashtable();
54
55 // now parse rawdata
56 parse(rawdata);
57 }
58
59 public int hashCode() {
60 int code = Integer.MAX_VALUE/45; // "random" value for empty lists
61 String paramName = null;
62 Enumeration enum_ = this.getNames();
63
64 while (enum_.hasMoreElements()) {
65 paramName = (String)enum_.nextElement();
66 code += paramName.hashCode();
67 code += this.get(paramName).hashCode();
68 }
69
70 return code;
71 } // hashCode()
72
73 /**
74 * Two parameter lists are considered equal if they have exactly
75 * the same set of parameter names and associated values. The
76 * order of the parameters is not considered.
77 */
78 public boolean equals(Object thatObject) {
79 //System.out.println("MimeTypeParameterList.equals("+this+","+thatObject+")");
80 if (!(thatObject instanceof MimeTypeParameterList)) {
81 return false;
82 }
83 MimeTypeParameterList that = (MimeTypeParameterList)thatObject;
84 if (this.size() != that.size()) {
85 return false;
86 }
87 String name = null;
88 String thisValue = null;
89 String thatValue = null;
90 Set entries = parameters.entrySet();
91 Iterator iterator = entries.iterator();
92 Map.Entry entry = null;
93 while (iterator.hasNext()) {
94 entry = (Map.Entry)iterator.next();
95 name = (String)entry.getKey();
96 thisValue = (String)entry.getValue();
97 thatValue = (String)that.parameters.get(name);
98 if ((thisValue == null) || (thatValue == null)) {
99 // both null -> equal, only one null -> not equal
100 if (thisValue != thatValue) {
101 return false;
102 }
103 } else if (!thisValue.equals(thatValue)) {
104 return false;
105 }
106 } // while iterator
107
108 return true;
109 } // equals()
110
111 /**
112 * A routine for parsing the parameter list out of a String.
113 */
114 protected void parse(String rawdata) throws MimeTypeParseException {
115 int length = rawdata.length();
116 if(length > 0) {
117 int currentIndex = skipWhiteSpace(rawdata, 0);
118 int lastIndex = 0;
119
120 if(currentIndex < length) {
121 char currentChar = rawdata.charAt(currentIndex);
122 while ((currentIndex < length) && (currentChar == ';')) {
123 String name;
124 String value;
125 boolean foundit;
126
127 // eat the ';'
128 ++currentIndex;
129
130 // now parse the parameter name
131
132 // skip whitespace
133 currentIndex = skipWhiteSpace(rawdata, currentIndex);
134
135 if(currentIndex < length) {
136 // find the end of the token char run
137 lastIndex = currentIndex;
138 currentChar = rawdata.charAt(currentIndex);
139 while((currentIndex < length) && isTokenChar(currentChar)) {
140 ++currentIndex;
141 currentChar = rawdata.charAt(currentIndex);
142 }
143 name = rawdata.substring(lastIndex, currentIndex).toLowerCase();
144
145 // now parse the '=' that separates the name from the value
146
147 // skip whitespace
148 currentIndex = skipWhiteSpace(rawdata, currentIndex);
149
150 if((currentIndex < length) && (rawdata.charAt(currentIndex) == '=')) {
151 // eat it and parse the parameter value
152 ++currentIndex;
153
154 // skip whitespace
155 currentIndex = skipWhiteSpace(rawdata, currentIndex);
156
157 if(currentIndex < length) {
158 // now find out whether or not we have a quoted value
159 currentChar = rawdata.charAt(currentIndex);
160 if(currentChar == '"') {
161 // yup it's quoted so eat it and capture the quoted string
162 ++currentIndex;
163 lastIndex = currentIndex;
164
165 if(currentIndex < length) {
166 // find the next unescqped quote
167 foundit = false;
168 while((currentIndex < length) && !foundit) {
169 currentChar = rawdata.charAt(currentIndex);
170 if(currentChar == '\\') {
171 // found an escape sequence so pass this and the next character
172 currentIndex += 2;
173 } else if(currentChar == '"') {
174 // foundit!
175 foundit = true;
176 } else {
177 ++currentIndex;
178 }
179 }
180 if(currentChar == '"') {
181 value = unquote(rawdata.substring(lastIndex, currentIndex));
182 // eat the quote
183 ++currentIndex;
184 } else {
185 throw new MimeTypeParseException("Encountered unterminated quoted parameter value.");
186 }
187 } else {
188 throw new MimeTypeParseException("Encountered unterminated quoted parameter value.");
189 }
190 } else if(isTokenChar(currentChar)) {
191 // nope it's an ordinary token so it ends with a non-token char
192 lastIndex = currentIndex;
193 foundit = false;
194 while((currentIndex < length) && !foundit) {
195 currentChar = rawdata.charAt(currentIndex);
196
197 if(isTokenChar(currentChar)) {
198 ++currentIndex;
199 } else {
200 foundit = true;
201 }
202 }
203 value = rawdata.substring(lastIndex, currentIndex);
204 } else {
205 // it ain't a value
206 throw new MimeTypeParseException("Unexpected character encountered at index " + currentIndex);
207 }
208
209 // now put the data into the hashtable
210 parameters.put(name, value);
211 } else {
212 throw new MimeTypeParseException("Couldn't find a value for parameter named " + name);
213 }
214 } else {
215 throw new MimeTypeParseException("Couldn't find the '=' that separates a parameter name from its value.");
216 }
217 } else {
218 throw new MimeTypeParseException("Couldn't find parameter name");
219 }
220
221 // setup the next iteration
222 currentIndex = skipWhiteSpace(rawdata, currentIndex);
223 if(currentIndex < length) {
224 currentChar = rawdata.charAt(currentIndex);
225 }
226 }
227 if(currentIndex < length) {
228 throw new MimeTypeParseException("More characters encountered in input than expected.");
229 }
230 }
231 }
232 }
233
234 /**
235 * return the number of name-value pairs in this list.
236 */
237 public int size() {
238 return parameters.size();
239 }
240
241 /**
242 * Determine whether or not this list is empty.
243 */
244 public boolean isEmpty() {
245 return parameters.isEmpty();
246 }
247
248 /**
249 * Retrieve the value associated with the given name, or null if there
250 * is no current association.
251 */
252 public String get(String name) {
253 return (String)parameters.get(name.trim().toLowerCase());
254 }
255
256 /**
257 * Set the value to be associated with the given name, replacing
258 * any previous association.
259 */
260 public void set(String name, String value) {
261 parameters.put(name.trim().toLowerCase(), value);
262 }
263
264 /**
265 * Remove any value associated with the given name.
266 */
267 public void remove(String name) {
268 parameters.remove(name.trim().toLowerCase());
269 }
270
271 /**
272 * Retrieve an enumeration of all the names in this list.
273 */
274 public Enumeration getNames() {
275 return parameters.keys();
276 }
277
278 public String toString() {
279 // Heuristic: 8 characters per field
280 StringBuilder buffer = new StringBuilder(parameters.size() * 16);
281
282 Enumeration keys = parameters.keys();
283 while(keys.hasMoreElements())
284 {
285 buffer.append("; ");
286
287 String key = (String)keys.nextElement();
288 buffer.append(key);
289 buffer.append('=');
290 buffer.append(quote((String)parameters.get(key)));
291 }
292
293 return buffer.toString();
294 }
295
296 /**
297 * @return a clone of this object
298 */
299
300 public Object clone() {
301 MimeTypeParameterList newObj = null;
302 try {
303 newObj = (MimeTypeParameterList)super.clone();
304 } catch (CloneNotSupportedException cannotHappen) {
305 }
306 newObj.parameters = (Hashtable)parameters.clone();
307 return newObj;
308 }
309
310 private Hashtable parameters;
311
312 // below here be scary parsing related things
313
314 /**
315 * Determine whether or not a given character belongs to a legal token.
316 */
317 private static boolean isTokenChar(char c) {
318 return ((c > 040) && (c < 0177)) && (TSPECIALS.indexOf(c) < 0);
319 }
320
321 /**
322 * return the index of the first non white space character in
323 * rawdata at or after index i.
324 */
325 private static int skipWhiteSpace(String rawdata, int i) {
326 int length = rawdata.length();
327 if (i < length) {
328 char c = rawdata.charAt(i);
329 while ((i < length) && Character.isWhitespace(c)) {
330 ++i;
331 c = rawdata.charAt(i);
332 }
333 }
334
335 return i;
336 }
337
338 /**
339 * A routine that knows how and when to quote and escape the given value.
340 */
341 private static String quote(String value) {
342 boolean needsQuotes = false;
343
344 // check to see if we actually have to quote this thing
345 int length = value.length();
346 for(int i = 0; (i < length) && !needsQuotes; ++i) {
347 needsQuotes = !isTokenChar(value.charAt(i));
348 }
349
350 if(needsQuotes) {
351 StringBuilder buffer = new StringBuilder((int)(length * 1.5));
352
353 // add the initial quote
354 buffer.append('"');
355
356 // add the properly escaped text
357 for(int i = 0; i < length; ++i) {
358 char c = value.charAt(i);
359 if((c == '\\') || (c == '"')) {
360 buffer.append('\\');
361 }
362 buffer.append(c);
363 }
364
365 // add the closing quote
366 buffer.append('"');
367
368 return buffer.toString();
369 }
370 else
371 {
372 return value;
373 }
374 }
375
376 /**
377 * A routine that knows how to strip the quotes and escape sequences from the given value.
378 */
379 private static String unquote(String value) {
380 int valueLength = value.length();
381 StringBuilder buffer = new StringBuilder(valueLength);
382
383 boolean escaped = false;
384 for(int i = 0; i < valueLength; ++i) {
385 char currentChar = value.charAt(i);
386 if(!escaped && (currentChar != '\\')) {
387 buffer.append(currentChar);
388 } else if(escaped) {
389 buffer.append(currentChar);
390 escaped = false;
391 } else {
392 escaped = true;
393 }
394 }
395
396 return buffer.toString();
397 }
398
399 /**
400 * A string that holds all the special chars.
401 */
402 private static final String TSPECIALS = "()<>@,;:\\\"/[]?=";
403
404}