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