001 package org.apache.tapestry.json;
002
003 /*
004 Copyright (c) 2002 JSON.org
005
006 Permission is hereby granted, free of charge, to any person obtaining a copy
007 of this software and associated documentation files (the "Software"), to deal
008 in the Software without restriction, including without limitation the rights
009 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010 copies of the Software, and to permit persons to whom the Software is
011 furnished to do so, subject to the following conditions:
012
013 The above copyright notice and this permission notice shall be included in all
014 copies or substantial portions of the Software.
015
016 The Software shall be used for Good, not Evil.
017
018 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
019 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
020 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
021 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
022 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
023 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
024 SOFTWARE.
025 */
026
027 import java.text.ParseException;
028
029 /**
030 * A JSONTokener takes a source string and extracts characters and tokens from
031 * it. It is used by the JSONObject and JSONArray constructors to parse JSON
032 * source strings.
033 *
034 * <p/>The toString() method has been modified from its original form to provide
035 * easier to understand exception reporting.
036 *
037 * @author JSON.org, jkuhnert
038 * @version 1
039 */
040 public class JSONTokener
041 {
042
043 /**
044 * The index of the next character.
045 */
046 private int myIndex;
047
048 /**
049 * The source string being tokenized.
050 */
051 private String mySource;
052
053 /**
054 * Construct a JSONTokener from a string.
055 *
056 * @param s
057 * A source string.
058 */
059 public JSONTokener(String s)
060 {
061 this.myIndex = 0;
062 this.mySource = s;
063 }
064
065 /**
066 * Back up one character. This provides a sort of lookahead capability, so
067 * that you can test for a digit or letter before attempting to parse the
068 * next number or identifier.
069 */
070 public void back()
071 {
072 if (this.myIndex > 0)
073 {
074 this.myIndex -= 1;
075 }
076 }
077
078 /**
079 * Get the hex value of a character (base16).
080 *
081 * @param c
082 * A character between '0' and '9' or between 'A' and 'F' or
083 * between 'a' and 'f'.
084 * @return An int between 0 and 15, or -1 if c was not a hex digit.
085 */
086 public static int dehexchar(char c)
087 {
088 if (c >= '0' && c <= '9') { return c - '0'; }
089 if (c >= 'A' && c <= 'F') { return c + 10 - 'A'; }
090 if (c >= 'a' && c <= 'f') { return c + 10 - 'a'; }
091 return -1;
092 }
093
094 /**
095 * Determine if the source string still contains characters that next() can
096 * consume.
097 *
098 * @return true if not yet at the end of the source.
099 */
100 public boolean more()
101 {
102 return this.myIndex < this.mySource.length();
103 }
104
105 /**
106 * Get the next character in the source string.
107 *
108 * @return The next character, or 0 if past the end of the source string.
109 */
110 public char next()
111 {
112 if (more())
113 {
114 char c = this.mySource.charAt(this.myIndex);
115 this.myIndex += 1;
116 return c;
117 }
118 return 0;
119 }
120
121 /**
122 * Consume the next character, and check that it matches a specified
123 * character.
124 *
125 * @param c
126 * The character to match.
127 * @return The character.
128 * @throws ParseException
129 * if the character does not match.
130 */
131 public char next(char c)
132 throws ParseException
133 {
134 char n = next();
135 if (n != c) { throw syntaxError("Expected '" + c + "' and instead saw '" + n + "'."); }
136 return n;
137 }
138
139 /**
140 * Get the next n characters.
141 *
142 * @param n
143 * The number of characters to take.
144 * @return A string of n characters.
145 * @exception ParseException
146 * Substring bounds error if there are not n characters
147 * remaining in the source string.
148 */
149 public String next(int n)
150 throws ParseException
151 {
152 int i = this.myIndex;
153 int j = i + n;
154 if (j >= this.mySource.length()) { throw syntaxError("Substring bounds error"); }
155 this.myIndex += n;
156 return this.mySource.substring(i, j);
157 }
158
159 /**
160 * Get the next char in the string, skipping whitespace and comments
161 * (slashslash, slashstar, and hash).
162 *
163 * @throws ParseException
164 * @return A character, or 0 if there are no more characters.
165 */
166 public char nextClean()
167 throws java.text.ParseException
168 {
169 while(true)
170 {
171 char c = next();
172 if (c == '/')
173 {
174 switch(next())
175 {
176 case '/':
177 do
178 {
179 c = next();
180 } while(c != '\n' && c != '\r' && c != 0);
181 break;
182 case '*':
183 while(true)
184 {
185 c = next();
186 if (c == 0) { throw syntaxError("Unclosed comment."); }
187 if (c == '*')
188 {
189 if (next() == '/')
190 {
191 break;
192 }
193 back();
194 }
195 }
196 break;
197 default:
198 back();
199 return '/';
200 }
201 }
202 else if (c == '#')
203 {
204 do
205 {
206 c = next();
207 } while(c != '\n' && c != '\r' && c != 0);
208 }
209 else if (c == 0 || c > ' ') { return c; }
210 }
211 }
212
213 /**
214 * Return the characters up to the next close quote character. Backslash
215 * processing is done. The formal JSON format does not allow strings in
216 * single quotes, but an implementation is allowed to accept them.
217 *
218 * @param quote
219 * The quoting character, either <code>"</code> <small>(double
220 * quote)</small> or <code>'</code> <small>(single
221 * quote)</small>.
222 * @return A String.
223 * @exception ParseException
224 * Unterminated string.
225 */
226 public String nextString(char quote)
227 throws ParseException
228 {
229 char c;
230 StringBuffer sb = new StringBuffer();
231 while(true)
232 {
233 c = next();
234 switch(c)
235 {
236 case 0:
237 case '\n':
238 case '\r':
239 throw syntaxError("Unterminated string");
240 case '\\':
241 c = next();
242 switch(c)
243 {
244 case 'b':
245 sb.append('\b');
246 break;
247 case 't':
248 sb.append('\t');
249 break;
250 case 'n':
251 sb.append('\n');
252 break;
253 case 'f':
254 sb.append('\f');
255 break;
256 case 'r':
257 sb.append('\r');
258 break;
259 case 'u':
260 sb.append((char) Integer.parseInt(next(4), 16));
261 break;
262 case 'x':
263 sb.append((char) Integer.parseInt(next(2), 16));
264 break;
265 default:
266 sb.append(c);
267 }
268 break;
269 default:
270 if (c == quote) { return sb.toString(); }
271 sb.append(c);
272 }
273 }
274 }
275
276 /**
277 * Get the text up but not including the specified character or the end of
278 * line, whichever comes first.
279 *
280 * @param d
281 * A delimiter character.
282 * @return A string.
283 */
284 public String nextTo(char d)
285 {
286 StringBuffer sb = new StringBuffer();
287 while(true)
288 {
289 char c = next();
290 if (c == d || c == 0 || c == '\n' || c == '\r')
291 {
292 if (c != 0)
293 {
294 back();
295 }
296 return sb.toString().trim();
297 }
298 sb.append(c);
299 }
300 }
301
302 /**
303 * Get the text up but not including one of the specified delimeter
304 * characters or the end of line, whichever comes first.
305 *
306 * @param delimiters
307 * A set of delimiter characters.
308 * @return A string, trimmed.
309 */
310 public String nextTo(String delimiters)
311 {
312 char c;
313 StringBuffer sb = new StringBuffer();
314 while(true)
315 {
316 c = next();
317 if (delimiters.indexOf(c) >= 0 || c == 0 || c == '\n' || c == '\r')
318 {
319 if (c != 0)
320 {
321 back();
322 }
323 return sb.toString().trim();
324 }
325 sb.append(c);
326 }
327 }
328
329 /**
330 * Get the next value. The value can be a Boolean, Double, Integer,
331 * JSONArray, JSONObject, or String, or the JSONObject.NULL object.
332 *
333 * @exception ParseException
334 * The source does not conform to JSON syntax.
335 * @return An object.
336 */
337 public Object nextValue()
338 throws ParseException
339 {
340 char c = nextClean();
341 String s;
342
343 switch(c)
344 {
345 case '"':
346 case '\'':
347 return nextString(c);
348 case '{':
349 back();
350 return new JSONObject(this);
351 case '[':
352 back();
353 return new JSONArray(this);
354 }
355
356 /*
357 * Handle unquoted text. This could be the values true, false, or null,
358 * or it can be a number. An implementation (such as this one) is
359 * allowed to also accept non-standard forms. Accumulate characters
360 * until we reach the end of the text or a formatting character.
361 */
362
363 StringBuffer sb = new StringBuffer();
364 char b = c;
365 while(c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0)
366 {
367 sb.append(c);
368 c = next();
369 }
370 back();
371
372 /*
373 * If it is true, false, or null, return the proper value.
374 */
375
376 s = sb.toString().trim();
377 if (s.equals("")) { throw syntaxError("Missing value."); }
378 if (s.equalsIgnoreCase("true")) { return Boolean.TRUE; }
379 if (s.equalsIgnoreCase("false")) { return Boolean.FALSE; }
380 if (s.equalsIgnoreCase("null")) { return JSONObject.NULL; }
381
382 /*
383 * If it might be a number, try converting it. We support the 0- and 0x-
384 * conventions. If a number cannot be produced, then the value will just
385 * be a string. Note that the 0-, 0x-, plus, and implied string
386 * conventions are non-standard. A JSON parser is free to accept
387 * non-JSON forms as long as it accepts all correct JSON forms.
388 */
389
390 if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+')
391 {
392 if (b == '0')
393 {
394 if (s.length() > 2 && (s.charAt(1) == 'x' || s.charAt(1) == 'X'))
395 {
396 try
397 {
398 return new Integer(Integer.parseInt(s.substring(2), 16));
399 }
400 catch (Exception e)
401 {
402 /* Ignore the error */
403 }
404 }
405 else
406 {
407 try
408 {
409 return new Integer(Integer.parseInt(s, 8));
410 }
411 catch (Exception e)
412 {
413 /* Ignore the error */
414 }
415 }
416 }
417 try
418 {
419 return new Integer(s);
420 }
421 catch (Exception e)
422 {
423 /* Ignore the error */
424 }
425 try
426 {
427 return new Double(s);
428 }
429 catch (Exception e)
430 {
431 /* Ignore the error */
432 }
433 }
434 return s;
435 }
436
437 /**
438 * Skip characters until the next character is the requested character. If
439 * the requested character is not found, no characters are skipped.
440 *
441 * @param to
442 * A character to skip to.
443 * @return The requested character, or zero if the requested character is
444 * not found.
445 */
446 public char skipTo(char to)
447 {
448 char c;
449 int index = this.myIndex;
450 do
451 {
452 c = next();
453 if (c == 0)
454 {
455 this.myIndex = index;
456 return c;
457 }
458 } while(c != to);
459 back();
460 return c;
461 }
462
463 /**
464 * Skip characters until past the requested string. If it is not found, we
465 * are left at the end of the source.
466 *
467 * @param to
468 * A string to skip past.
469 */
470 public void skipPast(String to)
471 {
472 this.myIndex = this.mySource.indexOf(to, this.myIndex);
473 if (this.myIndex < 0)
474 {
475 this.myIndex = this.mySource.length();
476 }
477 else
478 {
479 this.myIndex += to.length();
480 }
481 }
482
483 /**
484 * Make a ParseException to signal a syntax error.
485 *
486 * @param message
487 * The error message.
488 * @return A ParseException object, suitable for throwing
489 */
490 public ParseException syntaxError(String message)
491 {
492 return new ParseException(message + toString(), this.myIndex);
493 }
494
495 /**
496 * Make a printable string of this JSONTokener.
497 *
498 * @return " at character [this.myIndex] of [this.mySource]"
499 */
500 public String toString()
501 {
502 String before = this.mySource.substring(0, this.myIndex);
503 String after = this.mySource.substring(this.myIndex);
504
505 return " at character " + this.myIndex + " of " + before + ">>missing value<<" + after;
506 }
507
508 /**
509 * Convert <code>%</code><i>hh</i> sequences to single characters, and
510 * convert plus to space.
511 *
512 * @param s
513 * A string that may contain <code>+</code> <small>(plus)</small>
514 * and <code>%</code><i>hh</i> sequences.
515 * @return The unescaped string.
516 */
517 public static String unescape(String s)
518 {
519 int len = s.length();
520 StringBuffer b = new StringBuffer();
521 for(int i = 0; i < len; ++i)
522 {
523 char c = s.charAt(i);
524 if (c == '+')
525 {
526 c = ' ';
527 }
528 else if (c == '%' && i + 2 < len)
529 {
530 int d = dehexchar(s.charAt(i + 1));
531 int e = dehexchar(s.charAt(i + 2));
532 if (d >= 0 && e >= 0)
533 {
534 c = (char) (d * 16 + e);
535 i += 2;
536 }
537 }
538 b.append(c);
539 }
540 return b.toString();
541 }
542 }