/*
 * Copyright 2013 Nicolas Morel
 *
 * 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.github.nmorel.gwtjackson.client.stream;

import java.math.BigInteger;
import java.util.Arrays;

import com.github.nmorel.gwtjackson.client.GwtJacksonTestCase;
import com.github.nmorel.gwtjackson.client.exception.JsonDeserializationException;
import com.github.nmorel.gwtjackson.client.stream.impl.MalformedJsonException;
import com.github.nmorel.gwtjackson.client.stream.impl.StringReader;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArrayMixed;
import com.google.gwt.core.client.JsArrayString;

import static com.github.nmorel.gwtjackson.client.stream.JsonToken.BEGIN_ARRAY;
import static com.github.nmorel.gwtjackson.client.stream.JsonToken.BEGIN_OBJECT;
import static com.github.nmorel.gwtjackson.client.stream.JsonToken.BOOLEAN;
import static com.github.nmorel.gwtjackson.client.stream.JsonToken.END_ARRAY;
import static com.github.nmorel.gwtjackson.client.stream.JsonToken.END_OBJECT;
import static com.github.nmorel.gwtjackson.client.stream.JsonToken.NAME;
import static com.github.nmorel.gwtjackson.client.stream.JsonToken.NULL;
import static com.github.nmorel.gwtjackson.client.stream.JsonToken.NUMBER;
import static com.github.nmorel.gwtjackson.client.stream.JsonToken.STRING;

@SuppressWarnings("resource")
public abstract class AbstractJsonReaderTest extends GwtJacksonTestCase {

    public abstract JsonReader newJsonReader( String input );

    public void testReadArray() {
        JsonReader reader = newJsonReader( "[true, true]" );
        reader.beginArray();
        assertEquals( true, reader.nextBoolean() );
        assertEquals( true, reader.nextBoolean() );
        reader.endArray();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testReadEmptyArray() {
        JsonReader reader = newJsonReader( "[]" );
        reader.beginArray();
        assertFalse( reader.hasNext() );
        reader.endArray();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testReadObject() {
        JsonReader reader = newJsonReader( "{\"a\": \"android\", \"b\": \"banana\"}" );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        assertEquals( "android", reader.nextString() );
        assertEquals( "b", reader.nextName() );
        assertEquals( "banana", reader.nextString() );
        reader.endObject();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testReadEmptyObject() {
        JsonReader reader = newJsonReader( "{}" );
        reader.beginObject();
        assertFalse( reader.hasNext() );
        reader.endObject();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testSkipArray() {
        JsonReader reader = newJsonReader( "{\"a\": [\"one\", \"two\", \"three\"], \"b\": 123}" );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        reader.skipValue();
        assertEquals( "b", reader.nextName() );
        assertEquals( 123, reader.nextInt() );
        reader.endObject();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testSkipArrayAfterPeek() throws Exception {
        JsonReader reader = newJsonReader( "{\"a\": [\"one\", \"two\", \"three\"], \"b\": 123}" );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        assertEquals( BEGIN_ARRAY, reader.peek() );
        reader.skipValue();
        assertEquals( "b", reader.nextName() );
        assertEquals( 123, reader.nextInt() );
        reader.endObject();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testSkipTopLevelObject() throws Exception {
        JsonReader reader = newJsonReader( "{\"a\": [\"one\", \"two\", \"three\"], \"b\": 123}" );
        reader.skipValue();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testSkipObject() {
        JsonReader reader = newJsonReader( "{\"a\": { \"c\": [], \"d\": [true, true, {}] }, \"b\": \"banana\"}" );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        reader.skipValue();
        assertEquals( "b", reader.nextName() );
        reader.skipValue();
        reader.endObject();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testSkipObjectAfterPeek() throws Exception {
        String json = "{" + "  \"one\": { \"num\": 1 }" + ", \"two\": { \"num\": 2 }" + ", \"three\": { \"num\": 3 }" + "}";
        JsonReader reader = newJsonReader( json );
        reader.beginObject();
        assertEquals( "one", reader.nextName() );
        assertEquals( BEGIN_OBJECT, reader.peek() );
        reader.skipValue();
        assertEquals( "two", reader.nextName() );
        assertEquals( BEGIN_OBJECT, reader.peek() );
        reader.skipValue();
        assertEquals( "three", reader.nextName() );
        reader.skipValue();
        reader.endObject();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testSkipInteger() {
        JsonReader reader = newJsonReader( "{\"a\":123456789,\"b\":-123456789}" );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        reader.skipValue();
        assertEquals( "b", reader.nextName() );
        reader.skipValue();
        reader.endObject();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testSkipDouble() {
        JsonReader reader = newJsonReader( "{\"a\":-123.456e-789,\"b\":123456789.0}" );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        reader.skipValue();
        assertEquals( "b", reader.nextName() );
        reader.skipValue();
        reader.endObject();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testHelloWorld() {
        String json = "{\n" +
                "   \"hello\": true,\n" +
                "   \"foo\": [\"world\"]\n" +
                "}";
        JsonReader reader = newJsonReader( json );
        reader.beginObject();
        assertEquals( "hello", reader.nextName() );
        assertEquals( true, reader.nextBoolean() );
        assertEquals( "foo", reader.nextName() );
        reader.beginArray();
        assertEquals( "world", reader.nextString() );
        reader.endArray();
        reader.endObject();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testNulls() {
        try {
            newJsonReader( null );
            fail();
        } catch ( NullPointerException expected ) {
        }
    }

    public void testEmptyString() {
        try {
            newJsonReader( "" ).beginArray();
        } catch ( JsonDeserializationException expected ) {
        }
        try {
            newJsonReader( "" ).beginObject();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testNoTopLevelObject() {
        try {
            newJsonReader( "true" ).nextBoolean();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testCharacterUnescaping() {
        String json = "[\"a\"," + "\"a\\\"\"," + "\"\\\"\"," + "\":\"," + "\",\"," + "\"\\b\"," + "\"\\f\"," + "\"\\n\"," + "\"\\r\"," +
                "" + "\"\\t\"," + "\" \"," + "\"\\\\\"," + "\"{\"," + "\"}\"," + "\"[\"," + "\"]\"," + "\"\\u0000\"," + "\"\\u0019\"," +
                "" + "\"\\u20AC\"" + "]";
        JsonReader reader = newJsonReader( json );
        reader.beginArray();
        assertEquals( "a", reader.nextString() );
        assertEquals( "a\"", reader.nextString() );
        assertEquals( "\"", reader.nextString() );
        assertEquals( ":", reader.nextString() );
        assertEquals( ",", reader.nextString() );
        assertEquals( "\b", reader.nextString() );
        assertEquals( "\f", reader.nextString() );
        assertEquals( "\n", reader.nextString() );
        assertEquals( "\r", reader.nextString() );
        assertEquals( "\t", reader.nextString() );
        assertEquals( " ", reader.nextString() );
        assertEquals( "\\", reader.nextString() );
        assertEquals( "{", reader.nextString() );
        assertEquals( "}", reader.nextString() );
        assertEquals( "[", reader.nextString() );
        assertEquals( "]", reader.nextString() );
        assertEquals( "\0", reader.nextString() );
        assertEquals( "\u0019", reader.nextString() );
        assertEquals( "\u20AC", reader.nextString() );
        reader.endArray();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testUnescapingInvalidCharacters() {
        String json = "[\"\\u000g\"]";
        JsonReader reader = newJsonReader( json );
        reader.beginArray();
        try {
            reader.nextString();
            fail();
        } catch ( NumberFormatException expected ) {
        }
    }

    public void testUnescapingTruncatedCharacters() {
        String json = "[\"\\u000";
        JsonReader reader = newJsonReader( json );
        reader.beginArray();
        try {
            reader.nextString();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testUnescapingTruncatedSequence() {
        String json = "[\"\\";
        JsonReader reader = newJsonReader( json );
        reader.beginArray();
        try {
            reader.nextString();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testIntegersWithFractionalPartSpecified() {
        JsonReader reader = newJsonReader( "[1.0,1.0,1.0]" );
        reader.beginArray();
        assertEquals( 1.0, reader.nextDouble() );
        assertEquals( 1, reader.nextInt() );
        assertEquals( 1L, reader.nextLong() );
    }

    public void testDoubles() {
        String json = "[-0.0," + "1.0," + "1.7976931348623157E308," + "4.9E-324," + "0.0," + "-0.5," + "2.2250738585072014E-308," +
                "" + "3.141592653589793," + "2.718281828459045]";
        JsonReader reader = newJsonReader( json );
        reader.beginArray();
        assertEquals( -0.0, reader.nextDouble() );
        assertEquals( 1.0, reader.nextDouble() );
        assertEquals( 1.7976931348623157E308, reader.nextDouble() );
        assertEquals( 4.9E-324, reader.nextDouble() );
        assertEquals( 0.0, reader.nextDouble() );
        assertEquals( -0.5, reader.nextDouble() );
        assertEquals( 2.2250738585072014E-308, reader.nextDouble() );
        assertEquals( 3.141592653589793, reader.nextDouble() );
        assertEquals( 2.718281828459045, reader.nextDouble() );
        reader.endArray();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testStrictNonFiniteDoubles() {
        String json = "[NaN]";
        JsonReader reader = newJsonReader( json );
        reader.beginArray();
        try {
            reader.nextDouble();
            fail();
        } catch ( MalformedJsonException expected ) {
        }
    }

    public void testStrictQuotedNonFiniteDoubles() {
        String json = "[\"NaN\"]";
        JsonReader reader = newJsonReader( json );
        reader.beginArray();
        try {
            reader.nextDouble();
            fail();
        } catch ( MalformedJsonException expected ) {
        }
    }

    public void testLenientNonFiniteDoubles() {
        String json = "[NaN, -Infinity, Infinity]";
        JsonReader reader = newJsonReader( json );
        reader.setLenient( true );
        reader.beginArray();
        assertTrue( Double.isNaN( reader.nextDouble() ) );
        assertEquals( Double.NEGATIVE_INFINITY, reader.nextDouble() );
        assertEquals( Double.POSITIVE_INFINITY, reader.nextDouble() );
        reader.endArray();
    }

    public void testLenientQuotedNonFiniteDoubles() {
        String json = "[\"NaN\", \"-Infinity\", \"Infinity\"]";
        JsonReader reader = newJsonReader( json );
        reader.setLenient( true );
        reader.beginArray();
        assertTrue( Double.isNaN( reader.nextDouble() ) );
        assertEquals( Double.NEGATIVE_INFINITY, reader.nextDouble() );
        assertEquals( Double.POSITIVE_INFINITY, reader.nextDouble() );
        reader.endArray();
    }

    public void testStrictNonFiniteDoublesWithSkipValue() {
        String json = "[NaN]";
        JsonReader reader = newJsonReader( json );
        reader.beginArray();
        try {
            reader.skipValue();
            fail();
        } catch ( MalformedJsonException expected ) {
        }
    }

    public void testLongs() {
        String json = "[0,0,0," + "1,1,1," + "-1,-1,-1," + "-9223372036854775808," + "9223372036854775807]";
        JsonReader reader = newJsonReader( json );
        reader.beginArray();
        assertEquals( 0L, reader.nextLong() );
        assertEquals( 0, reader.nextInt() );
        assertEquals( 0.0, reader.nextDouble() );
        assertEquals( 1L, reader.nextLong() );
        assertEquals( 1, reader.nextInt() );
        assertEquals( 1.0, reader.nextDouble() );
        assertEquals( -1L, reader.nextLong() );
        assertEquals( -1, reader.nextInt() );
        assertEquals( -1.0, reader.nextDouble() );
        try {
            reader.nextInt();
            fail();
        } catch ( NumberFormatException expected ) {
        }
        assertEquals( Long.MIN_VALUE, reader.nextLong() );
        try {
            reader.nextInt();
            fail();
        } catch ( NumberFormatException expected ) {
        }
        assertEquals( Long.MAX_VALUE, reader.nextLong() );
        reader.endArray();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void disabled_testNumberWithOctalPrefix() {
        String json = "[01]";
        JsonReader reader = newJsonReader( json );
        reader.beginArray();
        try {
            reader.peek();
            fail();
        } catch ( MalformedJsonException expected ) {
        }
        try {
            reader.nextInt();
            fail();
        } catch ( MalformedJsonException expected ) {
        }
        try {
            reader.nextLong();
            fail();
        } catch ( MalformedJsonException expected ) {
        }
        try {
            reader.nextDouble();
            fail();
        } catch ( MalformedJsonException expected ) {
        }
        assertEquals( "01", reader.nextString() );
        reader.endArray();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testBooleans() {
        JsonReader reader = newJsonReader( "[true,false]" );
        reader.beginArray();
        assertEquals( true, reader.nextBoolean() );
        assertEquals( false, reader.nextBoolean() );
        reader.endArray();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testPeekingUnquotedStringsPrefixedWithBooleans() {
        JsonReader reader = newJsonReader( "[truey]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( STRING, reader.peek() );
        try {
            reader.nextBoolean();
            fail();
        } catch ( IllegalStateException expected ) {
        }
        assertEquals( "truey", reader.nextString() );
        reader.endArray();
    }

    public void testMalformedNumbers() {
        assertNotANumber( "-" );
        assertNotANumber( "." );

        // exponent lacks digit
        assertNotANumber( "e" );
        assertNotANumber( "0e" );
        assertNotANumber( ".e" );
        assertNotANumber( "0.e" );
        assertNotANumber( "-.0e" );

        // no integer
        assertNotANumber( "e1" );
        assertNotANumber( ".e1" );
        assertNotANumber( "-e1" );

        // trailing characters
        assertNotANumber( "1x" );
        assertNotANumber( "1.1x" );
        assertNotANumber( "1e1x" );
        assertNotANumber( "1ex" );
        assertNotANumber( "1.1ex" );
        assertNotANumber( "1.1e1x" );

        // fraction has no digit
        assertNotANumber( "0." );
        assertNotANumber( "-0." );
        assertNotANumber( "0.e1" );
        assertNotANumber( "-0.e1" );

        // no leading digit
        assertNotANumber( ".0" );
        assertNotANumber( "-.0" );
        assertNotANumber( ".0e1" );
        assertNotANumber( "-.0e1" );
    }

    private void assertNotANumber( String s ) {
        JsonReader reader = newJsonReader( "[" + s + "]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( JsonToken.STRING, reader.peek() );
        assertEquals( s, reader.nextString() );
        reader.endArray();
    }

    public void testPeekingUnquotedStringsPrefixedWithIntegers() {
        JsonReader reader = newJsonReader( "[12.34e5x]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( STRING, reader.peek() );
        try {
            reader.nextInt();
            fail();
        } catch ( IllegalStateException expected ) {
        }
        assertEquals( "12.34e5x", reader.nextString() );
    }

    public void testPeekLongMinValue() {
        JsonReader reader = newJsonReader( "[-9223372036854775808]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( NUMBER, reader.peek() );
        assertEquals( -9223372036854775808L, reader.nextLong() );
    }

    public void testPeekLongMaxValue() {
        JsonReader reader = newJsonReader( "[9223372036854775807]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( NUMBER, reader.peek() );
        assertEquals( 9223372036854775807L, reader.nextLong() );
    }

    public void testLongLargerThanMaxLongThatWrapsAround() {
        JsonReader reader = newJsonReader( "[22233720368547758070]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( NUMBER, reader.peek() );
        try {
            reader.nextLong();
            fail();
        } catch ( NumberFormatException expected ) {
        }
    }

    public void testLongLargerThanMinLongThatWrapsAround() {
        JsonReader reader = newJsonReader( "[-22233720368547758070]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( NUMBER, reader.peek() );
        try {
            reader.nextLong();
            fail();
        } catch ( NumberFormatException expected ) {
        }
    }

    /**
     * This test fails because there's no double for 9223372036854775808, and our
     * long parsing uses Double.parseDouble() for fractional values.
     */
    public void disabled_testPeekLargerThanLongMaxValue() {
        JsonReader reader = newJsonReader( "[9223372036854775808]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( NUMBER, reader.peek() );
        try {
            reader.nextLong();
            fail();
        } catch ( NumberFormatException e ) {
        }
    }

    /**
     * This test fails because there's no double for -9223372036854775809, and our
     * long parsing uses Double.parseDouble() for fractional values.
     */
    public void disabled_testPeekLargerThanLongMinValue() {
        JsonReader reader = newJsonReader( "[-9223372036854775809]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( NUMBER, reader.peek() );
        try {
            reader.nextLong();
            fail();
        } catch ( NumberFormatException expected ) {
        }
        assertEquals( -9223372036854775809d, reader.nextDouble() );
    }

    /**
     * This test fails because there's no double for 9223372036854775806, and
     * our long parsing uses Double.parseDouble() for fractional values.
     */
    public void disabled_testHighPrecisionLong() {
        String json = "[9223372036854775806.000]";
        JsonReader reader = newJsonReader( json );
        reader.beginArray();
        assertEquals( 9223372036854775806L, reader.nextLong() );
        reader.endArray();
    }

    public void testPeekMuchLargerThanLongMinValue() {
        JsonReader reader = newJsonReader( "[-92233720368547758080]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( NUMBER, reader.peek() );
        try {
            reader.nextLong();
            fail();
        } catch ( NumberFormatException expected ) {
        }
        assertEquals( -92233720368547758080d, reader.nextDouble() );
    }

    public void testQuotedNumberWithEscape() {
        JsonReader reader = newJsonReader( "[\"12\u00334\"]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( STRING, reader.peek() );
        assertEquals( 1234, reader.nextInt() );
    }

    public void testMixedCaseLiterals() {
        JsonReader reader = newJsonReader( "[True,TruE,False,FALSE,NULL,nulL]" );
        reader.beginArray();
        assertEquals( true, reader.nextBoolean() );
        assertEquals( true, reader.nextBoolean() );
        assertEquals( false, reader.nextBoolean() );
        assertEquals( false, reader.nextBoolean() );
        reader.nextNull();
        reader.nextNull();
        reader.endArray();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testMissingValue() {
        JsonReader reader = newJsonReader( "{\"a\":}" );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        try {
            reader.nextString();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testPrematureEndOfInput() {
        JsonReader reader = newJsonReader( "{\"a\":true," );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        assertEquals( true, reader.nextBoolean() );
        try {
            reader.nextName();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testPrematurelyClosed() {
        try {
            JsonReader reader = newJsonReader( "{\"a\":[]}" );
            reader.beginObject();
            reader.close();
            reader.nextName();
            fail();
        } catch ( IllegalStateException expected ) {
        }

        try {
            JsonReader reader = newJsonReader( "{\"a\":[]}" );
            reader.close();
            reader.beginObject();
            fail();
        } catch ( IllegalStateException expected ) {
        }

        try {
            JsonReader reader = newJsonReader( "{\"a\":true}" );
            reader.beginObject();
            reader.nextName();
            reader.peek();
            reader.close();
            reader.nextBoolean();
            fail();
        } catch ( IllegalStateException expected ) {
        }
    }

    public void testNextFailuresDoNotAdvance() {
        JsonReader reader = newJsonReader( "{\"a\":true}" );
        reader.beginObject();
        try {
            reader.nextString();
            fail();
        } catch ( IllegalStateException expected ) {
        }
        assertEquals( "a", reader.nextName() );
        try {
            reader.nextName();
            fail();
        } catch ( IllegalStateException expected ) {
        }
        try {
            reader.beginArray();
            fail();
        } catch ( IllegalStateException expected ) {
        }
        try {
            reader.endArray();
            fail();
        } catch ( IllegalStateException expected ) {
        }
        try {
            reader.beginObject();
            fail();
        } catch ( IllegalStateException expected ) {
        }
        try {
            reader.endObject();
            fail();
        } catch ( IllegalStateException expected ) {
        }
        assertEquals( true, reader.nextBoolean() );
        try {
            reader.nextString();
            fail();
        } catch ( IllegalStateException expected ) {
        }
        try {
            reader.nextName();
            fail();
        } catch ( IllegalStateException expected ) {
        }
        try {
            reader.beginArray();
            fail();
        } catch ( IllegalStateException expected ) {
        }
        try {
            reader.endArray();
            fail();
        } catch ( IllegalStateException expected ) {
        }
        reader.endObject();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
        reader.close();
    }

    public void testIntegerMismatchFailuresDoNotAdvance() {
        JsonReader reader = newJsonReader( "[1.5]" );
        reader.beginArray();
        try {
            reader.nextInt();
            fail();
        } catch ( NumberFormatException expected ) {
        }
        assertEquals( 1.5d, reader.nextDouble() );
        reader.endArray();
    }

    public void testStringNullIsNotNull() {
        JsonReader reader = newJsonReader( "[\"null\"]" );
        reader.beginArray();
        try {
            reader.nextNull();
            fail();
        } catch ( IllegalStateException expected ) {
        }
    }

    public void testNullLiteralIsNotAString() {
        JsonReader reader = newJsonReader( "[null]" );
        reader.beginArray();
        try {
            reader.nextString();
            fail();
        } catch ( IllegalStateException expected ) {
        }
    }

    public void testStrictNameValueSeparator() {
        JsonReader reader = newJsonReader( "{\"a\"=true}" );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        try {
            reader.nextBoolean();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }

        reader = newJsonReader( "{\"a\"=>true}" );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        try {
            reader.nextBoolean();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testLenientNameValueSeparator() {
        JsonReader reader = newJsonReader( "{\"a\"=true}" );
        reader.setLenient( true );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        assertEquals( true, reader.nextBoolean() );

        reader = newJsonReader( "{\"a\"=>true}" );
        reader.setLenient( true );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        assertEquals( true, reader.nextBoolean() );
    }

    public void testStrictNameValueSeparatorWithSkipValue() {
        JsonReader reader = newJsonReader( "{\"a\"=true}" );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        try {
            reader.skipValue();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }

        reader = newJsonReader( "{\"a\"=>true}" );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        try {
            reader.skipValue();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testCommentsInStringValue() throws Exception {
        JsonReader reader = newJsonReader( "[\"// comment\"]" );
        reader.beginArray();
        assertEquals( "// comment", reader.nextString() );
        reader.endArray();

        reader = newJsonReader( "{\"a\":\"#someComment\"}" );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        assertEquals( "#someComment", reader.nextString() );
        reader.endObject();

        reader = newJsonReader( "{\"#//a\":\"#some //Comment\"}" );
        reader.beginObject();
        assertEquals( "#//a", reader.nextName() );
        assertEquals( "#some //Comment", reader.nextString() );
        reader.endObject();
    }

    public void testStrictComments() {
        JsonReader reader = newJsonReader( "[// comment \n true]" );
        reader.beginArray();
        try {
            reader.nextBoolean();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }

        reader = newJsonReader( "[# comment \n true]" );
        reader.beginArray();
        try {
            reader.nextBoolean();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }

        reader = newJsonReader( "[/* comment */ true]" );
        reader.beginArray();
        try {
            reader.nextBoolean();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testLenientComments() {
        JsonReader reader = newJsonReader( "[// comment \n true]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( true, reader.nextBoolean() );

        reader = newJsonReader( "[# comment \n true]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( true, reader.nextBoolean() );

        reader = newJsonReader( "[/* comment */ true]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( true, reader.nextBoolean() );
    }

    public void testStrictCommentsWithSkipValue() {
        JsonReader reader = newJsonReader( "[// comment \n true]" );
        reader.beginArray();
        try {
            reader.skipValue();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }

        reader = newJsonReader( "[# comment \n true]" );
        reader.beginArray();
        try {
            reader.skipValue();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }

        reader = newJsonReader( "[/* comment */ true]" );
        reader.beginArray();
        try {
            reader.skipValue();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testStrictUnquotedNames() {
        JsonReader reader = newJsonReader( "{a:true}" );
        reader.beginObject();
        try {
            reader.nextName();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testLenientUnquotedNames() {
        JsonReader reader = newJsonReader( "{a:true}" );
        reader.setLenient( true );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
    }

    public void testStrictUnquotedNamesWithSkipValue() {
        JsonReader reader = newJsonReader( "{a:true}" );
        reader.beginObject();
        try {
            reader.skipValue();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testStrictSingleQuotedNames() {
        JsonReader reader = newJsonReader( "{'a':true}" );
        reader.beginObject();
        try {
            reader.nextName();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testLenientSingleQuotedNames() {
        JsonReader reader = newJsonReader( "{'a':true}" );
        reader.setLenient( true );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
    }

    public void testStrictSingleQuotedNamesWithSkipValue() {
        JsonReader reader = newJsonReader( "{'a':true}" );
        reader.beginObject();
        try {
            reader.skipValue();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testStrictUnquotedStrings() {
        JsonReader reader = newJsonReader( "[a]" );
        reader.beginArray();
        try {
            reader.nextString();
            fail();
        } catch ( MalformedJsonException expected ) {
        }
    }

    public void testStrictUnquotedStringsWithSkipValue() {
        JsonReader reader = newJsonReader( "[a]" );
        reader.beginArray();
        try {
            reader.skipValue();
            fail();
        } catch ( MalformedJsonException expected ) {
        }
    }

    public void testLenientUnquotedStrings() {
        JsonReader reader = newJsonReader( "[a]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( "a", reader.nextString() );
    }

    public void testStrictSingleQuotedStrings() {
        JsonReader reader = newJsonReader( "['a']" );
        reader.beginArray();
        try {
            reader.nextString();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testLenientSingleQuotedStrings() {
        JsonReader reader = newJsonReader( "['a']" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( "a", reader.nextString() );
    }

    public void testStrictSingleQuotedStringsWithSkipValue() {
        JsonReader reader = newJsonReader( "['a']" );
        reader.beginArray();
        try {
            reader.skipValue();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testStrictSemicolonDelimitedArray() {
        JsonReader reader = newJsonReader( "[true;true]" );
        reader.beginArray();
        try {
            reader.nextBoolean();
            reader.nextBoolean();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testLenientSemicolonDelimitedArray() {
        JsonReader reader = newJsonReader( "[true;true]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( true, reader.nextBoolean() );
        assertEquals( true, reader.nextBoolean() );
    }

    public void testStrictSemicolonDelimitedArrayWithSkipValue() {
        JsonReader reader = newJsonReader( "[true;true]" );
        reader.beginArray();
        try {
            reader.skipValue();
            reader.skipValue();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testStrictSemicolonDelimitedNameValuePair() {
        JsonReader reader = newJsonReader( "{\"a\":true;\"b\":true}" );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        try {
            reader.nextBoolean();
            reader.nextName();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testLenientSemicolonDelimitedNameValuePair() {
        JsonReader reader = newJsonReader( "{\"a\":true;\"b\":true}" );
        reader.setLenient( true );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        assertEquals( true, reader.nextBoolean() );
        assertEquals( "b", reader.nextName() );
    }

    public void testStrictSemicolonDelimitedNameValuePairWithSkipValue() {
        JsonReader reader = newJsonReader( "{\"a\":true;\"b\":true}" );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        try {
            reader.skipValue();
            reader.skipValue();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testStrictUnnecessaryArraySeparators() {
        JsonReader reader = newJsonReader( "[true,,true]" );
        reader.beginArray();
        assertEquals( true, reader.nextBoolean() );
        try {
            reader.nextNull();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }

        reader = newJsonReader( "[,true]" );
        reader.beginArray();
        try {
            reader.nextNull();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }

        reader = newJsonReader( "[true,]" );
        reader.beginArray();
        assertEquals( true, reader.nextBoolean() );
        try {
            reader.nextNull();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }

        reader = newJsonReader( "[,]" );
        reader.beginArray();
        try {
            reader.nextNull();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testLenientUnnecessaryArraySeparators() {
        JsonReader reader = newJsonReader( "[true,,true]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( true, reader.nextBoolean() );
        reader.nextNull();
        assertEquals( true, reader.nextBoolean() );
        reader.endArray();

        reader = newJsonReader( "[,true]" );
        reader.setLenient( true );
        reader.beginArray();
        reader.nextNull();
        assertEquals( true, reader.nextBoolean() );
        reader.endArray();

        reader = newJsonReader( "[true,]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( true, reader.nextBoolean() );
        reader.nextNull();
        reader.endArray();

        reader = newJsonReader( "[,]" );
        reader.setLenient( true );
        reader.beginArray();
        reader.nextNull();
        reader.nextNull();
        reader.endArray();
    }

    public void testStrictUnnecessaryArraySeparatorsWithSkipValue() {
        JsonReader reader = newJsonReader( "[true,,true]" );
        reader.beginArray();
        assertEquals( true, reader.nextBoolean() );
        try {
            reader.skipValue();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }

        reader = newJsonReader( "[,true]" );
        reader.beginArray();
        try {
            reader.skipValue();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }

        reader = newJsonReader( "[true,]" );
        reader.beginArray();
        assertEquals( true, reader.nextBoolean() );
        try {
            reader.skipValue();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }

        reader = newJsonReader( "[,]" );
        reader.beginArray();
        try {
            reader.skipValue();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testStrictMultipleTopLevelValues() {
        JsonReader reader = newJsonReader( "[] []" );
        reader.beginArray();
        reader.endArray();
        try {
            reader.peek();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testLenientMultipleTopLevelValues() {
        JsonReader reader = newJsonReader( "[] true {}" );
        reader.setLenient( true );
        reader.beginArray();
        reader.endArray();
        assertEquals( true, reader.nextBoolean() );
        reader.beginObject();
        reader.endObject();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testStrictMultipleTopLevelValuesWithSkipValue() {
        JsonReader reader = newJsonReader( "[] []" );
        reader.beginArray();
        reader.endArray();
        try {
            reader.skipValue();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testStrictTopLevelString() {
        JsonReader reader = newJsonReader( "\"a\"" );
        try {
            reader.nextString();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testLenientTopLevelString() {
        JsonReader reader = newJsonReader( "\"a\"" );
        reader.setLenient( true );
        assertEquals( "a", reader.nextString() );
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testStrictTopLevelValueType() {
        JsonReader reader = newJsonReader( "true" );
        try {
            reader.nextBoolean();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testLenientTopLevelValueType() {
        JsonReader reader = newJsonReader( "true" );
        reader.setLenient( true );
        assertEquals( true, reader.nextBoolean() );
    }

    public void testStrictTopLevelValueTypeWithSkipValue() {
        JsonReader reader = newJsonReader( "true" );
        try {
            reader.skipValue();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testStrictNonExecutePrefix() {
        JsonReader reader = newJsonReader( ")]}'\n []" );
        try {
            reader.beginArray();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testStrictNonExecutePrefixWithSkipValue() {
        JsonReader reader = newJsonReader( ")]}'\n []" );
        try {
            reader.skipValue();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testLenientNonExecutePrefix() {
        JsonReader reader = newJsonReader( ")]}'\n []" );
        reader.setLenient( true );
        reader.beginArray();
        reader.endArray();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testLenientNonExecutePrefixWithLeadingWhitespace() {
        JsonReader reader = newJsonReader( "\r\n \t)]}'\n []" );
        reader.setLenient( true );
        reader.beginArray();
        reader.endArray();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testLenientPartialNonExecutePrefix() {
        JsonReader reader = newJsonReader( ")]}' []" );
        reader.setLenient( true );
        try {
            assertEquals( ")", reader.nextString() );
            reader.nextString();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testBomIgnoredAsFirstCharacterOfDocument() {
        JsonReader reader = newJsonReader( "\ufeff[]" );
        reader.beginArray();
        reader.endArray();
    }

    public void testBomForbiddenAsOtherCharacterInDocument() {
        JsonReader reader = newJsonReader( "[\ufeff]" );
        reader.beginArray();
        try {
            reader.endArray();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testFailWithPosition() {
        testFailWithPosition( "Expected value at line 6 column 5", "[\n\n\n\n\n\"a\",}]" );
    }

    public void testFailWithPositionGreaterThanBufferSize() {
        String spaces = repeat( ' ', 8192 );
        testFailWithPosition( "Expected value at line 6 column 5", "[\n\n" + spaces + "\n\n\n\"a\",}]" );
    }

    public void testFailWithPositionOverSlashSlashEndOfLineComment() {
        testFailWithPosition( "Expected value at line 5 column 6", "\n// foo\n\n//bar\r\n[\"a\",}" );
    }

    public void testFailWithPositionOverHashEndOfLineComment() {
        testFailWithPosition( "Expected value at line 5 column 6", "\n# foo\n\n#bar\r\n[\"a\",}" );
    }

    public void testFailWithPositionOverCStyleComment() {
        testFailWithPosition( "Expected value at line 6 column 12", "\n\n/* foo\n*\n*\r\nbar */[\"a\",}" );
    }

    public void testFailWithPositionOverQuotedString() {
        testFailWithPosition( "Expected value at line 5 column 3", "[\"foo\nbar\r\nbaz\n\",\n  }" );
    }

    public void testFailWithPositionOverUnquotedString() {
        testFailWithPosition( "Expected value at line 5 column 2", "[\n\nabcd\n\n,}" );
    }

    public void testFailWithEscapedNewlineCharacter() {
        testFailWithPosition( "Expected value at line 5 column 3", "[\n\n\"\\\n\n\",}" );
    }

    public void testFailWithPositionIsOffsetByBom() {
        testFailWithPosition( "Expected value at line 1 column 6", "\ufeff[\"a\",}]" );
    }

    private void testFailWithPosition( String message, String json ) {
        // Validate that it works reading the string normally.
        JsonReader reader1 = newJsonReader( json );
        reader1.setLenient( true );
        reader1.beginArray();
        reader1.nextString();
        try {
            reader1.peek();
            fail();
        } catch ( JsonDeserializationException expected ) {
            assertEquals( message, expected.getMessage() );
        }

        // Also validate that it works when skipping.
        JsonReader reader2 = newJsonReader( json );
        reader2.setLenient( true );
        reader2.beginArray();
        reader2.skipValue();
        try {
            reader2.peek();
            fail();
        } catch ( JsonDeserializationException expected ) {
            assertEquals( message, expected.getMessage() );
        }
    }

    public void testVeryLongUnquotedLiteral() {
        String literal = "a" + repeat( 'b', 8192 ) + "c";
        JsonReader reader = newJsonReader( "[" + literal + "]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( literal, reader.nextString() );
        reader.endArray();
    }

    public void testDeeplyNestedArrays() {
        // this is nested 40 levels deep; Gson is tuned for nesting is 30 levels deep or fewer
        JsonReader reader = newJsonReader( "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" );
        for ( int i = 0; i < 40; i++ ) {
            reader.beginArray();
        }
        for ( int i = 0; i < 40; i++ ) {
            reader.endArray();
        }
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testDeeplyNestedObjects() {
        // Build a JSON document structured like {"a":{"a":{"a":{"a":true}}}}, but 40 levels deep
        String array = "{\"a\":%s}";
        String json = "true";
        for ( int i = 0; i < 40; i++ ) {
            //      json = String.format( array, json );
            json = array.replace( "%s", json );
        }

        JsonReader reader = newJsonReader( json );
        for ( int i = 0; i < 40; i++ ) {
            reader.beginObject();
            assertEquals( "a", reader.nextName() );
        }
        assertEquals( true, reader.nextBoolean() );
        for ( int i = 0; i < 40; i++ ) {
            reader.endObject();
        }
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    // http://code.google.com/p/google-gson/issues/detail?id=409
    public void testStringEndingInSlash() {
        JsonReader reader = newJsonReader( "/" );
        reader.setLenient( true );
        try {
            reader.peek();
            fail();
        } catch ( MalformedJsonException expected ) {
        }
    }

    public void testDocumentWithCommentEndingInSlash() {
        JsonReader reader = newJsonReader( "/* foo *//" );
        reader.setLenient( true );
        try {
            reader.peek();
            fail();
        } catch ( MalformedJsonException expected ) {
        }
    }

    public void testStringWithLeadingSlash() {
        JsonReader reader = newJsonReader( "/x" );
        reader.setLenient( true );
        try {
            reader.peek();
            fail();
        } catch ( MalformedJsonException expected ) {
        }
    }

    public void testUnterminatedObject() {
        JsonReader reader = newJsonReader( "{\"a\":\"android\"x" );
        reader.setLenient( true );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        assertEquals( "android", reader.nextString() );
        try {
            reader.peek();
            fail();
        } catch ( MalformedJsonException expected ) {
        }
    }

    public void testVeryLongQuotedString() {
        char[] stringChars = new char[1024 * 16];
        Arrays.fill( stringChars, 'x' );
        String string = new String( stringChars );
        String json = "[\"" + string + "\"]";
        JsonReader reader = newJsonReader( json );
        reader.beginArray();
        assertEquals( string, reader.nextString() );
        reader.endArray();
    }

    public void testVeryLongUnquotedString() {
        char[] stringChars = new char[1024 * 16];
        Arrays.fill( stringChars, 'x' );
        String string = new String( stringChars );
        String json = "[" + string + "]";
        JsonReader reader = newJsonReader( json );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( string, reader.nextString() );
        reader.endArray();
    }

    public void testVeryLongUnterminatedString() {
        char[] stringChars = new char[1024 * 16];
        Arrays.fill( stringChars, 'x' );
        String string = new String( stringChars );
        String json = "[" + string;
        JsonReader reader = newJsonReader( json );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( string, reader.nextString() );
        try {
            reader.peek();
            fail();
        } catch ( JsonDeserializationException expected ) {
            assertTrue( expected.getMessage().startsWith( "End of input at line" ) );
        }
    }

    public void testSkipVeryLongUnquotedString() {
        JsonReader reader = newJsonReader( "[" + repeat( 'x', 8192 ) + "]" );
        reader.setLenient( true );
        reader.beginArray();
        reader.skipValue();
        reader.endArray();
    }

    public void testSkipTopLevelUnquotedString() {
        JsonReader reader = newJsonReader( repeat( 'x', 8192 ) );
        reader.setLenient( true );
        reader.skipValue();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testSkipVeryLongQuotedString() {
        JsonReader reader = newJsonReader( "[\"" + repeat( 'x', 8192 ) + "\"]" );
        reader.beginArray();
        reader.skipValue();
        reader.endArray();
    }

    public void testSkipTopLevelQuotedString() {
        JsonReader reader = newJsonReader( "\"" + repeat( 'x', 8192 ) + "\"" );
        reader.setLenient( true );
        reader.skipValue();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testStringAsNumberWithTruncatedExponent() {
        JsonReader reader = newJsonReader( "[123e]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( STRING, reader.peek() );
    }

    public void testStringAsNumberWithDigitAndNonDigitExponent() {
        JsonReader reader = newJsonReader( "[123e4b]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( STRING, reader.peek() );
    }

    public void testStringAsNumberWithNonDigitExponent() {
        JsonReader reader = newJsonReader( "[123eb]" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( STRING, reader.peek() );
    }

    public void testEmptyStringName() {
        JsonReader reader = newJsonReader( "{\"\":true}" );
        reader.setLenient( true );
        assertEquals( BEGIN_OBJECT, reader.peek() );
        reader.beginObject();
        assertEquals( NAME, reader.peek() );
        assertEquals( "", reader.nextName() );
        assertEquals( JsonToken.BOOLEAN, reader.peek() );
        assertEquals( true, reader.nextBoolean() );
        assertEquals( JsonToken.END_OBJECT, reader.peek() );
        reader.endObject();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testStrictExtraCommasInMaps() {
        JsonReader reader = newJsonReader( "{\"a\":\"b\",}" );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        assertEquals( "b", reader.nextString() );
        try {
            reader.peek();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    public void testLenientExtraCommasInMaps() {
        JsonReader reader = newJsonReader( "{\"a\":\"b\",}" );
        reader.setLenient( true );
        reader.beginObject();
        assertEquals( "a", reader.nextName() );
        assertEquals( "b", reader.nextString() );
        try {
            reader.peek();
            fail();
        } catch ( JsonDeserializationException expected ) {
        }
    }

    protected String repeat( char c, int count ) {
        char[] array = new char[count];
        Arrays.fill( array, c );
        return new String( array );
    }

    public void testMalformedDocuments() {
        assertDocument( "{]", BEGIN_OBJECT, JsonDeserializationException.class );
        assertDocument( "{,", BEGIN_OBJECT, JsonDeserializationException.class );
        assertDocument( "{{", BEGIN_OBJECT, JsonDeserializationException.class );
        assertDocument( "{[", BEGIN_OBJECT, JsonDeserializationException.class );
        assertDocument( "{:", BEGIN_OBJECT, JsonDeserializationException.class );
        assertDocument( "{\"name\",", BEGIN_OBJECT, NAME, JsonDeserializationException.class );
        assertDocument( "{\"name\",", BEGIN_OBJECT, NAME, JsonDeserializationException.class );
        assertDocument( "{\"name\":}", BEGIN_OBJECT, NAME, JsonDeserializationException.class );
        assertDocument( "{\"name\"::", BEGIN_OBJECT, NAME, JsonDeserializationException.class );
        assertDocument( "{\"name\":,", BEGIN_OBJECT, NAME, JsonDeserializationException.class );
        assertDocument( "{\"name\"=}", BEGIN_OBJECT, NAME, JsonDeserializationException.class );
        assertDocument( "{\"name\"=>}", BEGIN_OBJECT, NAME, JsonDeserializationException.class );
        assertDocument( "{\"name\"=>\"string\":", BEGIN_OBJECT, NAME, STRING, JsonDeserializationException.class );
        assertDocument( "{\"name\"=>\"string\"=", BEGIN_OBJECT, NAME, STRING, JsonDeserializationException.class );
        assertDocument( "{\"name\"=>\"string\"=>", BEGIN_OBJECT, NAME, STRING, JsonDeserializationException.class );
        assertDocument( "{\"name\"=>\"string\",", BEGIN_OBJECT, NAME, STRING, JsonDeserializationException.class );
        assertDocument( "{\"name\"=>\"string\",\"name\"", BEGIN_OBJECT, NAME, STRING, NAME );
        assertDocument( "[}", BEGIN_ARRAY, JsonDeserializationException.class );
        assertDocument( "[,]", BEGIN_ARRAY, NULL, NULL, END_ARRAY );
        assertDocument( "{", BEGIN_OBJECT, JsonDeserializationException.class );
        assertDocument( "{\"name\"", BEGIN_OBJECT, NAME, JsonDeserializationException.class );
        assertDocument( "{\"name\",", BEGIN_OBJECT, NAME, JsonDeserializationException.class );
        assertDocument( "{'name'", BEGIN_OBJECT, NAME, JsonDeserializationException.class );
        assertDocument( "{'name',", BEGIN_OBJECT, NAME, JsonDeserializationException.class );
        assertDocument( "{name", BEGIN_OBJECT, NAME, JsonDeserializationException.class );
        assertDocument( "[", BEGIN_ARRAY, JsonDeserializationException.class );
        assertDocument( "[string", BEGIN_ARRAY, STRING, JsonDeserializationException.class );
        assertDocument( "[\"string\"", BEGIN_ARRAY, STRING, JsonDeserializationException.class );
        assertDocument( "['string'", BEGIN_ARRAY, STRING, JsonDeserializationException.class );
        assertDocument( "[123", BEGIN_ARRAY, NUMBER, JsonDeserializationException.class );
        assertDocument( "[123,", BEGIN_ARRAY, NUMBER, JsonDeserializationException.class );
        assertDocument( "{\"name\":123", BEGIN_OBJECT, NAME, NUMBER, JsonDeserializationException.class );
        assertDocument( "{\"name\":123,", BEGIN_OBJECT, NAME, NUMBER, JsonDeserializationException.class );
        assertDocument( "{\"name\":\"string\"", BEGIN_OBJECT, NAME, STRING, JsonDeserializationException.class );
        assertDocument( "{\"name\":\"string\",", BEGIN_OBJECT, NAME, STRING, JsonDeserializationException.class );
        assertDocument( "{\"name\":'string'", BEGIN_OBJECT, NAME, STRING, JsonDeserializationException.class );
        assertDocument( "{\"name\":'string',", BEGIN_OBJECT, NAME, STRING, JsonDeserializationException.class );
        assertDocument( "{\"name\":false", BEGIN_OBJECT, NAME, BOOLEAN, JsonDeserializationException.class );
        assertDocument( "{\"name\":false,,", BEGIN_OBJECT, NAME, BOOLEAN, JsonDeserializationException.class );
    }

    /**
     * This test behave slightly differently in Gson 2.2 and earlier. It fails
     * during peek rather than during nextString().
     */
    public void testUnterminatedStringFailure() {
        JsonReader reader = newJsonReader( "[\"string" );
        reader.setLenient( true );
        reader.beginArray();
        assertEquals( JsonToken.STRING, reader.peek() );
        try {
            reader.nextString();
            fail();
        } catch ( MalformedJsonException expected ) {
        }
    }

    public void testNextValueNull() {
        JsonReader reader = newJsonReader( "{\"value\":null}" );
        reader.beginObject();
        assertEquals( "value", reader.nextName() );
        assertEquals( "null", reader.nextValue() );
        reader.endObject();
    }

    public void testNextNumber() {
        JsonReader reader = newJsonReader( "[" +
                "123," +
                "12345678999," +
                "54878.45," +
                "\"literal\"," +
                "\"-1545.78\"," +
                "\"2147483647\"," +
                "\"2147483648\"," +
                Integer.MIN_VALUE + "," +
                (Long.valueOf( "" + Integer.MIN_VALUE ) - 1l) + "," +
                Integer.MAX_VALUE + "," +
                (Long.valueOf( "" + Integer.MAX_VALUE ) + 1l) + "," +
                Long.MIN_VALUE + ", " +
                "\"" + new BigInteger( Long.MIN_VALUE + "" ).subtract( BigInteger.ONE ) + "\"," +
                Long.MAX_VALUE + ", " +
                "\"" + new BigInteger( Long.MAX_VALUE + "" ).add( BigInteger.ONE ) + "\"" +
                "]" );
        reader.beginArray();
        assertEquals( new Integer( 123 ), reader.nextNumber() );
        assertEquals( new Long( 12345678999l ), reader.nextNumber() );
        assertEquals( new Double( 54878.45d ), reader.nextNumber() );
        try {
            reader.nextNumber();
            fail();
        } catch ( NumberFormatException e ) {
        }
        assertEquals( "literal", reader.nextString() );
        assertEquals( new Double( -1545.78d ), reader.nextNumber() );
        assertEquals( new Integer( 2147483647 ), reader.nextNumber() );
        assertEquals( new Long( 2147483648l ), reader.nextNumber() );
        assertEquals( Integer.MIN_VALUE, reader.nextNumber() );
        assertEquals( Long.valueOf( "" + Integer.MIN_VALUE ) - 1l, reader.nextNumber() );
        assertEquals( Integer.MAX_VALUE, reader.nextNumber() );
        assertEquals( Long.valueOf( "" + Integer.MAX_VALUE ) + 1l, reader.nextNumber() );
        assertEquals( Long.MIN_VALUE, reader.nextNumber() );
        assertEquals( new BigInteger( Long.MIN_VALUE + "" ).subtract( BigInteger.ONE ), reader.nextNumber() );
        assertEquals( Long.MAX_VALUE, reader.nextNumber() );
        assertEquals( new BigInteger( Long.MAX_VALUE + "" ).add( BigInteger.ONE ), reader.nextNumber() );
        reader.endArray();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testNextJavaScriptObjectRootNoObjectOrArray() {
        JsonReader reader = newJsonReader( "true" );
        reader.setLenient( true );
        try {
            reader.nextJavaScriptObject( true );
            fail();
        } catch ( IllegalStateException e ) {
            // expected exception
        }
    }

    public void testNextJavaScriptObjectNoRootNoObjectOrArray() {
        JsonReader reader = newJsonReader( "{\"name\":\"wrapper\",\"jso\":true}" );
        reader.setLenient( true );

        reader.beginObject();
        assertEquals( "name", reader.nextName() );
        assertEquals( "wrapper", reader.nextString() );

        assertEquals( "jso", reader.nextName() );
        try {
            reader.nextJavaScriptObject( true );
            fail();
        } catch ( IllegalStateException e ) {
            // expected exception
        }
    }

    public void testNextJavaScriptObjectRootObject() {
        // safeEval
        JsonReader reader = newJsonReader( "{\"firstName\":\"Bob\",\"lastName\":\"Morane\",\"bio\":null}" );
        Person person = reader.nextJavaScriptObject( true ).cast();
        assertEquals( "Bob", person.getFirstName() );
        assertEquals( "Morane", person.getLastName() );
        assertNull( person.getBio() );
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );

        // unsafeEval
        reader = newJsonReader( "  \n {\"firstName\":\"Bob\",\"lastName\":\"Morane\"}" );
        person = reader.nextJavaScriptObject( false ).cast();
        assertEquals( "Bob", person.getFirstName() );
        assertEquals( "Morane", person.getLastName() );
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testNextJavaScriptObjectRootArray() {
        // safeEval
        JsonReader reader = newJsonReader( "       [\"Bob\",\"Morane\"]  " );
        JsArrayString array = reader.nextJavaScriptObject( true ).cast();
        assertEquals( 2, array.length() );
        assertEquals( "Bob", array.get( 0 ) );
        assertEquals( "Morane", array.get( 1 ) );
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );

        // unsafeEval
        reader = newJsonReader( "[\"Bob\",\"Morane\"]" );
        array = reader.nextJavaScriptObject( false ).cast();
        assertEquals( 2, array.length() );
        assertEquals( "Bob", array.get( 0 ) );
        assertEquals( "Morane", array.get( 1 ) );
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testNextJavaScriptObjectNoRootObject() {
        // safeEval
        JsonReader reader = newJsonReader( "{\"name\":\"wrapper\",\"jso\":{\"firstName\":\"Bob\",\"lastName\":\"Mor\\\"ane\"}}" );
        reader.beginObject();
        assertEquals( "name", reader.nextName() );
        assertEquals( "wrapper", reader.nextString() );

        assertEquals( "jso", reader.nextName() );
        Person person = reader.nextJavaScriptObject( true ).cast();
        assertEquals( "Bob", person.getFirstName() );
        assertEquals( "Mor\"ane", person.getLastName() );

        reader.endObject();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );

        // unsafeEval
        reader = newJsonReader( "{\"jso\":{\"firstName\":\"Bob\",\"lastName\":\"Morane\",\"bio\":null},\"name\":\"wrapper\"}" );
        reader.beginObject();

        assertEquals( "jso", reader.nextName() );
        person = reader.nextJavaScriptObject( false ).cast();
        assertEquals( "Bob", person.getFirstName() );
        assertEquals( "Morane", person.getLastName() );
        assertNull( person.getBio() );

        assertEquals( "name", reader.nextName() );
        assertEquals( "wrapper", reader.nextString() );
        reader.endObject();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    public void testNextJavaScriptObjectNoRootArray() {
        // safeEval
        JsonReader reader = newJsonReader( "   {\"name\":\"wrapper\",\"jso\":    [\"Bob\",\"Morane\", true, null, 145] } " );
        reader.beginObject();
        assertEquals( "name", reader.nextName() );
        assertEquals( "wrapper", reader.nextString() );

        assertEquals( "jso", reader.nextName() );
        JsArrayMixed array = reader.nextJavaScriptObject( true ).cast();
        assertEquals( 5, array.length() );
        assertEquals( "Bob", array.getString( 0 ) );
        assertEquals( "Morane", array.getString( 1 ) );
        assertTrue( array.getBoolean( 2 ) );
        assertNull( array.getObject( 3 ) );
        assertEquals( 145d, array.getNumber( 4 ) );

        reader.endObject();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );

        // unsafeEval
        reader = newJsonReader( "   {\"jso\":    [\"Bob\",\"Morane\", true, 145] ,\"name\":\"wrapper\" } " );
        reader.beginObject();

        assertEquals( "jso", reader.nextName() );
        array = reader.nextJavaScriptObject( false ).cast();
        assertEquals( 4, array.length() );
        assertEquals( "Bob", array.getString( 0 ) );
        assertEquals( "Morane", array.getString( 1 ) );
        assertTrue( array.getBoolean( 2 ) );
        assertEquals( 145d, array.getNumber( 3 ) );

        assertEquals( "name", reader.nextName() );
        assertEquals( "wrapper", reader.nextString() );

        reader.endObject();
        assertEquals( JsonToken.END_DOCUMENT, reader.peek() );
    }

    private void assertDocument( String document, Object... expectations ) {
        JsonReader reader = newJsonReader( document );
        reader.setLenient( true );
        for ( Object expectation : expectations ) {
            if ( expectation == BEGIN_OBJECT ) {
                reader.beginObject();
            } else if ( expectation == BEGIN_ARRAY ) {
                reader.beginArray();
            } else if ( expectation == END_OBJECT ) {
                reader.endObject();
            } else if ( expectation == END_ARRAY ) {
                reader.endArray();
            } else if ( expectation == NAME ) {
                assertEquals( "name", reader.nextName() );
            } else if ( expectation == BOOLEAN ) {
                assertEquals( false, reader.nextBoolean() );
            } else if ( expectation == STRING ) {
                assertEquals( "string", reader.nextString() );
            } else if ( expectation == NUMBER ) {
                assertEquals( 123, reader.nextInt() );
            } else if ( expectation == NULL ) {
                reader.nextNull();
            } else if ( expectation == JsonDeserializationException.class ) {
                try {
                    reader.peek();
                    fail();
                } catch ( JsonDeserializationException expected ) {
                }
            } else {
                throw new AssertionError();
            }
        }
    }

    /**
     * Returns a reader that returns one character at a time.
     */
    private StringReader reader( final String s ) {
        return new StringReader( s );
    }
}