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> <small>(double
208 * quote)</small> or <code>'</code> <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 }