1 package com.google.polo.json;
2 
3 import java.io.BufferedReader;
4 import java.io.IOException;
5 import java.io.Reader;
6 import java.io.StringReader;
7 
8 /*
9 Copyright (c) 2002 JSON.org
10 
11 Permission is hereby granted, free of charge, to any person obtaining a copy
12 of this software and associated documentation files (the "Software"), to deal
13 in the Software without restriction, including without limitation the rights
14 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 copies of the Software, and to permit persons to whom the Software is
16 furnished to do so, subject to the following conditions:
17 
18 The above copyright notice and this permission notice shall be included in all
19 copies or substantial portions of the Software.
20 
21 The Software shall be used for Good, not Evil.
22 
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 SOFTWARE.
30 */
31 
32 /**
33  * A JSONTokener takes a source string and extracts characters and tokens from
34  * it. It is used by the JSONObject and JSONArray constructors to parse
35  * JSON source strings.
36  * @author JSON.org
37  * @version 2008-09-18
38  */
39 public class JSONTokener {
40 
41     private int index;
42     private Reader reader;
43     private char lastChar;
44     private boolean useLastChar;
45 
46 
47     /**
48      * Construct a JSONTokener from a string.
49      *
50      * @param reader     A reader.
51      */
JSONTokener(Reader reader)52     public JSONTokener(Reader reader) {
53         this.reader = reader.markSupported() ?
54         		reader : new BufferedReader(reader);
55         this.useLastChar = false;
56         this.index = 0;
57     }
58 
59 
60     /**
61      * Construct a JSONTokener from a string.
62      *
63      * @param s     A source string.
64      */
JSONTokener(String s)65     public JSONTokener(String s) {
66         this(new StringReader(s));
67     }
68 
69 
70     /**
71      * Back up one character. This provides a sort of lookahead capability,
72      * so that you can test for a digit or letter before attempting to parse
73      * the next number or identifier.
74      */
back()75     public void back() throws JSONException {
76         if (useLastChar || index <= 0) {
77             throw new JSONException("Stepping back two steps is not supported");
78         }
79         index -= 1;
80         useLastChar = true;
81     }
82 
83 
84 
85     /**
86      * Get the hex value of a character (base16).
87      * @param c A character between '0' and '9' or between 'A' and 'F' or
88      * between 'a' and 'f'.
89      * @return  An int between 0 and 15, or -1 if c was not a hex digit.
90      */
dehexchar(char c)91     public static int dehexchar(char c) {
92         if (c >= '0' && c <= '9') {
93             return c - '0';
94         }
95         if (c >= 'A' && c <= 'F') {
96             return c - ('A' - 10);
97         }
98         if (c >= 'a' && c <= 'f') {
99             return c - ('a' - 10);
100         }
101         return -1;
102     }
103 
104 
105     /**
106      * Determine if the source string still contains characters that next()
107      * can consume.
108      * @return true if not yet at the end of the source.
109      */
more()110     public boolean more() throws JSONException {
111         char nextChar = next();
112         if (nextChar == 0) {
113             return false;
114         }
115         back();
116         return true;
117     }
118 
119 
120     /**
121      * Get the next character in the source string.
122      *
123      * @return The next character, or 0 if past the end of the source string.
124      */
next()125     public char next() throws JSONException {
126         if (this.useLastChar) {
127         	this.useLastChar = false;
128             if (this.lastChar != 0) {
129             	this.index += 1;
130             }
131             return this.lastChar;
132         }
133         int c;
134         try {
135             c = this.reader.read();
136         } catch (IOException exc) {
137             throw new JSONException(exc);
138         }
139 
140         if (c <= 0) { // End of stream
141         	this.lastChar = 0;
142             return 0;
143         }
144     	this.index += 1;
145     	this.lastChar = (char) c;
146         return this.lastChar;
147     }
148 
149 
150     /**
151      * Consume the next character, and check that it matches a specified
152      * character.
153      * @param c The character to match.
154      * @return The character.
155      * @throws JSONException if the character does not match.
156      */
next(char c)157     public char next(char c) throws JSONException {
158         char n = next();
159         if (n != c) {
160             throw syntaxError("Expected '" + c + "' and instead saw '" +
161                     n + "'");
162         }
163         return n;
164     }
165 
166 
167     /**
168      * Get the next n characters.
169      *
170      * @param n     The number of characters to take.
171      * @return      A string of n characters.
172      * @throws JSONException
173      *   Substring bounds error if there are not
174      *   n characters remaining in the source string.
175      */
next(int n)176      public String next(int n) throws JSONException {
177          if (n == 0) {
178              return "";
179          }
180 
181          char[] buffer = new char[n];
182          int pos = 0;
183 
184          if (this.useLastChar) {
185         	 this.useLastChar = false;
186              buffer[0] = this.lastChar;
187              pos = 1;
188          }
189 
190          try {
191              int len;
192              while ((pos < n) && ((len = reader.read(buffer, pos, n - pos)) != -1)) {
193                  pos += len;
194              }
195          } catch (IOException exc) {
196              throw new JSONException(exc);
197          }
198          this.index += pos;
199 
200          if (pos < n) {
201              throw syntaxError("Substring bounds error");
202          }
203 
204          this.lastChar = buffer[n - 1];
205          return new String(buffer);
206      }
207 
208 
209     /**
210      * Get the next char in the string, skipping whitespace.
211      * @throws JSONException
212      * @return  A character, or 0 if there are no more characters.
213      */
nextClean()214     public char nextClean() throws JSONException {
215         for (;;) {
216             char c = next();
217             if (c == 0 || c > ' ') {
218                 return c;
219             }
220         }
221     }
222 
223 
224     /**
225      * Return the characters up to the next close quote character.
226      * Backslash processing is done. The formal JSON format does not
227      * allow strings in single quotes, but an implementation is allowed to
228      * accept them.
229      * @param quote The quoting character, either
230      *      <code>"</code>&nbsp;<small>(double quote)</small> or
231      *      <code>'</code>&nbsp;<small>(single quote)</small>.
232      * @return      A String.
233      * @throws JSONException Unterminated string.
234      */
nextString(char quote)235     public String nextString(char quote) throws JSONException {
236         char c;
237         StringBuffer sb = new StringBuffer();
238         for (;;) {
239             c = next();
240             switch (c) {
241             case 0:
242             case '\n':
243             case '\r':
244                 throw syntaxError("Unterminated string");
245             case '\\':
246                 c = next();
247                 switch (c) {
248                 case 'b':
249                     sb.append('\b');
250                     break;
251                 case 't':
252                     sb.append('\t');
253                     break;
254                 case 'n':
255                     sb.append('\n');
256                     break;
257                 case 'f':
258                     sb.append('\f');
259                     break;
260                 case 'r':
261                     sb.append('\r');
262                     break;
263                 case 'u':
264                     sb.append((char)Integer.parseInt(next(4), 16));
265                     break;
266                 case 'x' :
267                     sb.append((char) Integer.parseInt(next(2), 16));
268                     break;
269                 default:
270                     sb.append(c);
271                 }
272                 break;
273             default:
274                 if (c == quote) {
275                     return sb.toString();
276                 }
277                 sb.append(c);
278             }
279         }
280     }
281 
282 
283     /**
284      * Get the text up but not including the specified character or the
285      * end of line, whichever comes first.
286      * @param  d A delimiter character.
287      * @return   A string.
288      */
nextTo(char d)289     public String nextTo(char d) throws JSONException {
290         StringBuffer sb = new StringBuffer();
291         for (;;) {
292             char c = next();
293             if (c == d || c == 0 || c == '\n' || c == '\r') {
294                 if (c != 0) {
295                     back();
296                 }
297                 return sb.toString().trim();
298             }
299             sb.append(c);
300         }
301     }
302 
303 
304     /**
305      * Get the text up but not including one of the specified delimiter
306      * characters or the end of line, whichever comes first.
307      * @param delimiters A set of delimiter characters.
308      * @return A string, trimmed.
309      */
nextTo(String delimiters)310     public String nextTo(String delimiters) throws JSONException {
311         char c;
312         StringBuffer sb = new StringBuffer();
313         for (;;) {
314             c = next();
315             if (delimiters.indexOf(c) >= 0 || c == 0 ||
316                     c == '\n' || c == '\r') {
317                 if (c != 0) {
318                     back();
319                 }
320                 return sb.toString().trim();
321             }
322             sb.append(c);
323         }
324     }
325 
326 
327     /**
328      * Get the next value. The value can be a Boolean, Double, Integer,
329      * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
330      * @throws JSONException If syntax error.
331      *
332      * @return An object.
333      */
nextValue()334     public Object nextValue() throws JSONException {
335         char c = nextClean();
336         String s;
337 
338         switch (c) {
339             case '"':
340             case '\'':
341                 return nextString(c);
342             case '{':
343                 back();
344                 return new JSONObject(this);
345             case '[':
346             case '(':
347                 back();
348                 return new JSONArray(this);
349         }
350 
351         /*
352          * Handle unquoted text. This could be the values true, false, or
353          * null, or it can be a number. An implementation (such as this one)
354          * is allowed to also accept non-standard forms.
355          *
356          * Accumulate characters until we reach the end of the text or a
357          * formatting character.
358          */
359 
360         StringBuffer sb = new StringBuffer();
361         while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
362             sb.append(c);
363             c = next();
364         }
365         back();
366 
367         s = sb.toString().trim();
368         if (s.equals("")) {
369             throw syntaxError("Missing value");
370         }
371         return JSONObject.stringToValue(s);
372     }
373 
374 
375     /**
376      * Skip characters until the next character is the requested character.
377      * If the requested character is not found, no characters are skipped.
378      * @param to A character to skip to.
379      * @return The requested character, or zero if the requested character
380      * is not found.
381      */
skipTo(char to)382     public char skipTo(char to) throws JSONException {
383         char c;
384         try {
385             int startIndex = this.index;
386             reader.mark(Integer.MAX_VALUE);
387             do {
388                 c = next();
389                 if (c == 0) {
390                     reader.reset();
391                     this.index = startIndex;
392                     return c;
393                 }
394             } while (c != to);
395         } catch (IOException exc) {
396             throw new JSONException(exc);
397         }
398 
399         back();
400         return c;
401     }
402 
403     /**
404      * Make a JSONException to signal a syntax error.
405      *
406      * @param message The error message.
407      * @return  A JSONException object, suitable for throwing
408      */
syntaxError(String message)409     public JSONException syntaxError(String message) {
410         return new JSONException(message + toString());
411     }
412 
413 
414     /**
415      * Make a printable string of this JSONTokener.
416      *
417      * @return " at character [this.index]"
418      */
toString()419     public String toString() {
420         return " at character " + index;
421     }
422 }