View Javadoc

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