/* 
 | 
 * Copyright (C) 2010 Google Inc. 
 | 
 * 
 | 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 | 
 * you may not use this file except in compliance with the License. 
 | 
 * You may obtain a copy of the License at 
 | 
 * 
 | 
 *      http://www.apache.org/licenses/LICENSE-2.0 
 | 
 * 
 | 
 * Unless required by applicable law or agreed to in writing, software 
 | 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 | 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 | 
 * See the License for the specific language governing permissions and 
 | 
 * limitations under the License. 
 | 
 */ 
 | 
  
 | 
package cn.emay.sdk.util.json.gson.stream; 
 | 
  
 | 
import java.io.Closeable; 
 | 
import java.io.EOFException; 
 | 
import java.io.IOException; 
 | 
import java.io.Reader; 
 | 
  
 | 
import cn.emay.sdk.util.json.gson.internal.JsonReaderInternalAccess; 
 | 
import cn.emay.sdk.util.json.gson.internal.bind.JsonTreeReader; 
 | 
  
 | 
/** 
 | 
 * Reads a JSON (<a href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>) 
 | 
 * encoded value as a stream of tokens. This stream includes both literal values 
 | 
 * (strings, numbers, booleans, and nulls) as well as the begin and end 
 | 
 * delimiters of objects and arrays. The tokens are traversed in depth-first 
 | 
 * order, the same order that they appear in the JSON document. Within JSON 
 | 
 * objects, name/value pairs are represented by a single token. 
 | 
 * 
 | 
 * <h3>Parsing JSON</h3> To create a recursive descent parser for your own JSON 
 | 
 * streams, first create an entry point method that creates a 
 | 
 * {@code JsonReader}. 
 | 
 * 
 | 
 * <p> 
 | 
 * Next, create handler methods for each structure in your JSON text. You'll 
 | 
 * need a method for each object type and for each array type. 
 | 
 * <ul> 
 | 
 * <li>Within <strong>array handling</strong> methods, first call 
 | 
 * {@link #beginArray} to consume the array's opening bracket. Then create a 
 | 
 * while loop that accumulates values, terminating when {@link #hasNext} is 
 | 
 * false. Finally, read the array's closing bracket by calling 
 | 
 * {@link #endArray}. 
 | 
 * <li>Within <strong>object handling</strong> methods, first call 
 | 
 * {@link #beginObject} to consume the object's opening brace. Then create a 
 | 
 * while loop that assigns values to local variables based on their name. This 
 | 
 * loop should terminate when {@link #hasNext} is false. Finally, read the 
 | 
 * object's closing brace by calling {@link #endObject}. 
 | 
 * </ul> 
 | 
 * <p> 
 | 
 * When a nested object or array is encountered, delegate to the corresponding 
 | 
 * handler method. 
 | 
 * 
 | 
 * <p> 
 | 
 * When an unknown name is encountered, strict parsers should fail with an 
 | 
 * exception. Lenient parsers should call {@link #skipValue()} to recursively 
 | 
 * skip the value's nested tokens, which may otherwise conflict. 
 | 
 * 
 | 
 * <p> 
 | 
 * If a value may be null, you should first check using {@link #peek()}. Null 
 | 
 * literals can be consumed using either {@link #nextNull()} or 
 | 
 * {@link #skipValue()}. 
 | 
 * 
 | 
 * <h3>Example</h3> Suppose we'd like to parse a stream of messages such as the 
 | 
 * following: 
 | 
 *  
 | 
 * <pre> 
 | 
 *  {@code 
 | 
 * [ 
 | 
 *   { 
 | 
 *     "id": 912345678901, 
 | 
 *     "text": "How do I read a JSON stream in Java?", 
 | 
 *     "geo": null, 
 | 
 *     "user": { 
 | 
 *       "name": "json_newb", 
 | 
 *       "followers_count": 41 
 | 
 *      } 
 | 
 *   }, 
 | 
 *   { 
 | 
 *     "id": 912345678902, 
 | 
 *     "text": "@json_newb just use JsonReader!", 
 | 
 *     "geo": [50.454722, -104.606667], 
 | 
 *     "user": { 
 | 
 *       "name": "jesse", 
 | 
 *       "followers_count": 2 
 | 
 *     } 
 | 
 *   } 
 | 
 * ]} 
 | 
 * </pre> 
 | 
 *  
 | 
 * This code implements the parser for the above structure: 
 | 
 *  
 | 
 * <pre> 
 | 
 *    {@code 
 | 
 * 
 | 
 *   public List<Message> readJsonStream(InputStream in) throws IOException { 
 | 
 *     JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); 
 | 
 *     try { 
 | 
 *       return readMessagesArray(reader); 
 | 
 *     } finally { 
 | 
 *       reader.close(); 
 | 
 *     } 
 | 
 *   } 
 | 
 * 
 | 
 *   public List<Message> readMessagesArray(JsonReader reader) throws IOException { 
 | 
 *     List<Message> messages = new ArrayList<Message>(); 
 | 
 * 
 | 
 *     reader.beginArray(); 
 | 
 *     while (reader.hasNext()) { 
 | 
 *       messages.add(readMessage(reader)); 
 | 
 *     } 
 | 
 *     reader.endArray(); 
 | 
 *     return messages; 
 | 
 *   } 
 | 
 * 
 | 
 *   public Message readMessage(JsonReader reader) throws IOException { 
 | 
 *     long id = -1; 
 | 
 *     String text = null; 
 | 
 *     User user = null; 
 | 
 *     List<Double> geo = null; 
 | 
 * 
 | 
 *     reader.beginObject(); 
 | 
 *     while (reader.hasNext()) { 
 | 
 *       String name = reader.nextName(); 
 | 
 *       if (name.equals("id")) { 
 | 
 *         id = reader.nextLong(); 
 | 
 *       } else if (name.equals("text")) { 
 | 
 *         text = reader.nextString(); 
 | 
 *       } else if (name.equals("geo") && reader.peek() != JsonToken.NULL) { 
 | 
 *         geo = readDoublesArray(reader); 
 | 
 *       } else if (name.equals("user")) { 
 | 
 *         user = readUser(reader); 
 | 
 *       } else { 
 | 
 *         reader.skipValue(); 
 | 
 *       } 
 | 
 *     } 
 | 
 *     reader.endObject(); 
 | 
 *     return new Message(id, text, user, geo); 
 | 
 *   } 
 | 
 * 
 | 
 *   public List<Double> readDoublesArray(JsonReader reader) throws IOException { 
 | 
 *     List<Double> doubles = new ArrayList<Double>(); 
 | 
 * 
 | 
 *     reader.beginArray(); 
 | 
 *     while (reader.hasNext()) { 
 | 
 *       doubles.add(reader.nextDouble()); 
 | 
 *     } 
 | 
 *     reader.endArray(); 
 | 
 *     return doubles; 
 | 
 *   } 
 | 
 * 
 | 
 *   public User readUser(JsonReader reader) throws IOException { 
 | 
 *     String username = null; 
 | 
 *     int followersCount = -1; 
 | 
 * 
 | 
 *     reader.beginObject(); 
 | 
 *     while (reader.hasNext()) { 
 | 
 *       String name = reader.nextName(); 
 | 
 *       if (name.equals("name")) { 
 | 
 *         username = reader.nextString(); 
 | 
 *       } else if (name.equals("followers_count")) { 
 | 
 *         followersCount = reader.nextInt(); 
 | 
 *       } else { 
 | 
 *         reader.skipValue(); 
 | 
 *       } 
 | 
 *     } 
 | 
 *     reader.endObject(); 
 | 
 *     return new User(username, followersCount); 
 | 
 *   }} 
 | 
 * </pre> 
 | 
 * 
 | 
 * <h3>Number Handling</h3> This reader permits numeric values to be read as 
 | 
 * strings and string values to be read as numbers. For example, both elements 
 | 
 * of the JSON array {@code 
 | 
 * [1, "1"]} may be read using either {@link #nextInt} or {@link #nextString}. 
 | 
 * This behavior is intended to prevent lossy numeric conversions: double is 
 | 
 * JavaScript's only numeric type and very large values like {@code 
 | 
 * 9007199254740993} cannot be represented exactly on that platform. To minimize 
 | 
 * precision loss, extremely large values should be written and read as strings 
 | 
 * in JSON. 
 | 
 * 
 | 
 * <a name="nonexecuteprefix"/> 
 | 
 * <h3>Non-Execute Prefix</h3> Web servers that serve private data using JSON 
 | 
 * may be vulnerable to <a href= 
 | 
 * "http://en.wikipedia.org/wiki/JSON#Cross-site_request_forgery">Cross-site 
 | 
 * request forgery</a> attacks. In such an attack, a malicious site gains access 
 | 
 * to a private JSON file by executing it with an HTML {@code <script>} tag. 
 | 
 * 
 | 
 * <p> 
 | 
 * Prefixing JSON files with <code>")]}'\n"</code> makes them non-executable by 
 | 
 * {@code <script>} tags, disarming the attack. Since the prefix is malformed 
 | 
 * JSON, strict parsing fails when it is encountered. This class permits the 
 | 
 * non-execute prefix when {@link #setLenient(boolean) lenient parsing} is 
 | 
 * enabled. 
 | 
 * 
 | 
 * <p> 
 | 
 * Each {@code JsonReader} may be used to read a single JSON stream. Instances 
 | 
 * of this class are not thread safe. 
 | 
 * 
 | 
 * @author Jesse Wilson 
 | 
 * @since 1.6 
 | 
 */ 
 | 
