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