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