+/*
+ * 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 com.google.gson.stream;
+
+import com.google.gson.internal.JsonReaderInternalAccess;
+import com.google.gson.internal.bind.JsonTreeReader;
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * Reads a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</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;
+
+ private 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;
+ }
+
+ /**
+ * 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 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);
+ peeked = PEEKED_NONE;
+ } else {
+ throw new IllegalStateException("Expected BEGIN_ARRAY but was " + peek()
+ + " at line " + getLineNumber() + " column " + getColumnNumber());
+ }
+ }
+
+ /**
+ * 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--;
+ peeked = PEEKED_NONE;
+ } else {
+ throw new IllegalStateException("Expected END_ARRAY but was " + peek()
+ + " at line " + getLineNumber() + " column " + getColumnNumber());
+ }
+ }
+
+ /**
+ * 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());
+ }
+ }
+
+ /**
+ * 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--;
+ peeked = PEEKED_NONE;
+ } else {
+ throw new IllegalStateException("Expected END_OBJECT but was " + peek()
+ + " at line " + getLineNumber() + " column " + getColumnNumber());
+ }
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+ private 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 '"':
+ if (stackSize == 1) {
+ checkLenient();
+ }
+ 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.
+ }
+
+ if (stackSize == 1) {
+ checkLenient(); // Top-level value isn't an array or an object.
+ }
+
+ 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());
+ }
+ peeked = PEEKED_NONE;
+ 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());
+ }
+ peeked = PEEKED_NONE;
+ 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;
+ return true;
+ } else if (p == PEEKED_FALSE) {
+ peeked = PEEKED_NONE;
+ return false;
+ }
+ throw new IllegalStateException("Expected a boolean but was " + peek()
+ + " at line " + getLineNumber() + " column " + getColumnNumber());
+ }
+
+ /**
+ * 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;
+ } else {
+ throw new IllegalStateException("Expected null but was " + peek()
+ + " at line " + getLineNumber() + " column " + getColumnNumber());
+ }
+ }
+
+ /**
+ * 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;
+ 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());
+ }
+
+ 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());
+ }
+ peekedString = null;
+ peeked = PEEKED_NONE;
+ 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;
+ 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;
+ 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());
+ }
+
+ 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());
+ }
+ peekedString = null;
+ peeked = PEEKED_NONE;
+ 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());
+ }
+ peeked = PEEKED_NONE;
+ 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;
+ 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());
+ }
+
+ 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());
+ }
+ peekedString = null;
+ peeked = PEEKED_NONE;
+ 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);
+ }
+
+ private void push(int newTop) {
+ if (stackSize == stack.length) {
+ int[] newStack = new int[stackSize * 2];
+ System.arraycopy(stack, 0, newStack, 0, stackSize);
+ stack = newStack;
+ }
+ 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;
+ }
+
+ private int getLineNumber() {
+ return lineNumber + 1;
+ }
+
+ private 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();
+ }
+
+ /**
+ * 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());
+ }
+
+ /**
+ * 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());
+ }
+ }
+ };
+ }
+}