1 // ========================================================================
2 // Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
3 // ------------------------------------------------------------------------
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 // ========================================================================
14
15 package org.mortbay.util;
16
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 * @author Greg Wilkins (gregw)
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 public boolean hasMoreTokens()
90 {
91 // Already found a token
92 if (_hasToken)
93 return true;
94
95 _lastStart=_i;
96
97 int state=0;
98 boolean escape=false;
99 while (_i<_string.length())
100 {
101 char c=_string.charAt(_i++);
102
103 switch (state)
104 {
105 case 0: // Start
106 if(_delim.indexOf(c)>=0)
107 {
108 if (_returnDelimiters)
109 {
110 _token.append(c);
111 return _hasToken=true;
112 }
113 }
114 else if (c=='\'' && _single)
115 {
116 if (_returnQuotes)
117 _token.append(c);
118 state=2;
119 }
120 else if (c=='\"' && _double)
121 {
122 if (_returnQuotes)
123 _token.append(c);
124 state=3;
125 }
126 else
127 {
128 _token.append(c);
129 _hasToken=true;
130 state=1;
131 }
132 continue;
133
134 case 1: // Token
135 _hasToken=true;
136 if(_delim.indexOf(c)>=0)
137 {
138 if (_returnDelimiters)
139 _i--;
140 return _hasToken;
141 }
142 else if (c=='\'' && _single)
143 {
144 if (_returnQuotes)
145 _token.append(c);
146 state=2;
147 }
148 else if (c=='\"' && _double)
149 {
150 if (_returnQuotes)
151 _token.append(c);
152 state=3;
153 }
154 else
155 _token.append(c);
156 continue;
157
158
159 case 2: // Single Quote
160 _hasToken=true;
161 if (escape)
162 {
163 escape=false;
164 _token.append(c);
165 }
166 else if (c=='\'')
167 {
168 if (_returnQuotes)
169 _token.append(c);
170 state=1;
171 }
172 else if (c=='\\')
173 {
174 if (_returnQuotes)
175 _token.append(c);
176 escape=true;
177 }
178 else
179 _token.append(c);
180 continue;
181
182
183 case 3: // Double Quote
184 _hasToken=true;
185 if (escape)
186 {
187 escape=false;
188 _token.append(c);
189 }
190 else if (c=='\"')
191 {
192 if (_returnQuotes)
193 _token.append(c);
194 state=1;
195 }
196 else if (c=='\\')
197 {
198 if (_returnQuotes)
199 _token.append(c);
200 escape=true;
201 }
202 else
203 _token.append(c);
204 continue;
205 }
206 }
207
208 return _hasToken;
209 }
210
211 /* ------------------------------------------------------------ */
212 public String nextToken()
213 throws NoSuchElementException
214 {
215 if (!hasMoreTokens() || _token==null)
216 throw new NoSuchElementException();
217 String t=_token.toString();
218 _token.setLength(0);
219 _hasToken=false;
220 return t;
221 }
222
223 /* ------------------------------------------------------------ */
224 public String nextToken(String delim)
225 throws NoSuchElementException
226 {
227 _delim=delim;
228 _i=_lastStart;
229 _token.setLength(0);
230 _hasToken=false;
231 return nextToken();
232 }
233
234 /* ------------------------------------------------------------ */
235 public boolean hasMoreElements()
236 {
237 return hasMoreTokens();
238 }
239
240 /* ------------------------------------------------------------ */
241 public Object nextElement()
242 throws NoSuchElementException
243 {
244 return nextToken();
245 }
246
247 /* ------------------------------------------------------------ */
248 /** Not implemented.
249 */
250 public int countTokens()
251 {
252 return -1;
253 }
254
255
256 /* ------------------------------------------------------------ */
257 /** Quote a string.
258 * The string is quoted only if quoting is required due to
259 * embeded delimiters, quote characters or the
260 * empty string.
261 * @param s The string to quote.
262 * @return quoted string
263 */
264 public static String quote(String s, String delim)
265 {
266 if (s==null)
267 return null;
268 if (s.length()==0)
269 return "\"\"";
270
271
272 for (int i=0;i<s.length();i++)
273 {
274 char c = s.charAt(i);
275 if (c=='\\' || c=='"' || c=='\'' || Character.isWhitespace(c) || delim.indexOf(c)>=0)
276 {
277 StringBuffer b=new StringBuffer(s.length()+8);
278 quote(b,s);
279 return b.toString();
280 }
281 }
282
283 return s;
284 }
285
286 /* ------------------------------------------------------------ */
287 /** Quote a string.
288 * The string is quoted only if quoting is required due to
289 * embeded delimiters, quote characters or the
290 * empty string.
291 * @param s The string to quote.
292 * @return quoted string
293 */
294 public static String quote(String s)
295 {
296 if (s==null)
297 return null;
298 if (s.length()==0)
299 return "\"\"";
300
301 StringBuffer b=new StringBuffer(s.length()+8);
302 quote(b,s);
303 return b.toString();
304
305 }
306
307
308 /* ------------------------------------------------------------ */
309 /** Quote a string into a StringBuffer.
310 * The characters ", \, \n, \r, \t, \f and \b are escaped
311 * @param buf The StringBuffer
312 * @param s The String to quote.
313 */
314 public static void quote(StringBuffer buf, String s)
315 {
316 synchronized(buf)
317 {
318 buf.append('"');
319
320 int i=0;
321 loop:
322 for (;i<s.length();i++)
323 {
324 char c = s.charAt(i);
325 switch(c)
326 {
327 case '"':
328 buf.append(s,0,i);
329 buf.append("\\\"");
330 break loop;
331 case '\\':
332 buf.append(s,0,i);
333 buf.append("\\\\");
334 break loop;
335 case '\n':
336 buf.append(s,0,i);
337 buf.append("\\n");
338 break loop;
339 case '\r':
340 buf.append(s,0,i);
341 buf.append("\\r");
342 break loop;
343 case '\t':
344 buf.append(s,0,i);
345 buf.append("\\t");
346 break loop;
347 case '\f':
348 buf.append(s,0,i);
349 buf.append("\\f");
350 break loop;
351 case '\b':
352 buf.append(s,0,i);
353 buf.append("\\b");
354 break loop;
355
356 default:
357 continue;
358 }
359 }
360 if (i==s.length())
361 buf.append(s);
362 else
363 {
364 i++;
365 for (;i<s.length();i++)
366 {
367 char c = s.charAt(i);
368 switch(c)
369 {
370 case '"':
371 buf.append("\\\"");
372 continue;
373 case '\\':
374 buf.append("\\\\");
375 continue;
376 case '\n':
377 buf.append("\\n");
378 continue;
379 case '\r':
380 buf.append("\\r");
381 continue;
382 case '\t':
383 buf.append("\\t");
384 continue;
385 case '\f':
386 buf.append("\\f");
387 continue;
388 case '\b':
389 buf.append("\\b");
390 continue;
391
392 default:
393 buf.append(c);
394 continue;
395 }
396 }
397 }
398
399 buf.append('"');
400 }
401
402
403
404 }
405
406
407 /* ------------------------------------------------------------ */
408 /** Quote a string into a StringBuffer.
409 * The characters ", \, \n, \r, \t, \f, \b are escaped.
410 * Quotes are forced if any escaped characters are present or there
411 * is a ", ', space, + or % character.
412 *
413 * @param buf The StringBuffer
414 * @param s The String to quote.
415 */
416 public static void quoteIfNeeded(StringBuffer buf, String s)
417 {
418 synchronized(buf)
419 {
420 int e=-1;
421
422 search: for (int i=0;i<s.length();i++)
423 {
424 char c = s.charAt(i);
425 switch(c)
426 {
427 case '"':
428 case '\\':
429 case '\n':
430 case '\r':
431 case '\t':
432 case '\f':
433 case '\b':
434 case '%':
435 case '+':
436 case ' ':
437 e=i;
438 buf.append('"');
439 // TODO when 1.4 support is dropped: buf.append(s,0,e);
440 for (int j=0;j<e;j++)
441 buf.append(s.charAt(j));
442 break search;
443
444 default:
445 continue;
446 }
447 }
448
449 if (e<0)
450 {
451 buf.append(s);
452 return;
453 }
454
455 for (int i=e;i<s.length();i++)
456 {
457 char c = s.charAt(i);
458 switch(c)
459 {
460 case '"':
461 buf.append("\\\"");
462 continue;
463 case '\\':
464 buf.append("\\\\");
465 continue;
466 case '\n':
467 buf.append("\\n");
468 continue;
469 case '\r':
470 buf.append("\\r");
471 continue;
472 case '\t':
473 buf.append("\\t");
474 continue;
475 case '\f':
476 buf.append("\\f");
477 continue;
478 case '\b':
479 buf.append("\\b");
480 continue;
481
482 default:
483 buf.append(c);
484 continue;
485 }
486 }
487 buf.append('"');
488 }
489 }
490
491 /* ------------------------------------------------------------ */
492 /** Unquote a string.
493 * @param s The string to unquote.
494 * @return quoted string
495 */
496 public static String unquote(String s)
497 {
498 if (s==null)
499 return null;
500 if (s.length()<2)
501 return s;
502
503 char first=s.charAt(0);
504 char last=s.charAt(s.length()-1);
505 if (first!=last || (first!='"' && first!='\''))
506 return s;
507
508 StringBuffer b=new StringBuffer(s.length()-2);
509 synchronized(b)
510 {
511 boolean escape=false;
512 for (int i=1;i<s.length()-1;i++)
513 {
514 char c = s.charAt(i);
515
516 if (escape)
517 {
518 escape=false;
519 switch (c)
520 {
521 case 'n':
522 b.append('\n');
523 break;
524 case 'r':
525 b.append('\r');
526 break;
527 case 't':
528 b.append('\t');
529 break;
530 case 'f':
531 b.append('\f');
532 break;
533 case 'b':
534 b.append('\b');
535 break;
536 case 'u':
537 b.append((char)(
538 (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<24)+
539 (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<16)+
540 (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<8)+
541 (TypeUtil.convertHexDigit((byte)s.charAt(i++)))
542 )
543 );
544 break;
545 default:
546 b.append(c);
547 }
548 }
549 else if (c=='\\')
550 {
551 escape=true;
552 continue;
553 }
554 else
555 b.append(c);
556 }
557
558 return b.toString();
559 }
560 }
561
562 /* ------------------------------------------------------------ */
563 /**
564 * @return handle double quotes if true
565 */
566 public boolean getDouble()
567 {
568 return _double;
569 }
570
571 /* ------------------------------------------------------------ */
572 /**
573 * @param d handle double quotes if true
574 */
575 public void setDouble(boolean d)
576 {
577 _double=d;
578 }
579
580 /* ------------------------------------------------------------ */
581 /**
582 * @return handle single quotes if true
583 */
584 public boolean getSingle()
585 {
586 return _single;
587 }
588
589 /* ------------------------------------------------------------ */
590 /**
591 * @param single handle single quotes if true
592 */
593 public void setSingle(boolean single)
594 {
595 _single=single;
596 }
597 }
598
599
600
601
602
603
604
605
606
607
608
609