1 package org.apache.struts.flow.json;
2
3 import java.text.ParseException;
4
5 /***
6 * A JSONTokener takes a source string and extracts characters and tokens from
7 * it. It is used by the JSONObject and JSONArray constructors to parse
8 * JSON source strings.
9 * <p>
10 * Public Domain 2002 JSON.org
11 * @author JSON.org
12 * @version 0.1
13 */
14 public class JSONTokener {
15
16 /***
17 * The index of the next character.
18 */
19 private int myIndex;
20
21
22 /***
23 * The source string being tokenized.
24 */
25 private String mySource;
26
27
28 /***
29 * Construct a JSONTokener from a string.
30 *
31 * @param s A source string.
32 */
33 public JSONTokener(String s) {
34 myIndex = 0;
35 mySource = s;
36 }
37
38
39 /***
40 * Back up one character. This provides a sort of lookahead capability,
41 * so that you can test for a digit or letter before attempting to parse
42 * the next number or identifier.
43 */
44 public void back() {
45 if (myIndex > 0) {
46 myIndex -= 1;
47 }
48 }
49
50
51
52 /***
53 * Get the hex value of a character (base16).
54 * @param c A character between '0' and '9' or between 'A' and 'F' or
55 * between 'a' and 'f'.
56 * @return An int between 0 and 15, or -1 if c was not a hex digit.
57 */
58 public static int dehexchar(char c) {
59 if (c >= '0' && c <= '9') {
60 return c - '0';
61 }
62 if (c >= 'A' && c <= 'F') {
63 return c + 10 - 'A';
64 }
65 if (c >= 'a' && c <= 'f') {
66 return c + 10 - 'a';
67 }
68 return -1;
69 }
70
71
72 /***
73 * Determine if the source string still contains characters that next()
74 * can consume.
75 * @return true if not yet at the end of the source.
76 */
77 public boolean more() {
78 return myIndex < mySource.length();
79 }
80
81
82 /***
83 * Get the next character in the source string.
84 *
85 * @return The next character, or 0 if past the end of the source string.
86 */
87 public char next() {
88 char c = more() ? mySource.charAt(myIndex) : 0;
89 myIndex += 1;
90 return c;
91 }
92
93
94 /***
95 * Consume the next character, and check that it matches a specified
96 * character.
97 * @throws ParseException if the character does not match.
98 * @param c The character to match.
99 * @return The character.
100 */
101 public char next(char c) throws ParseException {
102 char n = next();
103 if (n != c) {
104 throw syntaxError("Expected '" + c + "' and instead saw '" +
105 n + "'.");
106 }
107 return n;
108 }
109
110
111 /***
112 * Get the next n characters.
113 * @exception ParseException
114 * Substring bounds error if there are not
115 * n characters remaining in the source string.
116 *
117 * @param n The number of characters to take.
118 * @return A string of n characters.
119 */
120 public String next(int n) throws ParseException {
121 int i = myIndex;
122 int j = i + n;
123 if (j >= mySource.length()) {
124 throw syntaxError("Substring bounds error");
125 }
126 myIndex += n;
127 return mySource.substring(i, j);
128 }
129
130
131 /***
132 * Get the next char in the string, skipping whitespace
133 * and comments (slashslash and slashstar).
134 * @throws ParseException
135 * @return A character, or 0 if there are no more characters.
136 */
137 public char nextClean() throws java.text.ParseException {
138 while (true) {
139 char c = next();
140 if (c == '/') {
141 switch (next()) {
142 case '/':
143 do {
144 c = next();
145 } while (c != '\n' && c != '\r' && c != 0);
146 break;
147 case '*':
148 while (true) {
149 c = next();
150 if (c == 0) {
151 throw syntaxError("Unclosed comment.");
152 }
153 if (c == '*') {
154 if (next() == '/') {
155 break;
156 }
157 back();
158 }
159 }
160 break;
161 default:
162 back();
163 return '/';
164 }
165 } else if (c == 0 || c > ' ') {
166 return c;
167 }
168 }
169 }
170
171
172 /***
173 * Return the characters up to the next close quote character.
174 * Backslash processing is done. The formal JSON format does not
175 * allow strings in single quotes, but an implementation is allowed to
176 * accept them.
177 * @exception ParseException Unterminated string.
178 * @param quote The quoting character, either " or '
179 * @return A String.
180 */
181 public String nextString(char quote) throws ParseException {
182 char c;
183 StringBuffer sb = new StringBuffer();
184 while (true) {
185 c = next();
186 switch (c) {
187 case 0:
188 case 0x0A:
189 case 0x0D:
190 throw syntaxError("Unterminated string");
191 case '//':
192 c = next();
193 switch (c) {
194 case 'b':
195 sb.append('\b');
196 break;
197 case 't':
198 sb.append('\t');
199 break;
200 case 'n':
201 sb.append('\n');
202 break;
203 case 'f':
204 sb.append('\f');
205 break;
206 case 'r':
207 sb.append('\r');
208 break;
209 case 'u':
210 sb.append((char)Integer.parseInt(next(4), 16));
211 break;
212 case 'x' :
213 sb.append((char) Integer.parseInt(next(2), 16));
214 break;
215 default:
216 sb.append(c);
217 }
218 break;
219 default:
220 if (c == quote) {
221 return sb.toString();
222 }
223 sb.append(c);
224 }
225 }
226 }
227
228
229 /***
230 * Get the text up but not including the specified character or the
231 * end of line, whichever comes first.
232 * @param d A delimiter character.
233 * @return A string.
234 */
235 public String nextTo(char d) {
236 StringBuffer sb = new StringBuffer();
237 while (true) {
238 char c = next();
239 if (c == d || c == 0 || c == '\n' || c == '\r') {
240 if (c != 0) {
241 back();
242 }
243 return sb.toString().trim();
244 }
245 sb.append(c);
246 }
247 }
248
249
250 /***
251 * Get the text up but not including one of the specified delimeter
252 * characters or the end of line, which ever comes first.
253 * @param delimiters A set of delimiter characters.
254 * @return A string, trimmed.
255 */
256 public String nextTo(String delimiters) {
257 char c;
258 StringBuffer sb = new StringBuffer();
259 while (true) {
260 c = next();
261 if (delimiters.indexOf(c) >= 0 || c == 0 ||
262 c == '\n' || c == '\r') {
263 if (c != 0) {
264 back();
265 }
266 return sb.toString().trim();
267 }
268 sb.append(c);
269 }
270 }
271
272
273 /***
274 * Get the next value. The value can be a Boolean, Double, Integer,
275 * JSONArray, JSONObject, or String, or the JSONObject.NULL object.
276 * @exception ParseException The source conform to JSON syntax.
277 *
278 * @return An object.
279 */
280 public Object nextValue() throws ParseException {
281 char c = nextClean();
282 String s;
283
284 if (c == '"' || c == '\'') {
285 return nextString(c);
286 }
287 if (c == '{') {
288 back();
289 return new JSONObject(this);
290 }
291 if (c == '[') {
292 back();
293 return new JSONArray(this);
294 }
295 StringBuffer sb = new StringBuffer();
296 char b = c;
297 while (c >= ' ' && c != ':' && c != ',' && c != ']' && c != '}' &&
298 c != '/') {
299 sb.append(c);
300 c = next();
301 }
302 back();
303 s = sb.toString().trim();
304 if (s.equals("true")) {
305 return Boolean.TRUE;
306 }
307 if (s.equals("false")) {
308 return Boolean.FALSE;
309 }
310 if (s.equals("null")) {
311 return JSONObject.NULL;
312 }
313 if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+') {
314 try {
315 return new Integer(s);
316 } catch (Exception e) {
317 }
318 try {
319 return new Double(s);
320 } catch (Exception e) {
321 }
322 }
323 if (s.equals("")) {
324 throw syntaxError("Missing value.");
325 }
326 return s;
327 }
328
329
330 /***
331 * Skip characters until the next character is the requested character.
332 * If the requested character is not found, no characters are skipped.
333 * @param to A character to skip to.
334 * @return The requested character, or zero if the requested character
335 * is not found.
336 */
337 public char skipTo(char to) {
338 char c;
339 int index = myIndex;
340 do {
341 c = next();
342 if (c == 0) {
343 myIndex = index;
344 return c;
345 }
346 } while (c != to);
347 back();
348 return c;
349 }
350
351
352 /***
353 * Skip characters until past the requested string.
354 * If it is not found, we are left at the end of the source.
355 * @param to A string to skip past.
356 */
357 public void skipPast(String to) {
358 myIndex = mySource.indexOf(to, myIndex);
359 if (myIndex < 0) {
360 myIndex = mySource.length();
361 } else {
362 myIndex += to.length();
363 }
364 }
365
366
367 /***
368 * Make a ParseException to signal a syntax error.
369 *
370 * @param message The error message.
371 * @return A ParseException object, suitable for throwing
372 */
373 public ParseException syntaxError(String message) {
374 return new ParseException(message + toString(), myIndex);
375 }
376
377
378 /***
379 * Make a printable string of this JSONTokener.
380 *
381 * @return " at character [myIndex] of [mySource]"
382 */
383 public String toString() {
384 return " at character " + myIndex + " of " + mySource;
385 }
386
387
388 /***
389 * Unescape the source text. Convert %hh sequences to single characters,
390 * and convert plus to space. There are Web transport systems that insist on
391 * doing unnecessary URL encoding. This provides a way to undo it.
392 */
393 public void unescape() {
394 mySource = unescape(mySource);
395 }
396
397 /***
398 * Convert %hh sequences to single characters, and convert plus to space.
399 * @param s A string that may contain plus and %hh sequences.
400 * @return The unescaped string.
401 */
402 public static String unescape(String s) {
403 int len = s.length();
404 StringBuffer b = new StringBuffer();
405 for (int i = 0; i < len; ++i) {
406 char c = s.charAt(i);
407 if (c == '+') {
408 c = ' ';
409 } else if (c == '%' && i + 2 < len) {
410 int d = dehexchar(s.charAt(i + 1));
411 int e = dehexchar(s.charAt(i + 2));
412 if (d >= 0 && e >= 0) {
413 c = (char)(d * 16 + e);
414 i += 2;
415 }
416 }
417 b.append(c);
418 }
419 return b.toString();
420 }
421 }