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