View Javadoc

1   package org.apache.struts.flow.json;
2   
3   import java.text.ParseException;
4   
5   /***
6    * A JSONTokener takes a source string and extracts characters and tokens from
7    * it. It is used by the JSONObject and JSONArray constructors to parse
8    * JSON source strings.
9    * <p>
10   * Public Domain 2002 JSON.org
11   * @author JSON.org
12   * @version 0.1
13   */
14  public class JSONTokener {
15  
16      /***
17       * The index of the next character.
18       */
19      private int myIndex;
20  
21  
22      /***
23       * The source string being tokenized.
24       */
25      private String mySource;
26  
27  
28      /***
29       * Construct a JSONTokener from a string.
30       *
31       * @param s     A source string.
32       */
33      public JSONTokener(String s) {
34          myIndex = 0;
35          mySource = s;
36      }
37  
38  
39      /***
40       * Back up one character. This provides a sort of lookahead capability,
41       * so that you can test for a digit or letter before attempting to parse
42       * the next number or identifier.
43       */
44      public void back() {
45          if (myIndex > 0) {
46              myIndex -= 1;
47          }
48      }
49  
50  
51  
52      /***
53       * Get the hex value of a character (base16).
54       * @param c A character between '0' and '9' or between 'A' and 'F' or
55       * between 'a' and 'f'.
56       * @return  An int between 0 and 15, or -1 if c was not a hex digit.
57       */
58      public static int dehexchar(char c) {
59          if (c >= '0' && c <= '9') {
60              return c - '0';
61          }
62          if (c >= 'A' && c <= 'F') {
63              return c + 10 - 'A';
64          }
65          if (c >= 'a' && c <= 'f') {
66              return c + 10 - 'a';
67          }
68          return -1;
69      }
70  
71  
72      /***
73       * Determine if the source string still contains characters that next()
74       * can consume.
75       * @return true if not yet at the end of the source.
76       */
77      public boolean more() {
78          return myIndex < mySource.length();
79      }
80  
81  
82      /***
83       * Get the next character in the source string.
84       *
85       * @return The next character, or 0 if past the end of the source string.
86       */
87      public char next() {
88          char c = more() ? mySource.charAt(myIndex) : 0;
89          myIndex += 1;
90          return c;
91      }
92  
93  
94      /***
95       * Consume the next character, and check that it matches a specified
96       * character.
97       * @throws ParseException if the character does not match.
98       * @param c The character to match.
99       * @return The character.
100      */
101     public char next(char c) throws ParseException {
102         char n = next();
103         if (n != c) {
104             throw syntaxError("Expected '" + c + "' and instead saw '" +
105                     n + "'.");
106         }
107         return n;
108     }
109 
110 
111     /***
112      * Get the next n characters.
113      * @exception ParseException
114      *   Substring bounds error if there are not
115      *   n characters remaining in the source string.
116      *
117      * @param n     The number of characters to take.
118      * @return      A string of n characters.
119      */
120      public String next(int n) throws ParseException {
121          int i = myIndex;
122          int j = i + n;
123          if (j >= mySource.length()) {
124             throw syntaxError("Substring bounds error");
125          }
126          myIndex += n;
127          return mySource.substring(i, j);
128      }
129 
130 
131     /***
132      * Get the next char in the string, skipping whitespace
133      * and comments (slashslash and slashstar).
134      * @throws ParseException
135      * @return  A character, or 0 if there are no more characters.
136      */
137     public char nextClean() throws java.text.ParseException {
138         while (true) {
139             char c = next();
140             if (c == '/') {
141                 switch (next()) {
142                 case '/':
143                     do {
144                         c = next();
145                     } while (c != '\n' && c != '\r' && c != 0);
146                     break;
147                 case '*':
148                     while (true) {
149                         c = next();
150                         if (c == 0) {
151                             throw syntaxError("Unclosed comment.");
152                         }
153                         if (c == '*') {
154                             if (next() == '/') {
155                                 break;
156                             }
157                             back();
158                         }
159                     }
160                     break;
161                 default:
162                     back();
163                     return '/';
164                 }
165             } else if (c == 0 || c > ' ') {
166                 return c;
167             }
168         }
169     }
170 
171 
172     /***
173      * Return the characters up to the next close quote character.
174      * Backslash processing is done. The formal JSON format does not
175      * allow strings in single quotes, but an implementation is allowed to
176      * accept them.
177      * @exception ParseException Unterminated string.
178      * @param quote The quoting character, either " or '
179      * @return      A String.
180      */
181     public String nextString(char quote) throws ParseException {
182         char c;
183         StringBuffer sb = new StringBuffer();
184         while (true) {
185             c = next();
186             switch (c) {
187             case 0:
188             case 0x0A:
189             case 0x0D:
190                 throw syntaxError("Unterminated string");
191             case '//':
192                 c = next();
193                 switch (c) {
194                 case 'b':
195                     sb.append('\b');
196                     break;
197                 case 't':
198                     sb.append('\t');
199                     break;
200                 case 'n':
201                     sb.append('\n');
202                     break;
203                 case 'f':
204                     sb.append('\f');
205                     break;
206                 case 'r':
207                     sb.append('\r');
208                     break;
209                 case 'u':
210                     sb.append((char)Integer.parseInt(next(4), 16));
211                     break;
212                 case 'x' :
213                     sb.append((char) Integer.parseInt(next(2), 16));
214                     break;
215                 default:
216                     sb.append(c);
217                 }
218                 break;
219             default:
220                 if (c == quote) {
221                     return sb.toString();
222                 }
223                 sb.append(c);
224             }
225         }
226     }
227 
228 
229     /***
230      * Get the text up but not including the specified character or the
231      * end of line, whichever comes first.
232      * @param  d A delimiter character.
233      * @return   A string.
234      */
235     public String nextTo(char d) {
236         StringBuffer sb = new StringBuffer();
237         while (true) {
238             char c = next();
239             if (c == d || c == 0 || c == '\n' || c == '\r') {
240                 if (c != 0) {
241                     back();
242                 }
243                 return sb.toString().trim();
244             }
245             sb.append(c);
246         }
247     }
248 
249 
250     /***
251      * Get the text up but not including one of the specified delimeter
252      * characters or the end of line, which ever comes first.
253      * @param delimiters A set of delimiter characters.
254      * @return A string, trimmed.
255      */
256     public String nextTo(String delimiters) {
257         char c;
258         StringBuffer sb = new StringBuffer();
259         while (true) {
260             c = next();
261             if (delimiters.indexOf(c) >= 0 || c == 0 ||
262                     c == '\n' || c == '\r') {
263                 if (c != 0) {
264                     back();
265                 }
266                 return sb.toString().trim();
267             }
268             sb.append(c);
269         }
270     }
271 
272 
273     /***
274      * Get the next value. The value can be a Boolean, Double, Integer,
275      * JSONArray, JSONObject, or String, or the JSONObject.NULL object.
276      * @exception ParseException The source conform to JSON syntax.
277      *
278      * @return An object.
279      */
280     public Object nextValue() throws ParseException {
281         char c = nextClean();
282         String s;
283 
284         if (c == '"' || c == '\'') {
285             return nextString(c);
286         }
287         if (c == '{') {
288             back();
289             return new JSONObject(this);
290         }
291         if (c == '[') {
292             back();
293             return new JSONArray(this);
294         }
295         StringBuffer sb = new StringBuffer();
296         char b = c;
297         while (c >= ' ' && c != ':' && c != ',' && c != ']' && c != '}' &&
298                 c != '/') {
299             sb.append(c);
300             c = next();
301         }
302         back();
303         s = sb.toString().trim();
304         if (s.equals("true")) {
305             return Boolean.TRUE;
306         }
307         if (s.equals("false")) {
308             return Boolean.FALSE;
309         }
310         if (s.equals("null")) {
311             return JSONObject.NULL;
312         }
313         if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+') {
314             try {
315                 return new Integer(s);
316             } catch (Exception e) {
317             }
318             try {
319                 return new Double(s);
320             } catch (Exception e) {
321             }
322         }
323         if (s.equals("")) {
324             throw syntaxError("Missing value.");
325         }
326         return s;
327     }
328 
329 
330     /***
331      * Skip characters until the next character is the requested character.
332      * If the requested character is not found, no characters are skipped.
333      * @param to A character to skip to.
334      * @return The requested character, or zero if the requested character
335      * is not found.
336      */
337     public char skipTo(char to) {
338         char c;
339         int index = myIndex;
340         do {
341             c = next();
342             if (c == 0) {
343                 myIndex = index;
344                 return c;
345             }
346         } while (c != to);
347         back();
348         return c;
349     }
350 
351 
352     /***
353      * Skip characters until past the requested string.
354      * If it is not found, we are left at the end of the source.
355      * @param to A string to skip past.
356      */
357     public void skipPast(String to) {
358         myIndex = mySource.indexOf(to, myIndex);
359         if (myIndex < 0) {
360             myIndex = mySource.length();
361         } else {
362             myIndex += to.length();
363         }
364     }
365 
366 
367     /***
368      * Make a ParseException to signal a syntax error.
369      *
370      * @param message The error message.
371      * @return  A ParseException object, suitable for throwing
372      */
373     public ParseException syntaxError(String message) {
374         return new ParseException(message + toString(), myIndex);
375     }
376 
377 
378     /***
379      * Make a printable string of this JSONTokener.
380      *
381      * @return " at character [myIndex] of [mySource]"
382      */
383     public String toString() {
384         return " at character " + myIndex + " of " + mySource;
385     }
386 
387 
388     /***
389      * Unescape the source text. Convert %hh sequences to single characters,
390      * and convert plus to space. There are Web transport systems that insist on
391      * doing unnecessary URL encoding. This provides a way to undo it.
392      */
393     public void unescape() {
394         mySource = unescape(mySource);
395     }
396 
397     /***
398      * Convert %hh sequences to single characters, and convert plus to space.
399      * @param s A string that may contain plus and %hh sequences.
400      * @return The unescaped string.
401      */
402     public static String unescape(String s) {
403         int len = s.length();
404         StringBuffer b = new StringBuffer();
405         for (int i = 0; i < len; ++i) {
406             char c = s.charAt(i);
407             if (c == '+') {
408                 c = ' ';
409             } else if (c == '%' && i + 2 < len) {
410                 int d = dehexchar(s.charAt(i + 1));
411                 int e = dehexchar(s.charAt(i + 2));
412                 if (d >= 0 && e >= 0) {
413                     c = (char)(d * 16 + e);
414                     i += 2;
415                 }
416             }
417             b.append(c);
418         }
419         return b.toString();
420     }
421 }