001package org.json; 002 003import java.io.BufferedReader; 004import java.io.IOException; 005import java.io.InputStream; 006import java.io.InputStreamReader; 007import java.io.Reader; 008import java.io.StringReader; 009 010/* 011Copyright (c) 2002 JSON.org 012 013Permission is hereby granted, free of charge, to any person obtaining a copy 014of this software and associated documentation files (the "Software"), to deal 015in the Software without restriction, including without limitation the rights 016to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 017copies of the Software, and to permit persons to whom the Software is 018furnished to do so, subject to the following conditions: 019 020The above copyright notice and this permission notice shall be included in all 021copies or substantial portions of the Software. 022 023The Software shall be used for Good, not Evil. 024 025THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 026IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 027FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 028AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 029LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 030OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 031SOFTWARE. 032*/ 033 034/** 035 * A JSONTokener takes a source string and extracts characters and tokens from 036 * it. It is used by the JSONObject and JSONArray constructors to parse 037 * JSON source strings. 038 * @author JSON.org 039 * @version 2014-05-03 040 */ 041public class JSONTokener { 042 043 private long character; 044 private boolean eof; 045 private long index; 046 private long line; 047 private char previous; 048 private Reader reader; 049 private boolean usePrevious; 050 051 052 /** 053 * Construct a JSONTokener from a Reader. 054 * 055 * @param reader A reader. 056 */ 057 public JSONTokener(Reader reader) { 058 this.reader = reader.markSupported() 059 ? reader 060 : new BufferedReader(reader); 061 this.eof = false; 062 this.usePrevious = false; 063 this.previous = 0; 064 this.index = 0; 065 this.character = 1; 066 this.line = 1; 067 } 068 069 070 /** 071 * Construct a JSONTokener from an InputStream. 072 * @param inputStream The source. 073 */ 074 public JSONTokener(InputStream inputStream) throws JSONException { 075 this(new InputStreamReader(inputStream)); 076 } 077 078 079 /** 080 * Construct a JSONTokener from a string. 081 * 082 * @param s A source string. 083 */ 084 public JSONTokener(String s) { 085 this(new StringReader(s)); 086 } 087 088 089 /** 090 * Back up one character. This provides a sort of lookahead capability, 091 * so that you can test for a digit or letter before attempting to parse 092 * the next number or identifier. 093 */ 094 public void back() throws JSONException { 095 if (this.usePrevious || this.index <= 0) { 096 throw new JSONException("Stepping back two steps is not supported"); 097 } 098 this.index -= 1; 099 this.character -= 1; 100 this.usePrevious = true; 101 this.eof = false; 102 } 103 104 105 /** 106 * Get the hex value of a character (base16). 107 * @param c A character between '0' and '9' or between 'A' and 'F' or 108 * between 'a' and 'f'. 109 * @return An int between 0 and 15, or -1 if c was not a hex digit. 110 */ 111 public static int dehexchar(char c) { 112 if (c >= '0' && c <= '9') { 113 return c - '0'; 114 } 115 if (c >= 'A' && c <= 'F') { 116 return c - ('A' - 10); 117 } 118 if (c >= 'a' && c <= 'f') { 119 return c - ('a' - 10); 120 } 121 return -1; 122 } 123 124 public boolean end() { 125 return this.eof && !this.usePrevious; 126 } 127 128 129 /** 130 * Determine if the source string still contains characters that next() 131 * can consume. 132 * @return true if not yet at the end of the source. 133 */ 134 public boolean more() throws JSONException { 135 this.next(); 136 if (this.end()) { 137 return false; 138 } 139 this.back(); 140 return true; 141 } 142 143 144 /** 145 * Get the next character in the source string. 146 * 147 * @return The next character, or 0 if past the end of the source string. 148 */ 149 public char next() throws JSONException { 150 int c; 151 if (this.usePrevious) { 152 this.usePrevious = false; 153 c = this.previous; 154 } else { 155 try { 156 c = this.reader.read(); 157 } catch (IOException exception) { 158 throw new JSONException(exception); 159 } 160 161 if (c <= 0) { // End of stream 162 this.eof = true; 163 c = 0; 164 } 165 } 166 this.index += 1; 167 if (this.previous == '\r') { 168 this.line += 1; 169 this.character = c == '\n' ? 0 : 1; 170 } else if (c == '\n') { 171 this.line += 1; 172 this.character = 0; 173 } else { 174 this.character += 1; 175 } 176 this.previous = (char) c; 177 return this.previous; 178 } 179 180 181 /** 182 * Consume the next character, and check that it matches a specified 183 * character. 184 * @param c The character to match. 185 * @return The character. 186 * @throws JSONException if the character does not match. 187 */ 188 public char next(char c) throws JSONException { 189 char n = this.next(); 190 if (n != c) { 191 throw this.syntaxError("Expected '" + c + "' and instead saw '" + 192 n + "'"); 193 } 194 return n; 195 } 196 197 198 /** 199 * Get the next n characters. 200 * 201 * @param n The number of characters to take. 202 * @return A string of n characters. 203 * @throws JSONException 204 * Substring bounds error if there are not 205 * n characters remaining in the source string. 206 */ 207 public String next(int n) throws JSONException { 208 if (n == 0) { 209 return ""; 210 } 211 212 char[] chars = new char[n]; 213 int pos = 0; 214 215 while (pos < n) { 216 chars[pos] = this.next(); 217 if (this.end()) { 218 throw this.syntaxError("Substring bounds error"); 219 } 220 pos += 1; 221 } 222 return new String(chars); 223 } 224 225 226 /** 227 * Get the next char in the string, skipping whitespace. 228 * @throws JSONException 229 * @return A character, or 0 if there are no more characters. 230 */ 231 public char nextClean() throws JSONException { 232 for (;;) { 233 char c = this.next(); 234 if (c == 0 || c > ' ') { 235 return c; 236 } 237 } 238 } 239 240 241 /** 242 * Return the characters up to the next close quote character. 243 * Backslash processing is done. The formal JSON format does not 244 * allow strings in single quotes, but an implementation is allowed to 245 * accept them. 246 * @param quote The quoting character, either 247 * <code>"</code> <small>(double quote)</small> or 248 * <code>'</code> <small>(single quote)</small>. 249 * @return A String. 250 * @throws JSONException Unterminated string. 251 */ 252 public String nextString(char quote) throws JSONException { 253 char c; 254 StringBuilder sb = new StringBuilder(); 255 for (;;) { 256 c = this.next(); 257 switch (c) { 258 case 0: 259 case '\n': 260 case '\r': 261 throw this.syntaxError("Unterminated string"); 262 case '\\': 263 c = this.next(); 264 switch (c) { 265 case 'b': 266 sb.append('\b'); 267 break; 268 case 't': 269 sb.append('\t'); 270 break; 271 case 'n': 272 sb.append('\n'); 273 break; 274 case 'f': 275 sb.append('\f'); 276 break; 277 case 'r': 278 sb.append('\r'); 279 break; 280 case 'u': 281 sb.append((char)Integer.parseInt(this.next(4), 16)); 282 break; 283 case '"': 284 case '\'': 285 case '\\': 286 case '/': 287 sb.append(c); 288 break; 289 default: 290 throw this.syntaxError("Illegal escape."); 291 } 292 break; 293 default: 294 if (c == quote) { 295 return sb.toString(); 296 } 297 sb.append(c); 298 } 299 } 300 } 301 302 303 /** 304 * Get the text up but not including the specified character or the 305 * end of line, whichever comes first. 306 * @param delimiter A delimiter character. 307 * @return A string. 308 */ 309 public String nextTo(char delimiter) throws JSONException { 310 StringBuilder sb = new StringBuilder(); 311 for (;;) { 312 char c = this.next(); 313 if (c == delimiter || c == 0 || c == '\n' || c == '\r') { 314 if (c != 0) { 315 this.back(); 316 } 317 return sb.toString().trim(); 318 } 319 sb.append(c); 320 } 321 } 322 323 324 /** 325 * Get the text up but not including one of the specified delimiter 326 * characters or the end of line, whichever comes first. 327 * @param delimiters A set of delimiter characters. 328 * @return A string, trimmed. 329 */ 330 public String nextTo(String delimiters) throws JSONException { 331 char c; 332 StringBuilder sb = new StringBuilder(); 333 for (;;) { 334 c = this.next(); 335 if (delimiters.indexOf(c) >= 0 || c == 0 || 336 c == '\n' || c == '\r') { 337 if (c != 0) { 338 this.back(); 339 } 340 return sb.toString().trim(); 341 } 342 sb.append(c); 343 } 344 } 345 346 347 /** 348 * Get the next value. The value can be a Boolean, Double, Integer, 349 * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object. 350 * @throws JSONException If syntax error. 351 * 352 * @return An object. 353 */ 354 public Object nextValue() throws JSONException { 355 char c = this.nextClean(); 356 String string; 357 358 switch (c) { 359 case '"': 360 case '\'': 361 return this.nextString(c); 362 case '{': 363 this.back(); 364 return new JSONObject(this); 365 case '[': 366 this.back(); 367 return new JSONArray(this); 368 } 369 370 /* 371 * Handle unquoted text. This could be the values true, false, or 372 * null, or it can be a number. An implementation (such as this one) 373 * is allowed to also accept non-standard forms. 374 * 375 * Accumulate characters until we reach the end of the text or a 376 * formatting character. 377 */ 378 379 StringBuilder sb = new StringBuilder(); 380 while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { 381 sb.append(c); 382 c = this.next(); 383 } 384 this.back(); 385 386 string = sb.toString().trim(); 387 if ("".equals(string)) { 388 throw this.syntaxError("Missing value"); 389 } 390 return JSONObject.stringToValue(string); 391 } 392 393 394 /** 395 * Skip characters until the next character is the requested character. 396 * If the requested character is not found, no characters are skipped. 397 * @param to A character to skip to. 398 * @return The requested character, or zero if the requested character 399 * is not found. 400 */ 401 public char skipTo(char to) throws JSONException { 402 char c; 403 try { 404 long startIndex = this.index; 405 long startCharacter = this.character; 406 long startLine = this.line; 407 this.reader.mark(1000000); 408 do { 409 c = this.next(); 410 if (c == 0) { 411 this.reader.reset(); 412 this.index = startIndex; 413 this.character = startCharacter; 414 this.line = startLine; 415 return c; 416 } 417 } while (c != to); 418 } catch (IOException exception) { 419 throw new JSONException(exception); 420 } 421 this.back(); 422 return c; 423 } 424 425 426 /** 427 * Make a JSONException to signal a syntax error. 428 * 429 * @param message The error message. 430 * @return A JSONException object, suitable for throwing 431 */ 432 public JSONException syntaxError(String message) { 433 return new JSONException(message + this.toString()); 434 } 435 436 437 /** 438 * Make a printable string of this JSONTokener. 439 * 440 * @return " at {index} [character {character} line {line}]" 441 */ 442 public String toString() { 443 return " at " + this.index + " [character " + this.character + " line " + 444 this.line + "]"; 445 } 446}