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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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> <small>(double quote)</small> or 231 * <code>'</code> <small>(single quote)</small>. 232 * @return A String. 233 * @throws JSONException Unterminated string. 234 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 419 public String toString() { 420 return " at character " + index; 421 } 422 }