1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.util; 18 19 import libcore.internal.StringPool; 20 21 import java.io.Closeable; 22 import java.io.EOFException; 23 import java.io.IOException; 24 import java.io.Reader; 25 import java.util.ArrayList; 26 import java.util.List; 27 28 29 /** 30 * Reads a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>) 31 * encoded value as a stream of tokens. This stream includes both literal 32 * values (strings, numbers, booleans, and nulls) as well as the begin and 33 * end delimiters of objects and arrays. The tokens are traversed in 34 * depth-first order, the same order that they appear in the JSON document. 35 * Within JSON objects, name/value pairs are represented by a single token. 36 * 37 * <h3>Parsing JSON</h3> 38 * To create a recursive descent parser for your own JSON streams, first create 39 * an entry point method that creates a {@code JsonReader}. 40 * 41 * <p>Next, create handler methods for each structure in your JSON text. You'll 42 * need a method for each object type and for each array type. 43 * <ul> 44 * <li>Within <strong>array handling</strong> methods, first call {@link 45 * #beginArray} to consume the array's opening bracket. Then create a 46 * while loop that accumulates values, terminating when {@link #hasNext} 47 * is false. Finally, read the array's closing bracket by calling {@link 48 * #endArray}. 49 * <li>Within <strong>object handling</strong> methods, first call {@link 50 * #beginObject} to consume the object's opening brace. Then create a 51 * while loop that assigns values to local variables based on their name. 52 * This loop should terminate when {@link #hasNext} is false. Finally, 53 * read the object's closing brace by calling {@link #endObject}. 54 * </ul> 55 * <p>When a nested object or array is encountered, delegate to the 56 * corresponding handler method. 57 * 58 * <p>When an unknown name is encountered, strict parsers should fail with an 59 * exception. Lenient parsers should call {@link #skipValue()} to recursively 60 * skip the value's nested tokens, which may otherwise conflict. 61 * 62 * <p>If a value may be null, you should first check using {@link #peek()}. 63 * Null literals can be consumed using either {@link #nextNull()} or {@link 64 * #skipValue()}. 65 * 66 * <h3>Example</h3> 67 * Suppose we'd like to parse a stream of messages such as the following: <pre> {@code 68 * [ 69 * { 70 * "id": 912345678901, 71 * "text": "How do I read JSON on Android?", 72 * "geo": null, 73 * "user": { 74 * "name": "android_newb", 75 * "followers_count": 41 76 * } 77 * }, 78 * { 79 * "id": 912345678902, 80 * "text": "@android_newb just use android.util.JsonReader!", 81 * "geo": [50.454722, -104.606667], 82 * "user": { 83 * "name": "jesse", 84 * "followers_count": 2 85 * } 86 * } 87 * ]}</pre> 88 * This code implements the parser for the above structure: <pre> {@code 89 * 90 * public List<Message> readJsonStream(InputStream in) throws IOException { 91 * JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); 92 * try { 93 * return readMessagesArray(reader); 94 * } finally { 95 * reader.close(); 96 * } 97 * } 98 * 99 * public List<Message> readMessagesArray(JsonReader reader) throws IOException { 100 * List<Message> messages = new ArrayList<Message>(); 101 * 102 * reader.beginArray(); 103 * while (reader.hasNext()) { 104 * messages.add(readMessage(reader)); 105 * } 106 * reader.endArray(); 107 * return messages; 108 * } 109 * 110 * public Message readMessage(JsonReader reader) throws IOException { 111 * long id = -1; 112 * String text = null; 113 * User user = null; 114 * List<Double> geo = null; 115 * 116 * reader.beginObject(); 117 * while (reader.hasNext()) { 118 * String name = reader.nextName(); 119 * if (name.equals("id")) { 120 * id = reader.nextLong(); 121 * } else if (name.equals("text")) { 122 * text = reader.nextString(); 123 * } else if (name.equals("geo") && reader.peek() != JsonToken.NULL) { 124 * geo = readDoublesArray(reader); 125 * } else if (name.equals("user")) { 126 * user = readUser(reader); 127 * } else { 128 * reader.skipValue(); 129 * } 130 * } 131 * reader.endObject(); 132 * return new Message(id, text, user, geo); 133 * } 134 * 135 * public List<Double> readDoublesArray(JsonReader reader) throws IOException { 136 * List<Double> doubles = new ArrayList<Double>(); 137 * 138 * reader.beginArray(); 139 * while (reader.hasNext()) { 140 * doubles.add(reader.nextDouble()); 141 * } 142 * reader.endArray(); 143 * return doubles; 144 * } 145 * 146 * public User readUser(JsonReader reader) throws IOException { 147 * String username = null; 148 * int followersCount = -1; 149 * 150 * reader.beginObject(); 151 * while (reader.hasNext()) { 152 * String name = reader.nextName(); 153 * if (name.equals("name")) { 154 * username = reader.nextString(); 155 * } else if (name.equals("followers_count")) { 156 * followersCount = reader.nextInt(); 157 * } else { 158 * reader.skipValue(); 159 * } 160 * } 161 * reader.endObject(); 162 * return new User(username, followersCount); 163 * }}</pre> 164 * 165 * <h3>Number Handling</h3> 166 * This reader permits numeric values to be read as strings and string values to 167 * be read as numbers. For example, both elements of the JSON array {@code 168 * [1, "1"]} may be read using either {@link #nextInt} or {@link #nextString}. 169 * This behavior is intended to prevent lossy numeric conversions: double is 170 * JavaScript's only numeric type and very large values like {@code 171 * 9007199254740993} cannot be represented exactly on that platform. To minimize 172 * precision loss, extremely large values should be written and read as strings 173 * in JSON. 174 * 175 * <p>Each {@code JsonReader} may be used to read a single JSON stream. Instances 176 * of this class are not thread safe. 177 */ 178 public final class JsonReader implements Closeable { 179 180 private static final String TRUE = "true"; 181 private static final String FALSE = "false"; 182 183 private final StringPool stringPool = new StringPool(); 184 185 /** The input JSON. */ 186 private final Reader in; 187 188 /** True to accept non-spec compliant JSON */ 189 private boolean lenient = false; 190 191 /** 192 * Use a manual buffer to easily read and unread upcoming characters, and 193 * also so we can create strings without an intermediate StringBuilder. 194 * We decode literals directly out of this buffer, so it must be at least as 195 * long as the longest token that can be reported as a number. 196 */ 197 private final char[] buffer = new char[1024]; 198 private int pos = 0; 199 private int limit = 0; 200 201 /* 202 * The offset of the first character in the buffer. 203 */ 204 private int bufferStartLine = 1; 205 private int bufferStartColumn = 1; 206 207 private final List<JsonScope> stack = new ArrayList<JsonScope>(); 208 { 209 push(JsonScope.EMPTY_DOCUMENT); 210 } 211 212 /** 213 * The type of the next token to be returned by {@link #peek} and {@link 214 * #advance}. If null, peek() will assign a value. 215 */ 216 private JsonToken token; 217 218 /** The text of the next name. */ 219 private String name; 220 221 /* 222 * For the next literal value, we may have the text value, or the position 223 * and length in the buffer. 224 */ 225 private String value; 226 private int valuePos; 227 private int valueLength; 228 229 /** True if we're currently handling a skipValue() call. */ 230 private boolean skipping = false; 231 232 /** 233 * Creates a new instance that reads a JSON-encoded stream from {@code in}. 234 */ JsonReader(Reader in)235 public JsonReader(Reader in) { 236 if (in == null) { 237 throw new NullPointerException("in == null"); 238 } 239 this.in = in; 240 } 241 242 /** 243 * Configure this parser to be be liberal in what it accepts. By default, 244 * this parser is strict and only accepts JSON as specified by <a 245 * href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the 246 * parser to lenient causes it to ignore the following syntax errors: 247 * 248 * <ul> 249 * <li>End of line comments starting with {@code //} or {@code #} and 250 * ending with a newline character. 251 * <li>C-style comments starting with {@code /*} and ending with 252 * {@code *}{@code /}. Such comments may not be nested. 253 * <li>Names that are unquoted or {@code 'single quoted'}. 254 * <li>Strings that are unquoted or {@code 'single quoted'}. 255 * <li>Array elements separated by {@code ;} instead of {@code ,}. 256 * <li>Unnecessary array separators. These are interpreted as if null 257 * was the omitted value. 258 * <li>Names and values separated by {@code =} or {@code =>} instead of 259 * {@code :}. 260 * <li>Name/value pairs separated by {@code ;} instead of {@code ,}. 261 * </ul> 262 */ setLenient(boolean lenient)263 public void setLenient(boolean lenient) { 264 this.lenient = lenient; 265 } 266 267 /** 268 * Returns true if this parser is liberal in what it accepts. 269 */ isLenient()270 public boolean isLenient() { 271 return lenient; 272 } 273 274 /** 275 * Consumes the next token from the JSON stream and asserts that it is the 276 * beginning of a new array. 277 */ beginArray()278 public void beginArray() throws IOException { 279 expect(JsonToken.BEGIN_ARRAY); 280 } 281 282 /** 283 * Consumes the next token from the JSON stream and asserts that it is the 284 * end of the current array. 285 */ endArray()286 public void endArray() throws IOException { 287 expect(JsonToken.END_ARRAY); 288 } 289 290 /** 291 * Consumes the next token from the JSON stream and asserts that it is the 292 * beginning of a new object. 293 */ beginObject()294 public void beginObject() throws IOException { 295 expect(JsonToken.BEGIN_OBJECT); 296 } 297 298 /** 299 * Consumes the next token from the JSON stream and asserts that it is the 300 * end of the current object. 301 */ endObject()302 public void endObject() throws IOException { 303 expect(JsonToken.END_OBJECT); 304 } 305 306 /** 307 * Consumes {@code expected}. 308 */ expect(JsonToken expected)309 private void expect(JsonToken expected) throws IOException { 310 peek(); 311 if (token != expected) { 312 throw new IllegalStateException("Expected " + expected + " but was " + peek()); 313 } 314 advance(); 315 } 316 317 /** 318 * Returns true if the current array or object has another element. 319 */ hasNext()320 public boolean hasNext() throws IOException { 321 peek(); 322 return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY; 323 } 324 325 /** 326 * Returns the type of the next token without consuming it. 327 */ peek()328 public JsonToken peek() throws IOException { 329 if (token != null) { 330 return token; 331 } 332 333 switch (peekStack()) { 334 case EMPTY_DOCUMENT: 335 replaceTop(JsonScope.NONEMPTY_DOCUMENT); 336 JsonToken firstToken = nextValue(); 337 if (!lenient && token != JsonToken.BEGIN_ARRAY && token != JsonToken.BEGIN_OBJECT) { 338 throw new IOException( 339 "Expected JSON document to start with '[' or '{' but was " + token); 340 } 341 return firstToken; 342 case EMPTY_ARRAY: 343 return nextInArray(true); 344 case NONEMPTY_ARRAY: 345 return nextInArray(false); 346 case EMPTY_OBJECT: 347 return nextInObject(true); 348 case DANGLING_NAME: 349 return objectValue(); 350 case NONEMPTY_OBJECT: 351 return nextInObject(false); 352 case NONEMPTY_DOCUMENT: 353 try { 354 JsonToken token = nextValue(); 355 if (lenient) { 356 return token; 357 } 358 throw syntaxError("Expected EOF"); 359 } catch (EOFException e) { 360 return token = JsonToken.END_DOCUMENT; // TODO: avoid throwing here? 361 } 362 case CLOSED: 363 throw new IllegalStateException("JsonReader is closed"); 364 default: 365 throw new AssertionError(); 366 } 367 } 368 369 /** 370 * Advances the cursor in the JSON stream to the next token. 371 */ advance()372 private JsonToken advance() throws IOException { 373 peek(); 374 375 JsonToken result = token; 376 token = null; 377 value = null; 378 name = null; 379 return result; 380 } 381 382 /** 383 * Returns the next token, a {@link JsonToken#NAME property name}, and 384 * consumes it. 385 * 386 * @throws IOException if the next token in the stream is not a property 387 * name. 388 */ nextName()389 public String nextName() throws IOException { 390 peek(); 391 if (token != JsonToken.NAME) { 392 throw new IllegalStateException("Expected a name but was " + peek()); 393 } 394 String result = name; 395 advance(); 396 return result; 397 } 398 399 /** 400 * Returns the {@link JsonToken#STRING string} value of the next token, 401 * consuming it. If the next token is a number, this method will return its 402 * string form. 403 * 404 * @throws IllegalStateException if the next token is not a string or if 405 * this reader is closed. 406 */ nextString()407 public String nextString() throws IOException { 408 peek(); 409 if (token != JsonToken.STRING && token != JsonToken.NUMBER) { 410 throw new IllegalStateException("Expected a string but was " + peek()); 411 } 412 413 String result = value; 414 advance(); 415 return result; 416 } 417 418 /** 419 * Returns the {@link JsonToken#BOOLEAN boolean} value of the next token, 420 * consuming it. 421 * 422 * @throws IllegalStateException if the next token is not a boolean or if 423 * this reader is closed. 424 */ nextBoolean()425 public boolean nextBoolean() throws IOException { 426 peek(); 427 if (token != JsonToken.BOOLEAN) { 428 throw new IllegalStateException("Expected a boolean but was " + token); 429 } 430 431 boolean result = (value == TRUE); 432 advance(); 433 return result; 434 } 435 436 /** 437 * Consumes the next token from the JSON stream and asserts that it is a 438 * literal null. 439 * 440 * @throws IllegalStateException if the next token is not null or if this 441 * reader is closed. 442 */ nextNull()443 public void nextNull() throws IOException { 444 peek(); 445 if (token != JsonToken.NULL) { 446 throw new IllegalStateException("Expected null but was " + token); 447 } 448 449 advance(); 450 } 451 452 /** 453 * Returns the {@link JsonToken#NUMBER double} value of the next token, 454 * consuming it. If the next token is a string, this method will attempt to 455 * parse it as a double using {@link Double#parseDouble(String)}. 456 * 457 * @throws IllegalStateException if the next token is not a literal value. 458 */ nextDouble()459 public double nextDouble() throws IOException { 460 peek(); 461 if (token != JsonToken.STRING && token != JsonToken.NUMBER) { 462 throw new IllegalStateException("Expected a double but was " + token); 463 } 464 465 double result = Double.parseDouble(value); 466 advance(); 467 return result; 468 } 469 470 /** 471 * Returns the {@link JsonToken#NUMBER long} value of the next token, 472 * consuming it. If the next token is a string, this method will attempt to 473 * parse it as a long. If the next token's numeric value cannot be exactly 474 * represented by a Java {@code long}, this method throws. 475 * 476 * @throws IllegalStateException if the next token is not a literal value. 477 * @throws NumberFormatException if the next literal value cannot be parsed 478 * as a number, or exactly represented as a long. 479 */ nextLong()480 public long nextLong() throws IOException { 481 peek(); 482 if (token != JsonToken.STRING && token != JsonToken.NUMBER) { 483 throw new IllegalStateException("Expected a long but was " + token); 484 } 485 486 long result; 487 try { 488 result = Long.parseLong(value); 489 } catch (NumberFormatException ignored) { 490 double asDouble = Double.parseDouble(value); // don't catch this NumberFormatException 491 result = (long) asDouble; 492 if ((double) result != asDouble) { 493 throw new NumberFormatException(value); 494 } 495 } 496 497 advance(); 498 return result; 499 } 500 501 /** 502 * Returns the {@link JsonToken#NUMBER int} value of the next token, 503 * consuming it. If the next token is a string, this method will attempt to 504 * parse it as an int. If the next token's numeric value cannot be exactly 505 * represented by a Java {@code int}, this method throws. 506 * 507 * @throws IllegalStateException if the next token is not a literal value. 508 * @throws NumberFormatException if the next literal value cannot be parsed 509 * as a number, or exactly represented as an int. 510 */ nextInt()511 public int nextInt() throws IOException { 512 peek(); 513 if (token != JsonToken.STRING && token != JsonToken.NUMBER) { 514 throw new IllegalStateException("Expected an int but was " + token); 515 } 516 517 int result; 518 try { 519 result = Integer.parseInt(value); 520 } catch (NumberFormatException ignored) { 521 double asDouble = Double.parseDouble(value); // don't catch this NumberFormatException 522 result = (int) asDouble; 523 if ((double) result != asDouble) { 524 throw new NumberFormatException(value); 525 } 526 } 527 528 advance(); 529 return result; 530 } 531 532 /** 533 * Closes this JSON reader and the underlying {@link Reader}. 534 */ close()535 public void close() throws IOException { 536 value = null; 537 token = null; 538 stack.clear(); 539 stack.add(JsonScope.CLOSED); 540 in.close(); 541 } 542 543 /** 544 * Skips the next value recursively. If it is an object or array, all nested 545 * elements are skipped. This method is intended for use when the JSON token 546 * stream contains unrecognized or unhandled values. 547 */ skipValue()548 public void skipValue() throws IOException { 549 skipping = true; 550 try { 551 if (!hasNext() || peek() == JsonToken.END_DOCUMENT) { 552 throw new IllegalStateException("No element left to skip"); 553 } 554 int count = 0; 555 do { 556 JsonToken token = advance(); 557 if (token == JsonToken.BEGIN_ARRAY || token == JsonToken.BEGIN_OBJECT) { 558 count++; 559 } else if (token == JsonToken.END_ARRAY || token == JsonToken.END_OBJECT) { 560 count--; 561 } 562 } while (count != 0); 563 } finally { 564 skipping = false; 565 } 566 } 567 peekStack()568 private JsonScope peekStack() { 569 return stack.get(stack.size() - 1); 570 } 571 pop()572 private JsonScope pop() { 573 return stack.remove(stack.size() - 1); 574 } 575 push(JsonScope newTop)576 private void push(JsonScope newTop) { 577 stack.add(newTop); 578 } 579 580 /** 581 * Replace the value on the top of the stack with the given value. 582 */ replaceTop(JsonScope newTop)583 private void replaceTop(JsonScope newTop) { 584 stack.set(stack.size() - 1, newTop); 585 } 586 nextInArray(boolean firstElement)587 private JsonToken nextInArray(boolean firstElement) throws IOException { 588 if (firstElement) { 589 replaceTop(JsonScope.NONEMPTY_ARRAY); 590 } else { 591 /* Look for a comma before each element after the first element. */ 592 switch (nextNonWhitespace()) { 593 case ']': 594 pop(); 595 return token = JsonToken.END_ARRAY; 596 case ';': 597 checkLenient(); // fall-through 598 case ',': 599 break; 600 default: 601 throw syntaxError("Unterminated array"); 602 } 603 } 604 605 switch (nextNonWhitespace()) { 606 case ']': 607 if (firstElement) { 608 pop(); 609 return token = JsonToken.END_ARRAY; 610 } 611 // fall-through to handle ",]" 612 case ';': 613 case ',': 614 /* In lenient mode, a 0-length literal means 'null' */ 615 checkLenient(); 616 pos--; 617 value = "null"; 618 return token = JsonToken.NULL; 619 default: 620 pos--; 621 return nextValue(); 622 } 623 } 624 nextInObject(boolean firstElement)625 private JsonToken nextInObject(boolean firstElement) throws IOException { 626 /* 627 * Read delimiters. Either a comma/semicolon separating this and the 628 * previous name-value pair, or a close brace to denote the end of the 629 * object. 630 */ 631 if (firstElement) { 632 /* Peek to see if this is the empty object. */ 633 switch (nextNonWhitespace()) { 634 case '}': 635 pop(); 636 return token = JsonToken.END_OBJECT; 637 default: 638 pos--; 639 } 640 } else { 641 switch (nextNonWhitespace()) { 642 case '}': 643 pop(); 644 return token = JsonToken.END_OBJECT; 645 case ';': 646 case ',': 647 break; 648 default: 649 throw syntaxError("Unterminated object"); 650 } 651 } 652 653 /* Read the name. */ 654 int quote = nextNonWhitespace(); 655 switch (quote) { 656 case '\'': 657 checkLenient(); // fall-through 658 case '"': 659 name = nextString((char) quote); 660 break; 661 default: 662 checkLenient(); 663 pos--; 664 name = nextLiteral(false); 665 if (name.isEmpty()) { 666 throw syntaxError("Expected name"); 667 } 668 } 669 670 replaceTop(JsonScope.DANGLING_NAME); 671 return token = JsonToken.NAME; 672 } 673 objectValue()674 private JsonToken objectValue() throws IOException { 675 /* 676 * Read the name/value separator. Usually a colon ':'. In lenient mode 677 * we also accept an equals sign '=', or an arrow "=>". 678 */ 679 switch (nextNonWhitespace()) { 680 case ':': 681 break; 682 case '=': 683 checkLenient(); 684 if ((pos < limit || fillBuffer(1)) && buffer[pos] == '>') { 685 pos++; 686 } 687 break; 688 default: 689 throw syntaxError("Expected ':'"); 690 } 691 692 replaceTop(JsonScope.NONEMPTY_OBJECT); 693 return nextValue(); 694 } 695 nextValue()696 private JsonToken nextValue() throws IOException { 697 int c = nextNonWhitespace(); 698 switch (c) { 699 case '{': 700 push(JsonScope.EMPTY_OBJECT); 701 return token = JsonToken.BEGIN_OBJECT; 702 703 case '[': 704 push(JsonScope.EMPTY_ARRAY); 705 return token = JsonToken.BEGIN_ARRAY; 706 707 case '\'': 708 checkLenient(); // fall-through 709 case '"': 710 value = nextString((char) c); 711 return token = JsonToken.STRING; 712 713 default: 714 pos--; 715 return readLiteral(); 716 } 717 } 718 719 /** 720 * Returns true once {@code limit - pos >= minimum}. If the data is 721 * exhausted before that many characters are available, this returns 722 * false. 723 */ fillBuffer(int minimum)724 private boolean fillBuffer(int minimum) throws IOException { 725 // Before clobbering the old characters, update where buffer starts 726 for (int i = 0; i < pos; i++) { 727 if (buffer[i] == '\n') { 728 bufferStartLine++; 729 bufferStartColumn = 1; 730 } else { 731 bufferStartColumn++; 732 } 733 } 734 735 if (limit != pos) { 736 limit -= pos; 737 System.arraycopy(buffer, pos, buffer, 0, limit); 738 } else { 739 limit = 0; 740 } 741 742 pos = 0; 743 int total; 744 while ((total = in.read(buffer, limit, buffer.length - limit)) != -1) { 745 limit += total; 746 747 // if this is the first read, consume an optional byte order mark (BOM) if it exists 748 if (bufferStartLine == 1 && bufferStartColumn == 1 749 && limit > 0 && buffer[0] == '\ufeff') { 750 pos++; 751 bufferStartColumn--; 752 } 753 754 if (limit >= minimum) { 755 return true; 756 } 757 } 758 return false; 759 } 760 getLineNumber()761 private int getLineNumber() { 762 int result = bufferStartLine; 763 for (int i = 0; i < pos; i++) { 764 if (buffer[i] == '\n') { 765 result++; 766 } 767 } 768 return result; 769 } 770 getColumnNumber()771 private int getColumnNumber() { 772 int result = bufferStartColumn; 773 for (int i = 0; i < pos; i++) { 774 if (buffer[i] == '\n') { 775 result = 1; 776 } else { 777 result++; 778 } 779 } 780 return result; 781 } 782 nextNonWhitespace()783 private int nextNonWhitespace() throws IOException { 784 while (pos < limit || fillBuffer(1)) { 785 int c = buffer[pos++]; 786 switch (c) { 787 case '\t': 788 case ' ': 789 case '\n': 790 case '\r': 791 continue; 792 793 case '/': 794 if (pos == limit && !fillBuffer(1)) { 795 return c; 796 } 797 798 checkLenient(); 799 char peek = buffer[pos]; 800 switch (peek) { 801 case '*': 802 // skip a /* c-style comment */ 803 pos++; 804 if (!skipTo("*/")) { 805 throw syntaxError("Unterminated comment"); 806 } 807 pos += 2; 808 continue; 809 810 case '/': 811 // skip a // end-of-line comment 812 pos++; 813 skipToEndOfLine(); 814 continue; 815 816 default: 817 return c; 818 } 819 820 case '#': 821 /* 822 * Skip a # hash end-of-line comment. The JSON RFC doesn't 823 * specify this behaviour, but it's required to parse 824 * existing documents. See http://b/2571423. 825 */ 826 checkLenient(); 827 skipToEndOfLine(); 828 continue; 829 830 default: 831 return c; 832 } 833 } 834 835 throw new EOFException("End of input"); 836 } 837 checkLenient()838 private void checkLenient() throws IOException { 839 if (!lenient) { 840 throw syntaxError("Use JsonReader.setLenient(true) to accept malformed JSON"); 841 } 842 } 843 844 /** 845 * Advances the position until after the next newline character. If the line 846 * is terminated by "\r\n", the '\n' must be consumed as whitespace by the 847 * caller. 848 */ skipToEndOfLine()849 private void skipToEndOfLine() throws IOException { 850 while (pos < limit || fillBuffer(1)) { 851 char c = buffer[pos++]; 852 if (c == '\r' || c == '\n') { 853 break; 854 } 855 } 856 } 857 skipTo(String toFind)858 private boolean skipTo(String toFind) throws IOException { 859 outer: 860 for (; pos + toFind.length() <= limit || fillBuffer(toFind.length()); pos++) { 861 for (int c = 0; c < toFind.length(); c++) { 862 if (buffer[pos + c] != toFind.charAt(c)) { 863 continue outer; 864 } 865 } 866 return true; 867 } 868 return false; 869 } 870 871 /** 872 * Returns the string up to but not including {@code quote}, unescaping any 873 * character escape sequences encountered along the way. The opening quote 874 * should have already been read. This consumes the closing quote, but does 875 * not include it in the returned string. 876 * 877 * @param quote either ' or ". 878 * @throws NumberFormatException if any unicode escape sequences are 879 * malformed. 880 */ nextString(char quote)881 private String nextString(char quote) throws IOException { 882 StringBuilder builder = null; 883 do { 884 /* the index of the first character not yet appended to the builder. */ 885 int start = pos; 886 while (pos < limit) { 887 int c = buffer[pos++]; 888 889 if (c == quote) { 890 if (skipping) { 891 return "skipped!"; 892 } else if (builder == null) { 893 return stringPool.get(buffer, start, pos - start - 1); 894 } else { 895 builder.append(buffer, start, pos - start - 1); 896 return builder.toString(); 897 } 898 899 } else if (c == '\\') { 900 if (builder == null) { 901 builder = new StringBuilder(); 902 } 903 builder.append(buffer, start, pos - start - 1); 904 builder.append(readEscapeCharacter()); 905 start = pos; 906 } 907 } 908 909 if (builder == null) { 910 builder = new StringBuilder(); 911 } 912 builder.append(buffer, start, pos - start); 913 } while (fillBuffer(1)); 914 915 throw syntaxError("Unterminated string"); 916 } 917 918 /** 919 * Reads the value up to but not including any delimiter characters. This 920 * does not consume the delimiter character. 921 * 922 * @param assignOffsetsOnly true for this method to only set the valuePos 923 * and valueLength fields and return a null result. This only works if 924 * the literal is short; a string is returned otherwise. 925 */ nextLiteral(boolean assignOffsetsOnly)926 private String nextLiteral(boolean assignOffsetsOnly) throws IOException { 927 StringBuilder builder = null; 928 valuePos = -1; 929 valueLength = 0; 930 int i = 0; 931 932 findNonLiteralCharacter: 933 while (true) { 934 for (; pos + i < limit; i++) { 935 switch (buffer[pos + i]) { 936 case '/': 937 case '\\': 938 case ';': 939 case '#': 940 case '=': 941 checkLenient(); // fall-through 942 case '{': 943 case '}': 944 case '[': 945 case ']': 946 case ':': 947 case ',': 948 case ' ': 949 case '\t': 950 case '\f': 951 case '\r': 952 case '\n': 953 break findNonLiteralCharacter; 954 } 955 } 956 957 /* 958 * Attempt to load the entire literal into the buffer at once. If 959 * we run out of input, add a non-literal character at the end so 960 * that decoding doesn't need to do bounds checks. 961 */ 962 if (i < buffer.length) { 963 if (fillBuffer(i + 1)) { 964 continue; 965 } else { 966 buffer[limit] = '\0'; 967 break; 968 } 969 } 970 971 // use a StringBuilder when the value is too long. It must be an unquoted string. 972 if (builder == null) { 973 builder = new StringBuilder(); 974 } 975 builder.append(buffer, pos, i); 976 valueLength += i; 977 pos += i; 978 i = 0; 979 if (!fillBuffer(1)) { 980 break; 981 } 982 } 983 984 String result; 985 if (assignOffsetsOnly && builder == null) { 986 valuePos = pos; 987 result = null; 988 } else if (skipping) { 989 result = "skipped!"; 990 } else if (builder == null) { 991 result = stringPool.get(buffer, pos, i); 992 } else { 993 builder.append(buffer, pos, i); 994 result = builder.toString(); 995 } 996 valueLength += i; 997 pos += i; 998 return result; 999 } 1000 toString()1001 @Override public String toString() { 1002 return getClass().getSimpleName() + " near " + getSnippet(); 1003 } 1004 1005 /** 1006 * Unescapes the character identified by the character or characters that 1007 * immediately follow a backslash. The backslash '\' should have already 1008 * been read. This supports both unicode escapes "u000A" and two-character 1009 * escapes "\n". 1010 * 1011 * @throws NumberFormatException if any unicode escape sequences are 1012 * malformed. 1013 */ readEscapeCharacter()1014 private char readEscapeCharacter() throws IOException { 1015 if (pos == limit && !fillBuffer(1)) { 1016 throw syntaxError("Unterminated escape sequence"); 1017 } 1018 1019 char escaped = buffer[pos++]; 1020 switch (escaped) { 1021 case 'u': 1022 if (pos + 4 > limit && !fillBuffer(4)) { 1023 throw syntaxError("Unterminated escape sequence"); 1024 } 1025 String hex = stringPool.get(buffer, pos, 4); 1026 pos += 4; 1027 return (char) Integer.parseInt(hex, 16); 1028 1029 case 't': 1030 return '\t'; 1031 1032 case 'b': 1033 return '\b'; 1034 1035 case 'n': 1036 return '\n'; 1037 1038 case 'r': 1039 return '\r'; 1040 1041 case 'f': 1042 return '\f'; 1043 1044 case '\'': 1045 case '"': 1046 case '\\': 1047 default: 1048 return escaped; 1049 } 1050 } 1051 1052 /** 1053 * Reads a null, boolean, numeric or unquoted string literal value. 1054 */ readLiteral()1055 private JsonToken readLiteral() throws IOException { 1056 value = nextLiteral(true); 1057 if (valueLength == 0) { 1058 throw syntaxError("Expected literal value"); 1059 } 1060 token = decodeLiteral(); 1061 if (token == JsonToken.STRING) { 1062 checkLenient(); 1063 } 1064 return token; 1065 } 1066 1067 /** 1068 * Assigns {@code nextToken} based on the value of {@code nextValue}. 1069 */ decodeLiteral()1070 private JsonToken decodeLiteral() throws IOException { 1071 if (valuePos == -1) { 1072 // it was too long to fit in the buffer so it can only be a string 1073 return JsonToken.STRING; 1074 } else if (valueLength == 4 1075 && ('n' == buffer[valuePos ] || 'N' == buffer[valuePos ]) 1076 && ('u' == buffer[valuePos + 1] || 'U' == buffer[valuePos + 1]) 1077 && ('l' == buffer[valuePos + 2] || 'L' == buffer[valuePos + 2]) 1078 && ('l' == buffer[valuePos + 3] || 'L' == buffer[valuePos + 3])) { 1079 value = "null"; 1080 return JsonToken.NULL; 1081 } else if (valueLength == 4 1082 && ('t' == buffer[valuePos ] || 'T' == buffer[valuePos ]) 1083 && ('r' == buffer[valuePos + 1] || 'R' == buffer[valuePos + 1]) 1084 && ('u' == buffer[valuePos + 2] || 'U' == buffer[valuePos + 2]) 1085 && ('e' == buffer[valuePos + 3] || 'E' == buffer[valuePos + 3])) { 1086 value = TRUE; 1087 return JsonToken.BOOLEAN; 1088 } else if (valueLength == 5 1089 && ('f' == buffer[valuePos ] || 'F' == buffer[valuePos ]) 1090 && ('a' == buffer[valuePos + 1] || 'A' == buffer[valuePos + 1]) 1091 && ('l' == buffer[valuePos + 2] || 'L' == buffer[valuePos + 2]) 1092 && ('s' == buffer[valuePos + 3] || 'S' == buffer[valuePos + 3]) 1093 && ('e' == buffer[valuePos + 4] || 'E' == buffer[valuePos + 4])) { 1094 value = FALSE; 1095 return JsonToken.BOOLEAN; 1096 } else { 1097 value = stringPool.get(buffer, valuePos, valueLength); 1098 return decodeNumber(buffer, valuePos, valueLength); 1099 } 1100 } 1101 1102 /** 1103 * Determine whether the characters is a JSON number. Numbers are of the 1104 * form -12.34e+56. Fractional and exponential parts are optional. Leading 1105 * zeroes are not allowed in the value or exponential part, but are allowed 1106 * in the fraction. 1107 */ decodeNumber(char[] chars, int offset, int length)1108 private JsonToken decodeNumber(char[] chars, int offset, int length) { 1109 int i = offset; 1110 int c = chars[i]; 1111 1112 if (c == '-') { 1113 c = chars[++i]; 1114 } 1115 1116 if (c == '0') { 1117 c = chars[++i]; 1118 } else if (c >= '1' && c <= '9') { 1119 c = chars[++i]; 1120 while (c >= '0' && c <= '9') { 1121 c = chars[++i]; 1122 } 1123 } else { 1124 return JsonToken.STRING; 1125 } 1126 1127 if (c == '.') { 1128 c = chars[++i]; 1129 while (c >= '0' && c <= '9') { 1130 c = chars[++i]; 1131 } 1132 } 1133 1134 if (c == 'e' || c == 'E') { 1135 c = chars[++i]; 1136 if (c == '+' || c == '-') { 1137 c = chars[++i]; 1138 } 1139 if (c >= '0' && c <= '9') { 1140 c = chars[++i]; 1141 while (c >= '0' && c <= '9') { 1142 c = chars[++i]; 1143 } 1144 } else { 1145 return JsonToken.STRING; 1146 } 1147 } 1148 1149 if (i == offset + length) { 1150 return JsonToken.NUMBER; 1151 } else { 1152 return JsonToken.STRING; 1153 } 1154 } 1155 1156 /** 1157 * Throws a new IO exception with the given message and a context snippet 1158 * with this reader's content. 1159 */ syntaxError(String message)1160 private IOException syntaxError(String message) throws IOException { 1161 throw new MalformedJsonException(message 1162 + " at line " + getLineNumber() + " column " + getColumnNumber()); 1163 } 1164 getSnippet()1165 private CharSequence getSnippet() { 1166 StringBuilder snippet = new StringBuilder(); 1167 int beforePos = Math.min(pos, 20); 1168 snippet.append(buffer, pos - beforePos, beforePos); 1169 int afterPos = Math.min(limit - pos, 20); 1170 snippet.append(buffer, pos, afterPos); 1171 return snippet; 1172 } 1173 } 1174