View Javadoc

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