View Javadoc

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 }