001 /*
002 * Apache License
003 * Version 2.0, January 2004
004 * http://www.apache.org/licenses/
005 *
006 * Copyright 2008 by chenillekit.org
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 */
015
016 package org.chenillekit.google.utils;
017
018 import java.io.BufferedReader;
019 import java.io.IOException;
020 import java.io.Reader;
021 import java.io.StringReader;
022
023 /**
024 * A JSONTokener takes a source string and extracts characters and tokens from
025 * it. It is used by the JSONObject and JSONArray constructors to parse
026 * JSON source strings.
027 *
028 * @author JSON.org
029 * @version 3
030 */
031 public class JSONTokener
032 {
033
034 private int index;
035 private Reader reader;
036 private char lastChar;
037 private boolean useLastChar;
038
039
040 /**
041 * Construct a JSONTokener from a string.
042 *
043 * @param reader A reader.
044 */
045 public JSONTokener(Reader reader)
046 {
047 this.reader = reader.markSupported() ?
048 reader : new BufferedReader(reader);
049 this.useLastChar = false;
050 this.index = 0;
051 }
052
053
054 /**
055 * Construct a JSONTokener from a string.
056 *
057 * @param s A source string.
058 */
059 public JSONTokener(String s)
060 {
061 this(new StringReader(s));
062 }
063
064
065 /**
066 * Back up one character. This provides a sort of lookahead capability,
067 * so that you can test for a digit or letter before attempting to parse
068 * the next number or identifier.
069 */
070 public void back() throws JSONException
071 {
072 if (useLastChar || index <= 0)
073 {
074 throw new JSONException("Stepping back two steps is not supported");
075 }
076 index -= 1;
077 useLastChar = true;
078 }
079
080
081 /**
082 * Get the hex value of a character (base16).
083 *
084 * @param c A character between '0' and '9' or between 'A' and 'F' or
085 * between 'a' and 'f'.
086 *
087 * @return An int between 0 and 15, or -1 if c was not a hex digit.
088 */
089 public static int dehexchar(char c)
090 {
091 if (c >= '0' && c <= '9')
092 {
093 return c - '0';
094 }
095 if (c >= 'A' && c <= 'F')
096 {
097 return c - ('A' - 10);
098 }
099 if (c >= 'a' && c <= 'f')
100 {
101 return c - ('a' - 10);
102 }
103 return -1;
104 }
105
106
107 /**
108 * Determine if the source string still contains characters that next()
109 * can consume.
110 *
111 * @return true if not yet at the end of the source.
112 */
113 public boolean more() throws JSONException
114 {
115 char nextChar = next();
116 if (nextChar == 0)
117 {
118 return false;
119 }
120 back();
121 return true;
122 }
123
124
125 /**
126 * Get the next character in the source string.
127 *
128 * @return The next character, or 0 if past the end of the source string.
129 */
130 public char next() throws JSONException
131 {
132 if (this.useLastChar)
133 {
134 this.useLastChar = false;
135 if (this.lastChar != 0)
136 {
137 this.index += 1;
138 }
139 return this.lastChar;
140 }
141 int c;
142 try
143 {
144 c = this.reader.read();
145 }
146 catch (IOException exc)
147 {
148 throw new JSONException(exc);
149 }
150
151 if (c <= 0)
152 { // End of stream
153 this.lastChar = 0;
154 return 0;
155 }
156 this.index += 1;
157 this.lastChar = (char) c;
158 return this.lastChar;
159 }
160
161
162 /**
163 * Consume the next character, and check that it matches a specified
164 * character.
165 *
166 * @param c The character to match.
167 *
168 * @return The character.
169 *
170 * @throws JSONException if the character does not match.
171 */
172 public char next(char c) throws JSONException
173 {
174 char n = next();
175 if (n != c)
176 {
177 throw syntaxError("Expected '" + c + "' and instead saw '" +
178 n + "'");
179 }
180 return n;
181 }
182
183
184 /**
185 * Get the next n characters.
186 *
187 * @param n The number of characters to take.
188 *
189 * @return A string of n characters.
190 *
191 * @throws JSONException Substring bounds error if there are not
192 * n characters remaining in the source string.
193 */
194 public String next(int n) throws JSONException
195 {
196 if (n == 0)
197 {
198 return "";
199 }
200
201 char[] buffer = new char[n];
202 int pos = 0;
203
204 if (this.useLastChar)
205 {
206 this.useLastChar = false;
207 buffer[0] = this.lastChar;
208 pos = 1;
209 }
210
211 try
212 {
213 int len;
214 while ((pos < n) && ((len = reader.read(buffer, pos, n - pos)) != -1))
215 {
216 pos += len;
217 }
218 }
219 catch (IOException exc)
220 {
221 throw new JSONException(exc);
222 }
223 this.index += pos;
224
225 if (pos < n)
226 {
227 throw syntaxError("Substring bounds error");
228 }
229
230 this.lastChar = buffer[n - 1];
231 return new String(buffer);
232 }
233
234
235 /**
236 * Get the next char in the string, skipping whitespace
237 * and comments (slashslash, slashstar, and hash).
238 *
239 * @return A character, or 0 if there are no more characters.
240 *
241 * @throws JSONException
242 */
243 public char nextClean() throws JSONException
244 {
245 for (; ;)
246 {
247 char c = next();
248 if (c == '/')
249 {
250 switch (next())
251 {
252 case '/':
253 do
254 {
255 c = next();
256 }
257 while (c != '\n' && c != '\r' && c != 0);
258 break;
259 case '*':
260 for (; ;)
261 {
262 c = next();
263 if (c == 0)
264 {
265 throw syntaxError("Unclosed comment");
266 }
267 if (c == '*')
268 {
269 if (next() == '/')
270 {
271 break;
272 }
273 back();
274 }
275 }
276 break;
277 default:
278 back();
279 return '/';
280 }
281 }
282 else if (c == '#')
283 {
284 do
285 {
286 c = next();
287 }
288 while (c != '\n' && c != '\r' && c != 0);
289 }
290 else if (c == 0 || c > ' ')
291 {
292 return c;
293 }
294 }
295 }
296
297
298 /**
299 * Return the characters up to the next close quote character.
300 * Backslash processing is done. The formal JSON format does not
301 * allow strings in single quotes, but an implementation is allowed to
302 * accept them.
303 *
304 * @param quote The quoting character, either
305 * <code>"</code> <small>(double quote)</small> or
306 * <code>'</code> <small>(single quote)</small>.
307 *
308 * @return A String.
309 *
310 * @throws JSONException Unterminated string.
311 */
312 public String nextString(char quote) throws JSONException
313 {
314 char c;
315 StringBuffer sb = new StringBuffer();
316 for (; ;)
317 {
318 c = next();
319 switch (c)
320 {
321 case 0:
322 case '\n':
323 case '\r':
324 throw syntaxError("Unterminated string");
325 case '\\':
326 c = next();
327 switch (c)
328 {
329 case 'b':
330 sb.append('\b');
331 break;
332 case 't':
333 sb.append('\t');
334 break;
335 case 'n':
336 sb.append('\n');
337 break;
338 case 'f':
339 sb.append('\f');
340 break;
341 case 'r':
342 sb.append('\r');
343 break;
344 case 'u':
345 sb.append((char) Integer.parseInt(next(4), 16));
346 break;
347 case 'x':
348 sb.append((char) Integer.parseInt(next(2), 16));
349 break;
350 default:
351 sb.append(c);
352 }
353 break;
354 default:
355 if (c == quote)
356 {
357 return sb.toString();
358 }
359 sb.append(c);
360 }
361 }
362 }
363
364
365 /**
366 * Get the text up but not including the specified character or the
367 * end of line, whichever comes first.
368 *
369 * @param d A delimiter character.
370 *
371 * @return A string.
372 */
373 public String nextTo(char d) throws JSONException
374 {
375 StringBuffer sb = new StringBuffer();
376 for (; ;)
377 {
378 char c = next();
379 if (c == d || c == 0 || c == '\n' || c == '\r')
380 {
381 if (c != 0)
382 {
383 back();
384 }
385 return sb.toString().trim();
386 }
387 sb.append(c);
388 }
389 }
390
391
392 /**
393 * Get the text up but not including one of the specified delimiter
394 * characters or the end of line, whichever comes first.
395 *
396 * @param delimiters A set of delimiter characters.
397 *
398 * @return A string, trimmed.
399 */
400 public String nextTo(String delimiters) throws JSONException
401 {
402 char c;
403 StringBuffer sb = new StringBuffer();
404 for (; ;)
405 {
406 c = next();
407 if (delimiters.indexOf(c) >= 0 || c == 0 ||
408 c == '\n' || c == '\r')
409 {
410 if (c != 0)
411 {
412 back();
413 }
414 return sb.toString().trim();
415 }
416 sb.append(c);
417 }
418 }
419
420
421 /**
422 * Get the next value. The value can be a Boolean, Double, Integer,
423 * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
424 *
425 * @return An object.
426 *
427 * @throws JSONException If syntax error.
428 */
429 public Object nextValue() throws JSONException
430 {
431 char c = nextClean();
432 String s;
433
434 switch (c)
435 {
436 case '"':
437 case '\'':
438 return nextString(c);
439 case '{':
440 back();
441 return new JSONObject(this);
442 case '[':
443 case '(':
444 back();
445 return new JSONArray(this);
446 }
447
448 /*
449 * Handle unquoted text. This could be the values true, false, or
450 * null, or it can be a number. An implementation (such as this one)
451 * is allowed to also accept non-standard forms.
452 *
453 * Accumulate characters until we reach the end of the text or a
454 * formatting character.
455 */
456
457 StringBuffer sb = new StringBuffer();
458 char b = c;
459 while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0)
460 {
461 sb.append(c);
462 c = next();
463 }
464 back();
465
466 /*
467 * If it is true, false, or null, return the proper value.
468 */
469
470 s = sb.toString().trim();
471 if (s.equals(""))
472 {
473 throw syntaxError("Missing value");
474 }
475 if (s.equalsIgnoreCase("true"))
476 {
477 return Boolean.TRUE;
478 }
479 if (s.equalsIgnoreCase("false"))
480 {
481 return Boolean.FALSE;
482 }
483 if (s.equalsIgnoreCase("null"))
484 {
485 return JSONObject.NULL;
486 }
487
488 /*
489 * If it might be a number, try converting it. We support the 0- and 0x-
490 * conventions. If a number cannot be produced, then the value will just
491 * be a string. Note that the 0-, 0x-, plus, and implied string
492 * conventions are non-standard. A JSON parser is free to accept
493 * non-JSON forms as long as it accepts all correct JSON forms.
494 */
495
496 if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+')
497 {
498 if (b == '0')
499 {
500 if (s.length() > 2 &&
501 (s.charAt(1) == 'x' || s.charAt(1) == 'X'))
502 {
503 try
504 {
505 return Integer.parseInt(s.substring(2), 16);
506 }
507 catch (Exception e)
508 {
509 /* Ignore the error */
510 }
511 }
512 else
513 {
514 try
515 {
516 return Integer.parseInt(s, 8);
517 }
518 catch (Exception e)
519 {
520 /* Ignore the error */
521 }
522 }
523 }
524 try
525 {
526 return new Integer(s);
527 }
528 catch (Exception e)
529 {
530 try
531 {
532 return new Long(s);
533 }
534 catch (Exception f)
535 {
536 try
537 {
538 return new Double(s);
539 }
540 catch (Exception g)
541 {
542 return s;
543 }
544 }
545 }
546 }
547 return s;
548 }
549
550
551 /**
552 * Skip characters until the next character is the requested character.
553 * If the requested character is not found, no characters are skipped.
554 *
555 * @param to A character to skip to.
556 *
557 * @return The requested character, or zero if the requested character
558 * is not found.
559 */
560 public char skipTo(char to) throws JSONException
561 {
562 char c;
563 try
564 {
565 int startIndex = this.index;
566 reader.mark(Integer.MAX_VALUE);
567 do
568 {
569 c = next();
570 if (c == 0)
571 {
572 reader.reset();
573 this.index = startIndex;
574 return c;
575 }
576 }
577 while (c != to);
578 }
579 catch (IOException exc)
580 {
581 throw new JSONException(exc);
582 }
583
584 back();
585 return c;
586 }
587
588
589 /**
590 * Make a JSONException to signal a syntax error.
591 *
592 * @param message The error message.
593 *
594 * @return A JSONException object, suitable for throwing
595 */
596 public JSONException syntaxError(String message)
597 {
598 return new JSONException(message + toString());
599 }
600
601
602 /**
603 * Make a printable string of this JSONTokener.
604 *
605 * @return " at character [this.index]"
606 */
607 public String toString()
608 {
609 return " at character " + index;
610 }
611 }