1 // ========================================================================
2 // Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
3 // ------------------------------------------------------------------------
4 // All rights reserved. This program and the accompanying materials
5 // are made available under the terms of the Eclipse Public License v1.0
6 // and Apache License v2.0 which accompanies this distribution.
7 // The Eclipse Public License is available at
8 // http://www.eclipse.org/legal/epl-v10.html
9 // The Apache License v2.0 is available at
10 // http://www.opensource.org/licenses/apache2.0.php
11 // You may elect to redistribute this code under either of these licenses.
12 // ========================================================================
13
14 package org.eclipse.jetty.util;
15
16 import java.io.IOException;
17 import java.util.NoSuchElementException;
18 import java.util.StringTokenizer;
19
20 /* ------------------------------------------------------------ */
21 /** StringTokenizer with Quoting support.
22 *
23 * This class is a copy of the java.util.StringTokenizer API and
24 * the behaviour is the same, except that single and doulbe quoted
25 * string values are recognized.
26 * Delimiters within quotes are not considered delimiters.
27 * Quotes can be escaped with '\'.
28 *
29 * @see java.util.StringTokenizer
30 *
31 */
32 public class QuotedStringTokenizer
33 extends StringTokenizer
34 {
35 private final static String __delim="\t\n\r";
36 private String _string;
37 private String _delim = __delim;
38 private boolean _returnQuotes=false;
39 private boolean _returnDelimiters=false;
40 private StringBuffer _token;
41 private boolean _hasToken=false;
42 private int _i=0;
43 private int _lastStart=0;
44 private boolean _double=true;
45 private boolean _single=true;
46
47 /* ------------------------------------------------------------ */
48 public QuotedStringTokenizer(String str,
49 String delim,
50 boolean returnDelimiters,
51 boolean returnQuotes)
52 {
53 super("");
54 _string=str;
55 if (delim!=null)
56 _delim=delim;
57 _returnDelimiters=returnDelimiters;
58 _returnQuotes=returnQuotes;
59
60 if (_delim.indexOf('\'')>=0 ||
61 _delim.indexOf('"')>=0)
62 throw new Error("Can't use quotes as delimiters: "+_delim);
63
64 _token=new StringBuffer(_string.length()>1024?512:_string.length()/2);
65 }
66
67 /* ------------------------------------------------------------ */
68 public QuotedStringTokenizer(String str,
69 String delim,
70 boolean returnDelimiters)
71 {
72 this(str,delim,returnDelimiters,false);
73 }
74
75 /* ------------------------------------------------------------ */
76 public QuotedStringTokenizer(String str,
77 String delim)
78 {
79 this(str,delim,false,false);
80 }
81
82 /* ------------------------------------------------------------ */
83 public QuotedStringTokenizer(String str)
84 {
85 this(str,null,false,false);
86 }
87
88 /* ------------------------------------------------------------ */
89 @Override
90 public boolean hasMoreTokens()
91 {
92 // Already found a token
93 if (_hasToken)
94 return true;
95
96 _lastStart=_i;
97
98 int state=0;
99 boolean escape=false;
100 while (_i<_string.length())
101 {
102 char c=_string.charAt(_i++);
103
104 switch (state)
105 {
106 case 0: // Start
107 if(_delim.indexOf(c)>=0)
108 {
109 if (_returnDelimiters)
110 {
111 _token.append(c);
112 return _hasToken=true;
113 }
114 }
115 else if (c=='\'' && _single)
116 {
117 if (_returnQuotes)
118 _token.append(c);
119 state=2;
120 }
121 else if (c=='\"' && _double)
122 {
123 if (_returnQuotes)
124 _token.append(c);
125 state=3;
126 }
127 else
128 {
129 _token.append(c);
130 _hasToken=true;
131 state=1;
132 }
133 continue;
134
135 case 1: // Token
136 _hasToken=true;
137 if(_delim.indexOf(c)>=0)
138 {
139 if (_returnDelimiters)
140 _i--;
141 return _hasToken;
142 }
143 else if (c=='\'' && _single)
144 {
145 if (_returnQuotes)
146 _token.append(c);
147 state=2;
148 }
149 else if (c=='\"' && _double)
150 {
151 if (_returnQuotes)
152 _token.append(c);
153 state=3;
154 }
155 else
156 _token.append(c);
157 continue;
158
159
160 case 2: // Single Quote
161 _hasToken=true;
162 if (escape)
163 {
164 escape=false;
165 _token.append(c);
166 }
167 else if (c=='\'')
168 {
169 if (_returnQuotes)
170 _token.append(c);
171 state=1;
172 }
173 else if (c=='\\')
174 {
175 if (_returnQuotes)
176 _token.append(c);
177 escape=true;
178 }
179 else
180 _token.append(c);
181 continue;
182
183
184 case 3: // Double Quote
185 _hasToken=true;
186 if (escape)
187 {
188 escape=false;
189 _token.append(c);
190 }
191 else if (c=='\"')
192 {
193 if (_returnQuotes)
194 _token.append(c);
195 state=1;
196 }
197 else if (c=='\\')
198 {
199 if (_returnQuotes)
200 _token.append(c);
201 escape=true;
202 }
203 else
204 _token.append(c);
205 continue;
206 }
207 }
208
209 return _hasToken;
210 }
211
212 /* ------------------------------------------------------------ */
213 @Override
214 public String nextToken()
215 throws NoSuchElementException
216 {
217 if (!hasMoreTokens() || _token==null)
218 throw new NoSuchElementException();
219 String t=_token.toString();
220 _token.setLength(0);
221 _hasToken=false;
222 return t;
223 }
224
225 /* ------------------------------------------------------------ */
226 @Override
227 public String nextToken(String delim)
228 throws NoSuchElementException
229 {
230 _delim=delim;
231 _i=_lastStart;
232 _token.setLength(0);
233 _hasToken=false;
234 return nextToken();
235 }
236
237 /* ------------------------------------------------------------ */
238 @Override
239 public boolean hasMoreElements()
240 {
241 return hasMoreTokens();
242 }
243
244 /* ------------------------------------------------------------ */
245 @Override
246 public Object nextElement()
247 throws NoSuchElementException
248 {
249 return nextToken();
250 }
251
252 /* ------------------------------------------------------------ */
253 /** Not implemented.
254 */
255 @Override
256 public int countTokens()
257 {
258 return -1;
259 }
260
261
262 /* ------------------------------------------------------------ */
263 /** Quote a string.
264 * The string is quoted only if quoting is required due to
265 * embeded delimiters, quote characters or the
266 * empty string.
267 * @param s The string to quote.
268 * @return quoted string
269 */
270 public static String quoteIfNeeded(String s, String delim)
271 {
272 if (s==null)
273 return null;
274 if (s.length()==0)
275 return "\"\"";
276
277
278 for (int i=0;i<s.length();i++)
279 {
280 char c = s.charAt(i);
281 if (c=='\\' || c=='"' || c=='\'' || Character.isWhitespace(c) || delim.indexOf(c)>=0)
282 {
283 StringBuffer b=new StringBuffer(s.length()+8);
284 quote(b,s);
285 return b.toString();
286 }
287 }
288
289 return s;
290 }
291
292 /* ------------------------------------------------------------ */
293 /** Quote a string.
294 * The string is quoted only if quoting is required due to
295 * embeded delimiters, quote characters or the
296 * empty string.
297 * @param s The string to quote.
298 * @return quoted string
299 */
300 public static String quote(String s)
301 {
302 if (s==null)
303 return null;
304 if (s.length()==0)
305 return "\"\"";
306
307 StringBuffer b=new StringBuffer(s.length()+8);
308 quote(b,s);
309 return b.toString();
310
311 }
312
313 /* ------------------------------------------------------------ */
314 /** Quote a string into an Appendable.
315 * The characters ", \, \n, \r, \t, \f and \b are escaped
316 * @param buf The Appendable
317 * @param s The String to quote.
318 */
319 public static void quote(Appendable buf, String s)
320 {
321 try
322 {
323 buf.append('"');
324
325 for (int i=0;i<s.length();i++)
326 {
327 char c = s.charAt(i);
328 switch(c)
329 {
330 case '"':
331 buf.append("\\\"");
332 continue;
333 case '\\':
334 buf.append("\\\\");
335 continue;
336 case '\n':
337 buf.append("\\n");
338 continue;
339 case '\r':
340 buf.append("\\r");
341 continue;
342 case '\t':
343 buf.append("\\t");
344 continue;
345 case '\f':
346 buf.append("\\f");
347 continue;
348 case '\b':
349 buf.append("\\b");
350 continue;
351
352 default:
353 if (c<0x10)
354 {
355 buf.append("\\u000");
356 buf.append(Integer.toString(c,16));
357 }
358 else if (c<=0x1f)
359 {
360 buf.append("\\u00");
361 buf.append(Integer.toString(c,16));
362 }
363 else
364 buf.append(c);
365 continue;
366 }
367 }
368
369 buf.append('"');
370 }
371 catch(IOException e)
372 {
373 throw new RuntimeException(e);
374 }
375 }
376
377 /* ------------------------------------------------------------ */
378 /** Quote a string into a StringBuffer only if needed.
379 * Quotes are forced if any delim characters are present.
380 *
381 * @param buf The StringBuffer
382 * @param s The String to quote.
383 * @param delim String of characters that must be quoted.
384 * @return true if quoted;
385 */
386 public static boolean quoteIfNeeded(Appendable buf, String s,String delim)
387 {
388 for (int i=0;i<s.length();i++)
389 {
390 char c = s.charAt(i);
391 if (delim.indexOf(c)>=0)
392 {
393 quote(buf,s);
394 return true;
395 }
396 }
397
398 try
399 {
400 buf.append(s);
401 return false;
402 }
403 catch(IOException e)
404 {
405 throw new RuntimeException(e);
406 }
407 }
408
409 /* ------------------------------------------------------------ */
410 /** Unquote a string.
411 * @param s The string to unquote.
412 * @return quoted string
413 */
414 public static String unquote(String s)
415 {
416 if (s==null)
417 return null;
418 if (s.length()<2)
419 return s;
420
421 char first=s.charAt(0);
422 char last=s.charAt(s.length()-1);
423 if (first!=last || (first!='"' && first!='\''))
424 return s;
425
426 StringBuffer b=new StringBuffer(s.length()-2);
427 synchronized(b)
428 {
429 boolean escape=false;
430 for (int i=1;i<s.length()-1;i++)
431 {
432 char c = s.charAt(i);
433
434 if (escape)
435 {
436 escape=false;
437 switch (c)
438 {
439 case 'n':
440 b.append('\n');
441 break;
442 case 'r':
443 b.append('\r');
444 break;
445 case 't':
446 b.append('\t');
447 break;
448 case 'f':
449 b.append('\f');
450 break;
451 case 'b':
452 b.append('\b');
453 break;
454 case '\\':
455 b.append('\\');
456 break;
457 case '/':
458 b.append('/');
459 break;
460 case '"':
461 b.append('"');
462 break;
463 case 'u':
464 b.append((char)(
465 (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<24)+
466 (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<16)+
467 (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<8)+
468 (TypeUtil.convertHexDigit((byte)s.charAt(i++)))
469 )
470 );
471 break;
472 default:
473 b.append(c);
474 }
475 }
476 else if (c=='\\')
477 {
478 escape=true;
479 continue;
480 }
481 else
482 b.append(c);
483 }
484
485 return b.toString();
486 }
487 }
488
489 /* ------------------------------------------------------------ */
490 /**
491 * @return handle double quotes if true
492 */
493 public boolean getDouble()
494 {
495 return _double;
496 }
497
498 /* ------------------------------------------------------------ */
499 /**
500 * @param d handle double quotes if true
501 */
502 public void setDouble(boolean d)
503 {
504 _double=d;
505 }
506
507 /* ------------------------------------------------------------ */
508 /**
509 * @return handle single quotes if true
510 */
511 public boolean getSingle()
512 {
513 return _single;
514 }
515
516 /* ------------------------------------------------------------ */
517 /**
518 * @param single handle single quotes if true
519 */
520 public void setSingle(boolean single)
521 {
522 _single=single;
523 }
524 }
525
526
527
528
529
530
531
532
533
534
535
536