public class JsonReader implements Closeable { 
 | 
    /** The only non-execute prefix this parser permits */ 
 | 
    private static final char[] NON_EXECUTE_PREFIX = ")]}'\n".toCharArray(); 
 | 
    private static final long MIN_INCOMPLETE_INTEGER = Long.MIN_VALUE / 10; 
 | 
  
 | 
    private static final int PEEKED_NONE = 0; 
 | 
    private static final int PEEKED_BEGIN_OBJECT = 1; 
 | 
    private static final int PEEKED_END_OBJECT = 2; 
 | 
    private static final int PEEKED_BEGIN_ARRAY = 3; 
 | 
    private static final int PEEKED_END_ARRAY = 4; 
 | 
    private static final int PEEKED_TRUE = 5; 
 | 
    private static final int PEEKED_FALSE = 6; 
 | 
    private static final int PEEKED_NULL = 7; 
 | 
    private static final int PEEKED_SINGLE_QUOTED = 8; 
 | 
    private static final int PEEKED_DOUBLE_QUOTED = 9; 
 | 
    private static final int PEEKED_UNQUOTED = 10; 
 | 
    /** When this is returned, the string value is stored in peekedString. */ 
 | 
    private static final int PEEKED_BUFFERED = 11; 
 | 
    private static final int PEEKED_SINGLE_QUOTED_NAME = 12; 
 | 
    private static final int PEEKED_DOUBLE_QUOTED_NAME = 13; 
 | 
    private static final int PEEKED_UNQUOTED_NAME = 14; 
 | 
    /** When this is returned, the integer value is stored in peekedLong. */ 
 | 
    private static final int PEEKED_LONG = 15; 
 | 
    private static final int PEEKED_NUMBER = 16; 
 | 
    private static final int PEEKED_EOF = 17; 
 | 
  
 | 
    /* State machine when parsing numbers */ 
 | 
    private static final int NUMBER_CHAR_NONE = 0; 
 | 
    private static final int NUMBER_CHAR_SIGN = 1; 
 | 
    private static final int NUMBER_CHAR_DIGIT = 2; 
 | 
    private static final int NUMBER_CHAR_DECIMAL = 3; 
 | 
    private static final int NUMBER_CHAR_FRACTION_DIGIT = 4; 
 | 
    private static final int NUMBER_CHAR_EXP_E = 5; 
 | 
    private static final int NUMBER_CHAR_EXP_SIGN = 6; 
 | 
    private static final int NUMBER_CHAR_EXP_DIGIT = 7; 
 | 
  
 | 
    /** The input JSON. */ 
 | 
    private final Reader in; 
 | 
  
 | 
    /** True to accept non-spec compliant JSON */ 
 | 
    private boolean lenient = false; 
 | 
  
 | 
    /** 
 | 
     * Use a manual buffer to easily read and unread upcoming characters, and also 
 | 
     * so we can create strings without an intermediate StringBuilder. We decode 
 | 
     * literals directly out of this buffer, so it must be at least as long as the 
 | 
     * longest token that can be reported as a number. 
 | 
     */ 
 | 
    private final char[] buffer = new char[1024]; 
 | 
    private int pos = 0; 
 | 
    private int limit = 0; 
 | 
  
 | 
    private int lineNumber = 0; 
 | 
    private int lineStart = 0; 
 | 
  
 | 
    int peeked = PEEKED_NONE; 
 | 
  
 | 
    /** 
 | 
     * A peeked value that was composed entirely of digits with an optional leading 
 | 
     * dash. Positive values may not have a leading 0. 
 | 
     */ 
 | 
    private long peekedLong; 
 | 
  
 | 
    /** 
 | 
     * The number of characters in a peeked number literal. Increment 'pos' by this 
 | 
     * after reading a number. 
 | 
     */ 
 | 
    private int peekedNumberLength; 
 | 
  
 | 
    /** 
 | 
     * A peeked string that should be parsed on the next double, long or string. 
 | 
     * This is populated before a numeric value is parsed and used if that parsing 
 | 
     * fails. 
 | 
     */ 
 | 
    private String peekedString; 
 | 
  
 | 
    /* 
 | 
     * The nesting stack. Using a manual array rather than an ArrayList saves 20%. 
 | 
     */ 
 | 
    private int[] stack = new int[32]; 
 | 
    private int stackSize = 0; 
 | 
    { 
 | 
        stack[stackSize++] = JsonScope.EMPTY_DOCUMENT; 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * The path members. It corresponds directly to stack: At indices where the 
 | 
     * stack contains an object (EMPTY_OBJECT, DANGLING_NAME or NONEMPTY_OBJECT), 
 | 
     * pathNames contains the name at this scope. Where it contains an array 
 | 
     * (EMPTY_ARRAY, NONEMPTY_ARRAY) pathIndices contains the current index in that 
 | 
     * array. Otherwise the value is undefined, and we take advantage of that by 
 | 
     * incrementing pathIndices when doing so isn't useful. 
 | 
     */ 
 | 
    private String[] pathNames = new String[32]; 
 | 
    private int[] pathIndices = new int[32]; 
 | 
  
 | 
    /** 
 | 
     * Creates a new instance that reads a JSON-encoded stream from {@code in}. 
 | 
     */ 
 | 
    public JsonReader(Reader in) { 
 | 
        if (in == null) { 
 | 
            throw new NullPointerException("in == null"); 
 | 
        } 
 | 
        this.in = in; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Configure this parser to be liberal in what it accepts. By default, this 
 | 
     * parser is strict and only accepts JSON as specified by 
 | 
     * <a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the 
 | 
     * parser to lenient causes it to ignore the following syntax errors: 
 | 
     * 
 | 
     * <ul> 
 | 
     * <li>Streams that start with the <a href="#nonexecuteprefix">non-execute 
 | 
     * prefix</a>, <code>")]}'\n"</code>. 
 | 
     * <li>Streams that include multiple top-level values. With strict parsing, each 
 | 
     * stream must contain exactly one top-level value. 
 | 
     * <li>Top-level values of any type. With strict parsing, the top-level value 
 | 
     * must be an object or an array. 
 | 
     * <li>Numbers may be {@link Double#isNaN() NaNs} or {@link Double#isInfinite() 
 | 
     * infinities}. 
 | 
     * <li>End of line comments starting with {@code //} or {@code #} and ending 
 | 
     * with a newline character. 
 | 
     * <li>C-style comments starting with {@code /*} and ending with 
 | 
     * {@code *}{@code /}. Such comments may not be nested. 
 | 
     * <li>Names that are unquoted or {@code 'single quoted'}. 
 | 
     * <li>Strings that are unquoted or {@code 'single quoted'}. 
 | 
     * <li>Array elements separated by {@code ;} instead of {@code ,}. 
 | 
     * <li>Unnecessary array separators. These are interpreted as if null was the 
 | 
     * omitted value. 
 | 
     * <li>Names and values separated by {@code =} or {@code =>} instead of 
 | 
     * {@code :}. 
 | 
     * <li>Name/value pairs separated by {@code ;} instead of {@code ,}. 
 | 
     * </ul> 
 | 
     */ 
 | 
    public final void setLenient(boolean lenient) { 
 | 
        this.lenient = lenient; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Returns true if this parser is liberal in what it accepts. 
 | 
     */ 
 | 
    public final boolean isLenient() { 
 | 
        return lenient; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Consumes the next token from the JSON stream and asserts that it is the 
 | 
     * beginning of a new array. 
 | 
     */ 
 | 
    public void beginArray() throws IOException { 
 | 
        int p = peeked; 
 | 
        if (p == PEEKED_NONE) { 
 | 
            p = doPeek(); 
 | 
        } 
 | 
        if (p == PEEKED_BEGIN_ARRAY) { 
 | 
            push(JsonScope.EMPTY_ARRAY); 
 | 
            pathIndices[stackSize - 1] = 0; 
 | 
            peeked = PEEKED_NONE; 
 | 
        } else { 
 | 
            throw new IllegalStateException("Expected BEGIN_ARRAY but was " + peek() + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Consumes the next token from the JSON stream and asserts that it is the end 
 | 
     * of the current array. 
 | 
     */ 
 | 
    public void endArray() throws IOException { 
 | 
        int p = peeked; 
 | 
        if (p == PEEKED_NONE) { 
 | 
            p = doPeek(); 
 | 
        } 
 | 
        if (p == PEEKED_END_ARRAY) { 
 | 
            stackSize--; 
 | 
            pathIndices[stackSize - 1]++; 
 | 
            peeked = PEEKED_NONE; 
 | 
        } else { 
 | 
            throw new IllegalStateException("Expected END_ARRAY but was " + peek() + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Consumes the next token from the JSON stream and asserts that it is the 
 | 
     * beginning of a new object. 
 | 
     */ 
 | 
    public void beginObject() throws IOException { 
 | 
        int p = peeked; 
 | 
        if (p == PEEKED_NONE) { 
 | 
            p = doPeek(); 
 | 
        } 
 | 
        if (p == PEEKED_BEGIN_OBJECT) { 
 | 
            push(JsonScope.EMPTY_OBJECT); 
 | 
            peeked = PEEKED_NONE; 
 | 
        } else { 
 | 
            throw new IllegalStateException("Expected BEGIN_OBJECT but was " + peek() + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Consumes the next token from the JSON stream and asserts that it is the end 
 | 
     * of the current object. 
 | 
     */ 
 | 
    public void endObject() throws IOException { 
 | 
        int p = peeked; 
 | 
        if (p == PEEKED_NONE) { 
 | 
            p = doPeek(); 
 | 
        } 
 | 
        if (p == PEEKED_END_OBJECT) { 
 | 
            stackSize--; 
 | 
            pathNames[stackSize] = null; // Free the last path name so that it can be garbage collected! 
 | 
            pathIndices[stackSize - 1]++; 
 | 
            peeked = PEEKED_NONE; 
 | 
        } else { 
 | 
            throw new IllegalStateException("Expected END_OBJECT but was " + peek() + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Returns true if the current array or object has another element. 
 | 
     */ 
 | 
    public boolean hasNext() throws IOException { 
 | 
        int p = peeked; 
 | 
        if (p == PEEKED_NONE) { 
 | 
            p = doPeek(); 
 | 
        } 
 | 
        return p != PEEKED_END_OBJECT && p != PEEKED_END_ARRAY; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Returns the type of the next token without consuming it. 
 | 
     */ 
 | 
    public JsonToken peek() throws IOException { 
 | 
        int p = peeked; 
 | 
        if (p == PEEKED_NONE) { 
 | 
            p = doPeek(); 
 | 
        } 
 | 
  
 | 
        switch (p) { 
 | 
        case PEEKED_BEGIN_OBJECT: 
 | 
            return JsonToken.BEGIN_OBJECT; 
 | 
        case PEEKED_END_OBJECT: 
 | 
            return JsonToken.END_OBJECT; 
 | 
        case PEEKED_BEGIN_ARRAY: 
 | 
            return JsonToken.BEGIN_ARRAY; 
 | 
        case PEEKED_END_ARRAY: 
 | 
            return JsonToken.END_ARRAY; 
 | 
        case PEEKED_SINGLE_QUOTED_NAME: 
 | 
        case PEEKED_DOUBLE_QUOTED_NAME: 
 | 
        case PEEKED_UNQUOTED_NAME: 
 | 
            return JsonToken.NAME; 
 | 
        case PEEKED_TRUE: 
 | 
        case PEEKED_FALSE: 
 | 
            return JsonToken.BOOLEAN; 
 | 
        case PEEKED_NULL: 
 | 
            return JsonToken.NULL; 
 | 
        case PEEKED_SINGLE_QUOTED: 
 | 
        case PEEKED_DOUBLE_QUOTED: 
 | 
        case PEEKED_UNQUOTED: 
 | 
        case PEEKED_BUFFERED: 
 | 
            return JsonToken.STRING; 
 | 
        case PEEKED_LONG: 
 | 
        case PEEKED_NUMBER: 
 | 
            return JsonToken.NUMBER; 
 | 
        case PEEKED_EOF: 
 | 
            return JsonToken.END_DOCUMENT; 
 | 
        default: 
 | 
            throw new AssertionError(); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    int doPeek() throws IOException { 
 | 
        int peekStack = stack[stackSize - 1]; 
 | 
        if (peekStack == JsonScope.EMPTY_ARRAY) { 
 | 
            stack[stackSize - 1] = JsonScope.NONEMPTY_ARRAY; 
 | 
        } else if (peekStack == JsonScope.NONEMPTY_ARRAY) { 
 | 
            // Look for a comma before the next element. 
 | 
            int c = nextNonWhitespace(true); 
 | 
            switch (c) { 
 | 
            case ']': 
 | 
                return peeked = PEEKED_END_ARRAY; 
 | 
            case ';': 
 | 
                checkLenient(); // fall-through 
 | 
            case ',': 
 | 
                break; 
 | 
            default: 
 | 
                throw syntaxError("Unterminated array"); 
 | 
            } 
 | 
        } else if (peekStack == JsonScope.EMPTY_OBJECT || peekStack == JsonScope.NONEMPTY_OBJECT) { 
 | 
            stack[stackSize - 1] = JsonScope.DANGLING_NAME; 
 | 
            // Look for a comma before the next element. 
 | 
            if (peekStack == JsonScope.NONEMPTY_OBJECT) { 
 | 
                int c = nextNonWhitespace(true); 
 | 
                switch (c) { 
 | 
                case '}': 
 | 
                    return peeked = PEEKED_END_OBJECT; 
 | 
                case ';': 
 | 
                    checkLenient(); // fall-through 
 | 
                case ',': 
 | 
                    break; 
 | 
                default: 
 | 
                    throw syntaxError("Unterminated object"); 
 | 
                } 
 | 
            } 
 | 
            int c = nextNonWhitespace(true); 
 | 
            switch (c) { 
 | 
            case '"': 
 | 
                return peeked = PEEKED_DOUBLE_QUOTED_NAME; 
 | 
            case '\'': 
 | 
                checkLenient(); 
 | 
                return peeked = PEEKED_SINGLE_QUOTED_NAME; 
 | 
            case '}': 
 | 
                if (peekStack != JsonScope.NONEMPTY_OBJECT) { 
 | 
                    return peeked = PEEKED_END_OBJECT; 
 | 
                } else { 
 | 
                    throw syntaxError("Expected name"); 
 | 
                } 
 | 
            default: 
 | 
                checkLenient(); 
 | 
                pos--; // Don't consume the first character in an unquoted string. 
 | 
                if (isLiteral((char) c)) { 
 | 
                    return peeked = PEEKED_UNQUOTED_NAME; 
 | 
                } else { 
 | 
                    throw syntaxError("Expected name"); 
 | 
                } 
 | 
            } 
 | 
        } else if (peekStack == JsonScope.DANGLING_NAME) { 
 | 
            stack[stackSize - 1] = JsonScope.NONEMPTY_OBJECT; 
 | 
            // Look for a colon before the value. 
 | 
            int c = nextNonWhitespace(true); 
 | 
            switch (c) { 
 | 
            case ':': 
 | 
                break; 
 | 
            case '=': 
 | 
                checkLenient(); 
 | 
                if ((pos < limit || fillBuffer(1)) && buffer[pos] == '>') { 
 | 
                    pos++; 
 | 
                } 
 | 
                break; 
 | 
            default: 
 | 
                throw syntaxError("Expected ':'"); 
 | 
            } 
 | 
        } else if (peekStack == JsonScope.EMPTY_DOCUMENT) { 
 | 
            if (lenient) { 
 | 
                consumeNonExecutePrefix(); 
 | 
            } 
 | 
            stack[stackSize - 1] = JsonScope.NONEMPTY_DOCUMENT; 
 | 
        } else if (peekStack == JsonScope.NONEMPTY_DOCUMENT) { 
 | 
            int c = nextNonWhitespace(false); 
 | 
            if (c == -1) { 
 | 
                return peeked = PEEKED_EOF; 
 | 
            } else { 
 | 
                checkLenient(); 
 | 
                pos--; 
 | 
            } 
 | 
        } else if (peekStack == JsonScope.CLOSED) { 
 | 
            throw new IllegalStateException("JsonReader is closed"); 
 | 
        } 
 | 
  
 | 
        int c = nextNonWhitespace(true); 
 | 
        switch (c) { 
 | 
        case ']': 
 | 
            if (peekStack == JsonScope.EMPTY_ARRAY) { 
 | 
                return peeked = PEEKED_END_ARRAY; 
 | 
            } 
 | 
            // fall-through to handle ",]" 
 | 
        case ';': 
 | 
        case ',': 
 | 
            // In lenient mode, a 0-length literal in an array means 'null'. 
 | 
            if (peekStack == JsonScope.EMPTY_ARRAY || peekStack == JsonScope.NONEMPTY_ARRAY) { 
 | 
                checkLenient(); 
 | 
                pos--; 
 | 
                return peeked = PEEKED_NULL; 
 | 
            } else { 
 | 
                throw syntaxError("Unexpected value"); 
 | 
            } 
 | 
        case '\'': 
 | 
            checkLenient(); 
 | 
            return peeked = PEEKED_SINGLE_QUOTED; 
 | 
        case '"': 
 | 
            return peeked = PEEKED_DOUBLE_QUOTED; 
 | 
        case '[': 
 | 
            return peeked = PEEKED_BEGIN_ARRAY; 
 | 
        case '{': 
 | 
            return peeked = PEEKED_BEGIN_OBJECT; 
 | 
        default: 
 | 
            pos--; // Don't consume the first character in a literal value. 
 | 
        } 
 | 
  
 | 
        int result = peekKeyword(); 
 | 
        if (result != PEEKED_NONE) { 
 | 
            return result; 
 | 
        } 
 | 
  
 | 
        result = peekNumber(); 
 | 
        if (result != PEEKED_NONE) { 
 | 
            return result; 
 | 
        } 
 | 
  
 | 
        if (!isLiteral(buffer[pos])) { 
 | 
            throw syntaxError("Expected value"); 
 | 
        } 
 | 
  
 | 
        checkLenient(); 
 | 
        return peeked = PEEKED_UNQUOTED; 
 | 
    } 
 | 
  
 | 
    private int peekKeyword() throws IOException { 
 | 
        // Figure out which keyword we're matching against by its first character. 
 | 
        char c = buffer[pos]; 
 | 
        String keyword; 
 | 
        String keywordUpper; 
 | 
        int peeking; 
 | 
        if (c == 't' || c == 'T') { 
 | 
            keyword = "true"; 
 | 
            keywordUpper = "TRUE"; 
 | 
            peeking = PEEKED_TRUE; 
 | 
        } else if (c == 'f' || c == 'F') { 
 | 
            keyword = "false"; 
 | 
            keywordUpper = "FALSE"; 
 | 
            peeking = PEEKED_FALSE; 
 | 
        } else if (c == 'n' || c == 'N') { 
 | 
            keyword = "null"; 
 | 
            keywordUpper = "NULL"; 
 | 
            peeking = PEEKED_NULL; 
 | 
        } else { 
 | 
            return PEEKED_NONE; 
 | 
        } 
 | 
  
 | 
        // Confirm that chars [1..length) match the keyword. 
 | 
        int length = keyword.length(); 
 | 
        for (int i = 1; i < length; i++) { 
 | 
            if (pos + i >= limit && !fillBuffer(i + 1)) { 
 | 
                return PEEKED_NONE; 
 | 
            } 
 | 
            c = buffer[pos + i]; 
 | 
            if (c != keyword.charAt(i) && c != keywordUpper.charAt(i)) { 
 | 
                return PEEKED_NONE; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        if ((pos + length < limit || fillBuffer(length + 1)) && isLiteral(buffer[pos + length])) { 
 | 
            return PEEKED_NONE; // Don't match trues, falsey or nullsoft! 
 | 
        } 
 | 
  
 | 
        // We've found the keyword followed either by EOF or by a non-literal character. 
 | 
        pos += length; 
 | 
        return peeked = peeking; 
 | 
    } 
 | 
  
 | 
    private int peekNumber() throws IOException { 
 | 
        // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field 
 | 
        // access. 
 | 
        char[] buffer = this.buffer; 
 | 
        int p = pos; 
 | 
        int l = limit; 
 | 
  
 | 
        long value = 0; // Negative to accommodate Long.MIN_VALUE more easily. 
 | 
        boolean negative = false; 
 | 
        boolean fitsInLong = true; 
 | 
        int last = NUMBER_CHAR_NONE; 
 | 
  
 | 
        int i = 0; 
 | 
  
 | 
        charactersOfNumber: for (; true; i++) { 
 | 
            if (p + i == l) { 
 | 
                if (i == buffer.length) { 
 | 
                    // Though this looks like a well-formed number, it's too long to continue 
 | 
                    // reading. Give up 
 | 
                    // and let the application handle this as an unquoted literal. 
 | 
                    return PEEKED_NONE; 
 | 
                } 
 | 
                if (!fillBuffer(i + 1)) { 
 | 
                    break; 
 | 
                } 
 | 
                p = pos; 
 | 
                l = limit; 
 | 
            } 
 | 
  
 | 
            char c = buffer[p + i]; 
 | 
            switch (c) { 
 | 
            case '-': 
 | 
                if (last == NUMBER_CHAR_NONE) { 
 | 
                    negative = true; 
 | 
                    last = NUMBER_CHAR_SIGN; 
 | 
                    continue; 
 | 
                } else if (last == NUMBER_CHAR_EXP_E) { 
 | 
                    last = NUMBER_CHAR_EXP_SIGN; 
 | 
                    continue; 
 | 
                } 
 | 
                return PEEKED_NONE; 
 | 
  
 | 
            case '+': 
 | 
                if (last == NUMBER_CHAR_EXP_E) { 
 | 
                    last = NUMBER_CHAR_EXP_SIGN; 
 | 
                    continue; 
 | 
                } 
 | 
                return PEEKED_NONE; 
 | 
  
 | 
            case 'e': 
 | 
            case 'E': 
 | 
                if (last == NUMBER_CHAR_DIGIT || last == NUMBER_CHAR_FRACTION_DIGIT) { 
 | 
                    last = NUMBER_CHAR_EXP_E; 
 | 
                    continue; 
 | 
                } 
 | 
                return PEEKED_NONE; 
 | 
  
 | 
            case '.': 
 | 
                if (last == NUMBER_CHAR_DIGIT) { 
 | 
                    last = NUMBER_CHAR_DECIMAL; 
 | 
                    continue; 
 | 
                } 
 | 
                return PEEKED_NONE; 
 | 
  
 | 
            default: 
 | 
                if (c < '0' || c > '9') { 
 | 
                    if (!isLiteral(c)) { 
 | 
                        break charactersOfNumber; 
 | 
                    } 
 | 
                    return PEEKED_NONE; 
 | 
                } 
 | 
                if (last == NUMBER_CHAR_SIGN || last == NUMBER_CHAR_NONE) { 
 | 
                    value = -(c - '0'); 
 | 
                    last = NUMBER_CHAR_DIGIT; 
 | 
                } else if (last == NUMBER_CHAR_DIGIT) { 
 | 
                    if (value == 0) { 
 | 
                        return PEEKED_NONE; // Leading '0' prefix is not allowed (since it could be octal). 
 | 
                    } 
 | 
                    long newValue = value * 10 - (c - '0'); 
 | 
                    fitsInLong &= value > MIN_INCOMPLETE_INTEGER || (value == MIN_INCOMPLETE_INTEGER && newValue < value); 
 | 
                    value = newValue; 
 | 
                } else if (last == NUMBER_CHAR_DECIMAL) { 
 | 
                    last = NUMBER_CHAR_FRACTION_DIGIT; 
 | 
                } else if (last == NUMBER_CHAR_EXP_E || last == NUMBER_CHAR_EXP_SIGN) { 
 | 
                    last = NUMBER_CHAR_EXP_DIGIT; 
 | 
                } 
 | 
            } 
 | 
        } 
 | 
  
 | 
        // We've read a complete number. Decide if it's a PEEKED_LONG or a 
 | 
        // PEEKED_NUMBER. 
 | 
        if (last == NUMBER_CHAR_DIGIT && fitsInLong && (value != Long.MIN_VALUE || negative)) { 
 | 
            peekedLong = negative ? value : -value; 
 | 
            pos += i; 
 | 
            return peeked = PEEKED_LONG; 
 | 
        } else if (last == NUMBER_CHAR_DIGIT || last == NUMBER_CHAR_FRACTION_DIGIT || last == NUMBER_CHAR_EXP_DIGIT) { 
 | 
            peekedNumberLength = i; 
 | 
            return peeked = PEEKED_NUMBER; 
 | 
        } else { 
 | 
            return PEEKED_NONE; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    private boolean isLiteral(char c) throws IOException { 
 | 
        switch (c) { 
 | 
        case '/': 
 | 
        case '\\': 
 | 
        case ';': 
 | 
        case '#': 
 | 
        case '=': 
 | 
            checkLenient(); // fall-through 
 | 
        case '{': 
 | 
        case '}': 
 | 
        case '[': 
 | 
        case ']': 
 | 
        case ':': 
 | 
        case ',': 
 | 
        case ' ': 
 | 
        case '\t': 
 | 
        case '\f': 
 | 
        case '\r': 
 | 
        case '\n': 
 | 
            return false; 
 | 
        default: 
 | 
            return true; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Returns the next token, a {@link com.google.gson.stream.JsonToken#NAME 
 | 
     * property name}, and consumes it. 
 | 
     * 
 | 
     * @throws java.io.IOException 
 | 
     *             if the next token in the stream is not a property name. 
 | 
     */ 
 | 
    public String nextName() throws IOException { 
 | 
        int p = peeked; 
 | 
        if (p == PEEKED_NONE) { 
 | 
            p = doPeek(); 
 | 
        } 
 | 
        String result; 
 | 
        if (p == PEEKED_UNQUOTED_NAME) { 
 | 
            result = nextUnquotedValue(); 
 | 
        } else if (p == PEEKED_SINGLE_QUOTED_NAME) { 
 | 
            result = nextQuotedValue('\''); 
 | 
        } else if (p == PEEKED_DOUBLE_QUOTED_NAME) { 
 | 
            result = nextQuotedValue('"'); 
 | 
        } else { 
 | 
            throw new IllegalStateException("Expected a name but was " + peek() + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); 
 | 
        } 
 | 
        peeked = PEEKED_NONE; 
 | 
        pathNames[stackSize - 1] = result; 
 | 
        return result; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Returns the {@link com.google.gson.stream.JsonToken#STRING string} value of 
 | 
     * the next token, consuming it. If the next token is a number, this method will 
 | 
     * return its string form. 
 | 
     * 
 | 
     * @throws IllegalStateException 
 | 
     *             if the next token is not a string or if this reader is closed. 
 | 
     */ 
 | 
    public String nextString() throws IOException { 
 | 
        int p = peeked; 
 | 
        if (p == PEEKED_NONE) { 
 | 
            p = doPeek(); 
 | 
        } 
 | 
        String result; 
 | 
        if (p == PEEKED_UNQUOTED) { 
 | 
            result = nextUnquotedValue(); 
 | 
        } else if (p == PEEKED_SINGLE_QUOTED) { 
 | 
            result = nextQuotedValue('\''); 
 | 
        } else if (p == PEEKED_DOUBLE_QUOTED) { 
 | 
            result = nextQuotedValue('"'); 
 | 
        } else if (p == PEEKED_BUFFERED) { 
 | 
            result = peekedString; 
 | 
            peekedString = null; 
 | 
        } else if (p == PEEKED_LONG) { 
 | 
            result = Long.toString(peekedLong); 
 | 
        } else if (p == PEEKED_NUMBER) { 
 | 
            result = new String(buffer, pos, peekedNumberLength); 
 | 
            pos += peekedNumberLength; 
 | 
        } else { 
 | 
            throw new IllegalStateException("Expected a string but was " + peek() + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); 
 | 
        } 
 | 
        peeked = PEEKED_NONE; 
 | 
        pathIndices[stackSize - 1]++; 
 | 
        return result; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Returns the {@link com.google.gson.stream.JsonToken#BOOLEAN boolean} value of 
 | 
     * the next token, consuming it. 
 | 
     * 
 | 
     * @throws IllegalStateException 
 | 
     *             if the next token is not a boolean or if this reader is closed. 
 | 
     */ 
 | 
    public boolean nextBoolean() throws IOException { 
 | 
        int p = peeked; 
 | 
        if (p == PEEKED_NONE) { 
 | 
            p = doPeek(); 
 | 
        } 
 | 
        if (p == PEEKED_TRUE) { 
 | 
            peeked = PEEKED_NONE; 
 | 
            pathIndices[stackSize - 1]++; 
 | 
            return true; 
 | 
        } else if (p == PEEKED_FALSE) { 
 | 
            peeked = PEEKED_NONE; 
 | 
            pathIndices[stackSize - 1]++; 
 | 
            return false; 
 | 
        } 
 | 
        throw new IllegalStateException("Expected a boolean but was " + peek() + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Consumes the next token from the JSON stream and asserts that it is a literal 
 | 
     * null. 
 | 
     * 
 | 
     * @throws IllegalStateException 
 | 
     *             if the next token is not null or if this reader is closed. 
 | 
     */ 
 | 
    public void nextNull() throws IOException { 
 | 
        int p = peeked; 
 | 
        if (p == PEEKED_NONE) { 
 | 
            p = doPeek(); 
 | 
        } 
 | 
        if (p == PEEKED_NULL) { 
 | 
            peeked = PEEKED_NONE; 
 | 
            pathIndices[stackSize - 1]++; 
 | 
        } else { 
 | 
            throw new IllegalStateException("Expected null but was " + peek() + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Returns the {@link com.google.gson.stream.JsonToken#NUMBER double} value of 
 | 
     * the next token, consuming it. If the next token is a string, this method will 
 | 
     * attempt to parse it as a double using {@link Double#parseDouble(String)}. 
 | 
     * 
 | 
     * @throws IllegalStateException 
 | 
     *             if the next token is not a literal value. 
 | 
     * @throws NumberFormatException 
 | 
     *             if the next literal value cannot be parsed as a double, or is 
 | 
     *             non-finite. 
 | 
     */ 
 | 
    public double nextDouble() throws IOException { 
 | 
        int p = peeked; 
 | 
        if (p == PEEKED_NONE) { 
 | 
            p = doPeek(); 
 | 
        } 
 | 
  
 | 
        if (p == PEEKED_LONG) { 
 | 
            peeked = PEEKED_NONE; 
 | 
            pathIndices[stackSize - 1]++; 
 | 
            return (double) peekedLong; 
 | 
        } 
 | 
  
 | 
        if (p == PEEKED_NUMBER) { 
 | 
            peekedString = new String(buffer, pos, peekedNumberLength); 
 | 
            pos += peekedNumberLength; 
 | 
        } else if (p == PEEKED_SINGLE_QUOTED || p == PEEKED_DOUBLE_QUOTED) { 
 | 
            peekedString = nextQuotedValue(p == PEEKED_SINGLE_QUOTED ? '\'' : '"'); 
 | 
        } else if (p == PEEKED_UNQUOTED) { 
 | 
            peekedString = nextUnquotedValue(); 
 | 
        } else if (p != PEEKED_BUFFERED) { 
 | 
            throw new IllegalStateException("Expected a double but was " + peek() + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); 
 | 
        } 
 | 
  
 | 
        peeked = PEEKED_BUFFERED; 
 | 
        double result = Double.parseDouble(peekedString); // don't catch this NumberFormatException. 
 | 
        if (!lenient && (Double.isNaN(result) || Double.isInfinite(result))) { 
 | 
            throw new MalformedJsonException("JSON forbids NaN and infinities: " + result + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); 
 | 
        } 
 | 
        peekedString = null; 
 | 
        peeked = PEEKED_NONE; 
 | 
        pathIndices[stackSize - 1]++; 
 | 
        return result; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Returns the {@link com.google.gson.stream.JsonToken#NUMBER long} value of the 
 | 
     * next token, consuming it. If the next token is a string, this method will 
 | 
     * attempt to parse it as a long. If the next token's numeric value cannot be 
 | 
     * exactly represented by a Java {@code long}, this method throws. 
 | 
     * 
 | 
     * @throws IllegalStateException 
 | 
     *             if the next token is not a literal value. 
 | 
     * @throws NumberFormatException 
 | 
     *             if the next literal value cannot be parsed as a number, or 
 | 
     *             exactly represented as a long. 
 | 
     */ 
 | 
    public long nextLong() throws IOException { 
 | 
        int p = peeked; 
 | 
        if (p == PEEKED_NONE) { 
 | 
            p = doPeek(); 
 | 
        } 
 | 
  
 | 
        if (p == PEEKED_LONG) { 
 | 
            peeked = PEEKED_NONE; 
 | 
            pathIndices[stackSize - 1]++; 
 | 
            return peekedLong; 
 | 
        } 
 | 
  
 | 
        if (p == PEEKED_NUMBER) { 
 | 
            peekedString = new String(buffer, pos, peekedNumberLength); 
 | 
            pos += peekedNumberLength; 
 | 
        } else if (p == PEEKED_SINGLE_QUOTED || p == PEEKED_DOUBLE_QUOTED) { 
 | 
            peekedString = nextQuotedValue(p == PEEKED_SINGLE_QUOTED ? '\'' : '"'); 
 | 
            try { 
 | 
                long result = Long.parseLong(peekedString); 
 | 
                peeked = PEEKED_NONE; 
 | 
                pathIndices[stackSize - 1]++; 
 | 
                return result; 
 | 
            } catch (NumberFormatException ignored) { 
 | 
                // Fall back to parse as a double below. 
 | 
            } 
 | 
        } else { 
 | 
            throw new IllegalStateException("Expected a long but was " + peek() + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); 
 | 
        } 
 | 
  
 | 
        peeked = PEEKED_BUFFERED; 
 | 
        double asDouble = Double.parseDouble(peekedString); // don't catch this NumberFormatException. 
 | 
        long result = (long) asDouble; 
 | 
        if (result != asDouble) { // Make sure no precision was lost casting to 'long'. 
 | 
            throw new NumberFormatException("Expected a long but was " + peekedString + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); 
 | 
        } 
 | 
        peekedString = null; 
 | 
        peeked = PEEKED_NONE; 
 | 
        pathIndices[stackSize - 1]++; 
 | 
        return result; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Returns the string up to but not including {@code quote}, unescaping any 
 | 
     * character escape sequences encountered along the way. The opening quote 
 | 
     * should have already been read. This consumes the closing quote, but does not 
 | 
     * include it in the returned string. 
 | 
     * 
 | 
     * @param quote 
 | 
     *            either ' or ". 
 | 
     * @throws NumberFormatException 
 | 
     *             if any unicode escape sequences are malformed. 
 | 
     */ 
 | 
    private String nextQuotedValue(char quote) throws IOException { 
 | 
        // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field 
 | 
        // access. 
 | 
        char[] buffer = this.buffer; 
 | 
        StringBuilder builder = new StringBuilder(); 
 | 
        while (true) { 
 | 
            int p = pos; 
 | 
            int l = limit; 
 | 
            /* the index of the first character not yet appended to the builder. */ 
 | 
            int start = p; 
 | 
            while (p < l) { 
 | 
                int c = buffer[p++]; 
 | 
  
 | 
                if (c == quote) { 
 | 
                    pos = p; 
 | 
                    builder.append(buffer, start, p - start - 1); 
 | 
                    return builder.toString(); 
 | 
                } else if (c == '\\') { 
 | 
                    pos = p; 
 | 
                    builder.append(buffer, start, p - start - 1); 
 | 
                    builder.append(readEscapeCharacter()); 
 | 
                    p = pos; 
 | 
                    l = limit; 
 | 
                    start = p; 
 | 
                } else if (c == '\n') { 
 | 
                    lineNumber++; 
 | 
                    lineStart = p; 
 | 
                } 
 | 
            } 
 | 
  
 | 
            builder.append(buffer, start, p - start); 
 | 
            pos = p; 
 | 
            if (!fillBuffer(1)) { 
 | 
                throw syntaxError("Unterminated string"); 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Returns an unquoted value as a string. 
 | 
     */ 
 | 
    @SuppressWarnings("fallthrough") 
 | 
    private String nextUnquotedValue() throws IOException { 
 | 
        StringBuilder builder = null; 
 | 
        int i = 0; 
 | 
  
 | 
        findNonLiteralCharacter: while (true) { 
 | 
            for (; pos + i < limit; i++) { 
 | 
                switch (buffer[pos + i]) { 
 | 
                case '/': 
 | 
                case '\\': 
 | 
                case ';': 
 | 
                case '#': 
 | 
                case '=': 
 | 
                    checkLenient(); // fall-through 
 | 
                case '{': 
 | 
                case '}': 
 | 
                case '[': 
 | 
                case ']': 
 | 
                case ':': 
 | 
                case ',': 
 | 
                case ' ': 
 | 
                case '\t': 
 | 
                case '\f': 
 | 
                case '\r': 
 | 
                case '\n': 
 | 
                    break findNonLiteralCharacter; 
 | 
                } 
 | 
            } 
 | 
  
 | 
            // Attempt to load the entire literal into the buffer at once. 
 | 
            if (i < buffer.length) { 
 | 
                if (fillBuffer(i + 1)) { 
 | 
                    continue; 
 | 
                } else { 
 | 
                    break; 
 | 
                } 
 | 
            } 
 | 
  
 | 
            // use a StringBuilder when the value is too long. This is too long to be a 
 | 
            // number! 
 | 
            if (builder == null) { 
 | 
                builder = new StringBuilder(); 
 | 
            } 
 | 
            builder.append(buffer, pos, i); 
 | 
            pos += i; 
 | 
            i = 0; 
 | 
            if (!fillBuffer(1)) { 
 | 
                break; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        String result; 
 | 
        if (builder == null) { 
 | 
            result = new String(buffer, pos, i); 
 | 
        } else { 
 | 
            builder.append(buffer, pos, i); 
 | 
            result = builder.toString(); 
 | 
        } 
 | 
        pos += i; 
 | 
        return result; 
 | 
    } 
 | 
  
 | 
    private void skipQuotedValue(char quote) throws IOException { 
 | 
        // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field 
 | 
        // access. 
 | 
        char[] buffer = this.buffer; 
 | 
        do { 
 | 
            int p = pos; 
 | 
            int l = limit; 
 | 
            /* the index of the first character not yet appended to the builder. */ 
 | 
            while (p < l) { 
 | 
                int c = buffer[p++]; 
 | 
                if (c == quote) { 
 | 
                    pos = p; 
 | 
                    return; 
 | 
                } else if (c == '\\') { 
 | 
                    pos = p; 
 | 
                    readEscapeCharacter(); 
 | 
                    p = pos; 
 | 
                    l = limit; 
 | 
                } else if (c == '\n') { 
 | 
                    lineNumber++; 
 | 
                    lineStart = p; 
 | 
                } 
 | 
            } 
 | 
            pos = p; 
 | 
        } while (fillBuffer(1)); 
 | 
        throw syntaxError("Unterminated string"); 
 | 
    } 
 | 
  
 | 
    private void skipUnquotedValue() throws IOException { 
 | 
        do { 
 | 
            int i = 0; 
 | 
            for (; pos + i < limit; i++) { 
 | 
                switch (buffer[pos + i]) { 
 | 
                case '/': 
 | 
                case '\\': 
 | 
                case ';': 
 | 
                case '#': 
 | 
                case '=': 
 | 
                    checkLenient(); // fall-through 
 | 
                case '{': 
 | 
                case '}': 
 | 
                case '[': 
 | 
                case ']': 
 | 
                case ':': 
 | 
                case ',': 
 | 
                case ' ': 
 | 
                case '\t': 
 | 
                case '\f': 
 | 
                case '\r': 
 | 
                case '\n': 
 | 
                    pos += i; 
 | 
                    return; 
 | 
                } 
 | 
            } 
 | 
            pos += i; 
 | 
        } while (fillBuffer(1)); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Returns the {@link com.google.gson.stream.JsonToken#NUMBER int} value of the 
 | 
     * next token, consuming it. If the next token is a string, this method will 
 | 
     * attempt to parse it as an int. If the next token's numeric value cannot be 
 | 
     * exactly represented by a Java {@code int}, this method throws. 
 | 
     * 
 | 
     * @throws IllegalStateException 
 | 
     *             if the next token is not a literal value. 
 | 
     * @throws NumberFormatException 
 | 
     *             if the next literal value cannot be parsed as a number, or 
 | 
     *             exactly represented as an int. 
 | 
     */ 
 | 
    public int nextInt() throws IOException { 
 | 
        int p = peeked; 
 | 
        if (p == PEEKED_NONE) { 
 | 
            p = doPeek(); 
 | 
        } 
 | 
  
 | 
        int result; 
 | 
        if (p == PEEKED_LONG) { 
 | 
            result = (int) peekedLong; 
 | 
            if (peekedLong != result) { // Make sure no precision was lost casting to 'int'. 
 | 
                throw new NumberFormatException("Expected an int but was " + peekedLong + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); 
 | 
            } 
 | 
            peeked = PEEKED_NONE; 
 | 
            pathIndices[stackSize - 1]++; 
 | 
            return result; 
 | 
        } 
 | 
  
 | 
        if (p == PEEKED_NUMBER) { 
 | 
            peekedString = new String(buffer, pos, peekedNumberLength); 
 | 
            pos += peekedNumberLength; 
 | 
        } else if (p == PEEKED_SINGLE_QUOTED || p == PEEKED_DOUBLE_QUOTED) { 
 | 
            peekedString = nextQuotedValue(p == PEEKED_SINGLE_QUOTED ? '\'' : '"'); 
 | 
            try { 
 | 
                result = Integer.parseInt(peekedString); 
 | 
                peeked = PEEKED_NONE; 
 | 
                pathIndices[stackSize - 1]++; 
 | 
                return result; 
 | 
            } catch (NumberFormatException ignored) { 
 | 
                // Fall back to parse as a double below. 
 | 
            } 
 | 
        } else { 
 | 
            throw new IllegalStateException("Expected an int but was " + peek() + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); 
 | 
        } 
 | 
  
 | 
        peeked = PEEKED_BUFFERED; 
 | 
        double asDouble = Double.parseDouble(peekedString); // don't catch this NumberFormatException. 
 | 
        result = (int) asDouble; 
 | 
        if (result != asDouble) { // Make sure no precision was lost casting to 'int'. 
 | 
            throw new NumberFormatException("Expected an int but was " + peekedString + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); 
 | 
        } 
 | 
        peekedString = null; 
 | 
        peeked = PEEKED_NONE; 
 | 
        pathIndices[stackSize - 1]++; 
 | 
        return result; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Closes this JSON reader and the underlying {@link java.io.Reader}. 
 | 
     */ 
 | 
    public void close() throws IOException { 
 | 
        peeked = PEEKED_NONE; 
 | 
        stack[0] = JsonScope.CLOSED; 
 | 
        stackSize = 1; 
 | 
        in.close(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Skips the next value recursively. If it is an object or array, all nested 
 | 
     * elements are skipped. This method is intended for use when the JSON token 
 | 
     * stream contains unrecognized or unhandled values. 
 | 
     */ 
 | 
    public void skipValue() throws IOException { 
 | 
        int count = 0; 
 | 
        do { 
 | 
            int p = peeked; 
 | 
            if (p == PEEKED_NONE) { 
 | 
                p = doPeek(); 
 | 
            } 
 | 
  
 | 
            if (p == PEEKED_BEGIN_ARRAY) { 
 | 
                push(JsonScope.EMPTY_ARRAY); 
 | 
                count++; 
 | 
            } else if (p == PEEKED_BEGIN_OBJECT) { 
 | 
                push(JsonScope.EMPTY_OBJECT); 
 | 
                count++; 
 | 
            } else if (p == PEEKED_END_ARRAY) { 
 | 
                stackSize--; 
 | 
                count--; 
 | 
            } else if (p == PEEKED_END_OBJECT) { 
 | 
                stackSize--; 
 | 
                count--; 
 | 
            } else if (p == PEEKED_UNQUOTED_NAME || p == PEEKED_UNQUOTED) { 
 | 
                skipUnquotedValue(); 
 | 
            } else if (p == PEEKED_SINGLE_QUOTED || p == PEEKED_SINGLE_QUOTED_NAME) { 
 | 
                skipQuotedValue('\''); 
 | 
            } else if (p == PEEKED_DOUBLE_QUOTED || p == PEEKED_DOUBLE_QUOTED_NAME) { 
 | 
                skipQuotedValue('"'); 
 | 
            } else if (p == PEEKED_NUMBER) { 
 | 
                pos += peekedNumberLength; 
 | 
            } 
 | 
            peeked = PEEKED_NONE; 
 | 
        } while (count != 0); 
 | 
  
 | 
        pathIndices[stackSize - 1]++; 
 | 
        pathNames[stackSize - 1] = "null"; 
 | 
    } 
 | 
  
 | 
    private void push(int newTop) { 
 | 
        if (stackSize == stack.length) { 
 | 
            int[] newStack = new int[stackSize * 2]; 
 | 
            int[] newPathIndices = new int[stackSize * 2]; 
 | 
            String[] newPathNames = new String[stackSize * 2]; 
 | 
            System.arraycopy(stack, 0, newStack, 0, stackSize); 
 | 
            System.arraycopy(pathIndices, 0, newPathIndices, 0, stackSize); 
 | 
            System.arraycopy(pathNames, 0, newPathNames, 0, stackSize); 
 | 
            stack = newStack; 
 | 
            pathIndices = newPathIndices; 
 | 
            pathNames = newPathNames; 
 | 
        } 
 | 
        stack[stackSize++] = newTop; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Returns true once {@code limit - pos >= minimum}. If the data is exhausted 
 | 
     * before that many characters are available, this returns false. 
 | 
     */ 
 | 
    private boolean fillBuffer(int minimum) throws IOException { 
 | 
        char[] buffer = this.buffer; 
 | 
        lineStart -= pos; 
 | 
        if (limit != pos) { 
 | 
            limit -= pos; 
 | 
            System.arraycopy(buffer, pos, buffer, 0, limit); 
 | 
        } else { 
 | 
            limit = 0; 
 | 
        } 
 | 
  
 | 
        pos = 0; 
 | 
        int total; 
 | 
        while ((total = in.read(buffer, limit, buffer.length - limit)) != -1) { 
 | 
            limit += total; 
 | 
  
 | 
            // if this is the first read, consume an optional byte order mark (BOM) if it 
 | 
            // exists 
 | 
            if (lineNumber == 0 && lineStart == 0 && limit > 0 && buffer[0] == '\ufeff') { 
 | 
                pos++; 
 | 
                lineStart++; 
 | 
                minimum++; 
 | 
            } 
 | 
  
 | 
            if (limit >= minimum) { 
 | 
                return true; 
 | 
            } 
 | 
        } 
 | 
        return false; 
 | 
    } 
 | 
  
 | 
    int getLineNumber() { 
 | 
        return lineNumber + 1; 
 | 
    } 
 | 
  
 | 
    int getColumnNumber() { 
 | 
        return pos - lineStart + 1; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Returns the next character in the stream that is neither whitespace nor a 
 | 
     * part of a comment. When this returns, the returned character is always at 
 | 
     * {@code buffer[pos-1]}; this means the caller can always push back the 
 | 
     * returned character by decrementing {@code pos}. 
 | 
     */ 
 | 
    private int nextNonWhitespace(boolean throwOnEof) throws IOException { 
 | 
        /* 
 | 
         * This code uses ugly local variables 'p' and 'l' representing the 'pos' and 
 | 
         * 'limit' fields respectively. Using locals rather than fields saves a few 
 | 
         * field reads for each whitespace character in a pretty-printed document, 
 | 
         * resulting in a 5% speedup. We need to flush 'p' to its field before any 
 | 
         * (potentially indirect) call to fillBuffer() and reread both 'p' and 'l' after 
 | 
         * any (potentially indirect) call to the same method. 
 | 
         */ 
 | 
        char[] buffer = this.buffer; 
 | 
        int p = pos; 
 | 
        int l = limit; 
 | 
        while (true) { 
 | 
            if (p == l) { 
 | 
                pos = p; 
 | 
                if (!fillBuffer(1)) { 
 | 
                    break; 
 | 
                } 
 | 
                p = pos; 
 | 
                l = limit; 
 | 
            } 
 | 
  
 | 
            int c = buffer[p++]; 
 | 
            if (c == '\n') { 
 | 
                lineNumber++; 
 | 
                lineStart = p; 
 | 
                continue; 
 | 
            } else if (c == ' ' || c == '\r' || c == '\t') { 
 | 
                continue; 
 | 
            } 
 | 
  
 | 
            if (c == '/') { 
 | 
                pos = p; 
 | 
                if (p == l) { 
 | 
                    pos--; // push back '/' so it's still in the buffer when this method returns 
 | 
                    boolean charsLoaded = fillBuffer(2); 
 | 
                    pos++; // consume the '/' again 
 | 
                    if (!charsLoaded) { 
 | 
                        return c; 
 | 
                    } 
 | 
                } 
 | 
  
 | 
                checkLenient(); 
 | 
                char peek = buffer[pos]; 
 | 
                switch (peek) { 
 | 
                case '*': 
 | 
                    // skip a /* c-style comment */ 
 | 
                    pos++; 
 | 
                    if (!skipTo("*/")) { 
 | 
                        throw syntaxError("Unterminated comment"); 
 | 
                    } 
 | 
                    p = pos + 2; 
 | 
                    l = limit; 
 | 
                    continue; 
 | 
  
 | 
                case '/': 
 | 
                    // skip a // end-of-line comment 
 | 
                    pos++; 
 | 
                    skipToEndOfLine(); 
 | 
                    p = pos; 
 | 
                    l = limit; 
 | 
                    continue; 
 | 
  
 | 
                default: 
 | 
                    return c; 
 | 
                } 
 | 
            } else if (c == '#') { 
 | 
                pos = p; 
 | 
                /* 
 | 
                 * Skip a # hash end-of-line comment. The JSON RFC doesn't specify this 
 | 
                 * behaviour, but it's required to parse existing documents. See 
 | 
                 * http://b/2571423. 
 | 
                 */ 
 | 
                checkLenient(); 
 | 
                skipToEndOfLine(); 
 | 
                p = pos; 
 | 
                l = limit; 
 | 
            } else { 
 | 
                pos = p; 
 | 
                return c; 
 | 
            } 
 | 
        } 
 | 
        if (throwOnEof) { 
 | 
            throw new EOFException("End of input" + " at line " + getLineNumber() + " column " + getColumnNumber()); 
 | 
        } else { 
 | 
            return -1; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    private void checkLenient() throws IOException { 
 | 
        if (!lenient) { 
 | 
            throw syntaxError("Use JsonReader.setLenient(true) to accept malformed JSON"); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Advances the position until after the next newline character. If the line is 
 | 
     * terminated by "\r\n", the '\n' must be consumed as whitespace by the caller. 
 | 
     */ 
 | 
    private void skipToEndOfLine() throws IOException { 
 | 
        while (pos < limit || fillBuffer(1)) { 
 | 
            char c = buffer[pos++]; 
 | 
            if (c == '\n') { 
 | 
                lineNumber++; 
 | 
                lineStart = pos; 
 | 
                break; 
 | 
            } else if (c == '\r') { 
 | 
                break; 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * @param toFind 
 | 
     *            a string to search for. Must not contain a newline. 
 | 
     */ 
 | 
    private boolean skipTo(String toFind) throws IOException { 
 | 
        outer: for (; pos + toFind.length() <= limit || fillBuffer(toFind.length()); pos++) { 
 | 
            if (buffer[pos] == '\n') { 
 | 
                lineNumber++; 
 | 
                lineStart = pos + 1; 
 | 
                continue; 
 | 
            } 
 | 
            for (int c = 0; c < toFind.length(); c++) { 
 | 
                if (buffer[pos + c] != toFind.charAt(c)) { 
 | 
                    continue outer; 
 | 
                } 
 | 
            } 
 | 
            return true; 
 | 
        } 
 | 
        return false; 
 | 
    } 
 | 
  
 | 
    @Override 
 | 
    public String toString() { 
 | 
        return getClass().getSimpleName() + " at line " + getLineNumber() + " column " + getColumnNumber(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Returns a <a href="http://goessner.net/articles/JsonPath/">JsonPath</a> to 
 | 
     * the current location in the JSON value. 
 | 
     */ 
 | 
    public String getPath() { 
 | 
        StringBuilder result = new StringBuilder().append('$'); 
 | 
        for (int i = 0, size = stackSize; i < size; i++) { 
 | 
            switch (stack[i]) { 
 | 
            case JsonScope.EMPTY_ARRAY: 
 | 
            case JsonScope.NONEMPTY_ARRAY: 
 | 
                result.append('[').append(pathIndices[i]).append(']'); 
 | 
                break; 
 | 
  
 | 
            case JsonScope.EMPTY_OBJECT: 
 | 
            case JsonScope.DANGLING_NAME: 
 | 
            case JsonScope.NONEMPTY_OBJECT: 
 | 
                result.append('.'); 
 | 
                if (pathNames[i] != null) { 
 | 
                    result.append(pathNames[i]); 
 | 
                } 
 | 
                break; 
 | 
  
 | 
            case JsonScope.NONEMPTY_DOCUMENT: 
 | 
            case JsonScope.EMPTY_DOCUMENT: 
 | 
            case JsonScope.CLOSED: 
 | 
                break; 
 | 
            } 
 | 
        } 
 | 
        return result.toString(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Unescapes the character identified by the character or characters that 
 | 
     * immediately follow a backslash. The backslash '\' should have already been 
 | 
     * read. This supports both unicode escapes "u000A" and two-character escapes 
 | 
     * "\n". 
 | 
     * 
 | 
     * @throws NumberFormatException 
 | 
     *             if any unicode escape sequences are malformed. 
 | 
     */ 
 | 
    private char readEscapeCharacter() throws IOException { 
 | 
        if (pos == limit && !fillBuffer(1)) { 
 | 
            throw syntaxError("Unterminated escape sequence"); 
 | 
        } 
 | 
  
 | 
        char escaped = buffer[pos++]; 
 | 
        switch (escaped) { 
 | 
        case 'u': 
 | 
            if (pos + 4 > limit && !fillBuffer(4)) { 
 | 
                throw syntaxError("Unterminated escape sequence"); 
 | 
            } 
 | 
            // Equivalent to Integer.parseInt(stringPool.get(buffer, pos, 4), 16); 
 | 
            char result = 0; 
 | 
            for (int i = pos, end = i + 4; i < end; i++) { 
 | 
                char c = buffer[i]; 
 | 
                result <<= 4; 
 | 
                if (c >= '0' && c <= '9') { 
 | 
                    result += (c - '0'); 
 | 
                } else if (c >= 'a' && c <= 'f') { 
 | 
                    result += (c - 'a' + 10); 
 | 
                } else if (c >= 'A' && c <= 'F') { 
 | 
                    result += (c - 'A' + 10); 
 | 
                } else { 
 | 
                    throw new NumberFormatException("\\u" + new String(buffer, pos, 4)); 
 | 
                } 
 | 
            } 
 | 
            pos += 4; 
 | 
            return result; 
 | 
  
 | 
        case 't': 
 | 
            return '\t'; 
 | 
  
 | 
        case 'b': 
 | 
            return '\b'; 
 | 
  
 | 
        case 'n': 
 | 
            return '\n'; 
 | 
  
 | 
        case 'r': 
 | 
            return '\r'; 
 | 
  
 | 
        case 'f': 
 | 
            return '\f'; 
 | 
  
 | 
        case '\n': 
 | 
            lineNumber++; 
 | 
            lineStart = pos; 
 | 
            // fall-through 
 | 
  
 | 
        case '\'': 
 | 
        case '"': 
 | 
        case '\\': 
 | 
        default: 
 | 
            return escaped; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Throws a new IO exception with the given message and a context snippet with 
 | 
     * this reader's content. 
 | 
     */ 
 | 
    private IOException syntaxError(String message) throws IOException { 
 | 
        throw new MalformedJsonException(message + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Consumes the non-execute prefix if it exists. 
 | 
     */ 
 | 
    private void consumeNonExecutePrefix() throws IOException { 
 | 
        // fast forward through the leading whitespace 
 | 
        nextNonWhitespace(true); 
 | 
        pos--; 
 | 
  
 | 
        if (pos + NON_EXECUTE_PREFIX.length > limit && !fillBuffer(NON_EXECUTE_PREFIX.length)) { 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        for (int i = 0; i < NON_EXECUTE_PREFIX.length; i++) { 
 | 
            if (buffer[pos + i] != NON_EXECUTE_PREFIX[i]) { 
 | 
                return; // not a security token! 
 | 
            } 
 | 
        } 
 | 
  
 | 
        // we consumed a security token! 
 | 
        pos += NON_EXECUTE_PREFIX.length; 
 | 
    } 
 | 
  
 | 
    static { 
 | 
        JsonReaderInternalAccess.INSTANCE = new JsonReaderInternalAccess() { 
 | 
            @Override 
 | 
            public void promoteNameToValue(JsonReader reader) throws IOException { 
 | 
                if (reader instanceof JsonTreeReader) { 
 | 
                    ((JsonTreeReader) reader).promoteNameToValue(); 
 | 
                    return; 
 | 
                } 
 | 
                int p = reader.peeked; 
 | 
                if (p == PEEKED_NONE) { 
 | 
                    p = reader.doPeek(); 
 | 
                } 
 | 
                if (p == PEEKED_DOUBLE_QUOTED_NAME) { 
 | 
                    reader.peeked = PEEKED_DOUBLE_QUOTED; 
 | 
                } else if (p == PEEKED_SINGLE_QUOTED_NAME) { 
 | 
                    reader.peeked = PEEKED_SINGLE_QUOTED; 
 | 
                } else if (p == PEEKED_UNQUOTED_NAME) { 
 | 
                    reader.peeked = PEEKED_UNQUOTED; 
 | 
                } else { 
 | 
                    throw new IllegalStateException( 
 | 
                            "Expected a name but was " + reader.peek() + " " + " at line " + reader.getLineNumber() + " column " + reader.getColumnNumber() + " path " + reader.getPath()); 
 | 
                } 
 | 
            } 
 | 
        }; 
 | 
    } 
 | 
} 
 |