SimpleParserTest.java

package com.fasterxml.jackson.core.read;

import java.io.*;
import java.net.URL;
import java.util.*;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.testsupport.MockDataInput;
import com.fasterxml.jackson.core.util.JsonParserDelegate;

import static org.junit.jupiter.api.Assertions.*;

/**
 * Set of basic unit tests for verifying that the basic parser
 * functionality works as expected.
 */
@SuppressWarnings("resource")
class SimpleParserTest extends JUnit5TestBase
{
    @Test
    void config() throws Exception
    {
        JsonParser p = createParser(MODE_READER, "[ ]");
        Object src = p.getInputSource();
        assertNotNull(src);
        assertTrue(src instanceof Reader);
        p.enable(JsonParser.Feature.AUTO_CLOSE_SOURCE);
        assertTrue(p.isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
        p.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE);
        assertFalse(p.isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));

        p.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, true);
        assertTrue(p.isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
        p.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false);
        assertFalse(p.isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
        p.close();

        p = createParser(MODE_INPUT_STREAM, "[ ]");
        src = p.getInputSource();
        assertNotNull(src);
        assertTrue(src instanceof InputStream);
        p.close();

        p = createParser(MODE_DATA_INPUT, "[ ]");
        src = p.getInputSource();
        assertNotNull(src);
        assertTrue(src instanceof DataInput);
        p.close();
    }

    @Test
    void interningWithStreams() throws Exception
    {
        _testIntern(true, true, "a");
        _testIntern(true, false, "b");
    }

    @Test
    void interningWithReaders() throws Exception
    {
        _testIntern(false, true, "c");
        _testIntern(false, false, "d");
    }

    private void _testIntern(boolean useStream, boolean enableIntern, String expName) throws IOException
    {
        JsonFactory f = JsonFactory.builder()
                .configure(JsonFactory.Feature.INTERN_FIELD_NAMES, enableIntern)
                .build();
        assertEquals(enableIntern, f.isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
        final String JSON = "{ \""+expName+"\" : 1}";
        JsonParser p = useStream ?
            createParserUsingStream(f, JSON, "UTF-8") : createParserUsingReader(f, JSON);

        assertToken(JsonToken.START_OBJECT, p.nextToken());
        assertToken(JsonToken.FIELD_NAME, p.nextToken());
        // needs to be same of cours
        String actName = p.currentName();
        assertEquals(expName, actName);
        if (enableIntern) {
            assertSame(expName, actName);
        } else {
            assertNotSame(expName, actName);
        }
        p.close();
    }

    /**
     * This basic unit test verifies that example given in the JSON
     * specification (RFC-4627 or later) is properly parsed at
     * high-level, without verifying values.
     */
    @Test
    void specExampleSkipping() throws Exception
    {
        _doTestSpec(false);
    }

    /**
     * Unit test that verifies that the spec example JSON is completely
     * parsed, and proper values are given for contents of all
     * events/tokens.
     */
    @Test
    void specExampleFully() throws Exception
    {
        _doTestSpec(true);
    }

    /**
     * Unit test that verifies that 3 basic keywords (null, true, false)
     * are properly parsed in various contexts.
     */
    @Test
    void keywords() throws Exception
    {
        final String DOC = "{\n"
            +"\"key1\" : null,\n"
            +"\"key2\" : true,\n"
            +"\"key3\" : false,\n"
            +"\"key4\" : [ false, null, true ]\n"
            +"}"
            ;

        JsonParser p = createParserUsingStream(JSON_FACTORY, DOC, "UTF-8");
        _testKeywords(p, true);
        p.close();

        p = createParserUsingReader(JSON_FACTORY, DOC);
        _testKeywords(p, true);
        p.close();

        p = createParserForDataInput(JSON_FACTORY, new MockDataInput(DOC));
        _testKeywords(p, false);
        p.close();
    }

    private void _testKeywords(JsonParser p, boolean checkColumn) throws Exception
    {
        JsonStreamContext ctxt = p.getParsingContext();
        assertEquals("/", ctxt.toString());
        assertTrue(ctxt.inRoot());
        assertFalse(ctxt.inArray());
        assertFalse(ctxt.inObject());
        assertEquals(0, ctxt.getEntryCount());
        assertEquals(0, ctxt.getCurrentIndex());

        // Before advancing to content, we should have following default state...
        assertFalse(p.hasCurrentToken());
        assertNull(p.getText());
        assertNull(p.getTextCharacters());
        assertEquals(0, p.getTextLength());
        // not sure if this is defined but:
        assertEquals(0, p.getTextOffset());

        assertToken(JsonToken.START_OBJECT, p.nextToken());
        assertEquals("/", ctxt.toString());

        assertTrue(p.hasCurrentToken());
        JsonLocation loc = p.currentTokenLocation();
        assertNotNull(loc);
        assertEquals(1, loc.getLineNr());
        if (checkColumn) {
            assertEquals(1, loc.getColumnNr());
        }

        ctxt = p.getParsingContext();
        assertFalse(ctxt.inRoot());
        assertFalse(ctxt.inArray());
        assertTrue(ctxt.inObject());
        assertEquals(0, ctxt.getEntryCount());
        assertEquals(0, ctxt.getCurrentIndex());

        assertToken(JsonToken.FIELD_NAME, p.nextToken());
        verifyFieldName(p, "key1");
        assertEquals("{\"key1\"}", ctxt.toString());
        assertEquals(2, p.currentTokenLocation().getLineNr());

        ctxt = p.getParsingContext();
        assertFalse(ctxt.inRoot());
        assertFalse(ctxt.inArray());
        assertTrue(ctxt.inObject());
        assertEquals(1, ctxt.getEntryCount());
        assertEquals(0, ctxt.getCurrentIndex());
        assertEquals("key1", ctxt.getCurrentName());

        assertToken(JsonToken.VALUE_NULL, p.nextToken());
        assertEquals("key1", ctxt.getCurrentName());

        ctxt = p.getParsingContext();
        assertEquals(1, ctxt.getEntryCount());
        assertEquals(0, ctxt.getCurrentIndex());

        assertToken(JsonToken.FIELD_NAME, p.nextToken());
        verifyFieldName(p, "key2");
        ctxt = p.getParsingContext();
        assertEquals(2, ctxt.getEntryCount());
        assertEquals(1, ctxt.getCurrentIndex());
        assertEquals("key2", ctxt.getCurrentName());

        assertToken(JsonToken.VALUE_TRUE, p.nextToken());
        assertEquals("key2", ctxt.getCurrentName());

        assertToken(JsonToken.FIELD_NAME, p.nextToken());
        verifyFieldName(p, "key3");
        assertToken(JsonToken.VALUE_FALSE, p.nextToken());

        assertToken(JsonToken.FIELD_NAME, p.nextToken());
        verifyFieldName(p, "key4");

        assertToken(JsonToken.START_ARRAY, p.nextToken());
        ctxt = p.getParsingContext();
        assertTrue(ctxt.inArray());
        assertNull(ctxt.getCurrentName());
        assertEquals("key4", ctxt.getParent().getCurrentName());

        assertToken(JsonToken.VALUE_FALSE, p.nextToken());
        assertEquals("[0]", ctxt.toString());

        assertToken(JsonToken.VALUE_NULL, p.nextToken());
        assertToken(JsonToken.VALUE_TRUE, p.nextToken());
        assertToken(JsonToken.END_ARRAY, p.nextToken());

        ctxt = p.getParsingContext();
        assertTrue(ctxt.inObject());

        assertToken(JsonToken.END_OBJECT, p.nextToken());
        ctxt = p.getParsingContext();
        assertTrue(ctxt.inRoot());
        assertNull(ctxt.getCurrentName());
    }

    @Test
    void skipping() throws Exception {
        _testSkipping(MODE_INPUT_STREAM);
        _testSkipping(MODE_INPUT_STREAM_THROTTLED);
        _testSkipping(MODE_READER);
        _testSkipping(MODE_DATA_INPUT);
    }

    private void _testSkipping(int mode) throws Exception
    {
        // InputData has some limitations to take into consideration
        boolean isInputData = (mode == MODE_DATA_INPUT);
        String DOC = a2q(
            "[ 1, 3, [ true, null ], 3, { 'a\\\\b':'quoted: \\'stuff\\'' }, [ [ ] ], { } ]"
        );
        JsonParser p = createParser(mode, DOC);

        // First, skipping of the whole thing
        assertToken(JsonToken.START_ARRAY, p.nextToken());
        p.skipChildren();
        assertEquals(JsonToken.END_ARRAY, p.currentToken());
        if (!isInputData) {
            JsonToken t = p.nextToken();
            if (t != null) {
                fail("Expected null at end of doc, got "+t);
            }
        }
        p.close();

        // Then individual ones
        p = createParser(mode, DOC);
        assertToken(JsonToken.START_ARRAY, p.nextToken());

        assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
        p.skipChildren();
        // shouldn't move
        assertToken(JsonToken.VALUE_NUMBER_INT, p.currentToken());
        assertEquals(1, p.getIntValue());

        assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
        // then skip array
        assertToken(JsonToken.START_ARRAY, p.nextToken());
        p.skipChildren();
        assertToken(JsonToken.END_ARRAY, p.currentToken());

        assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
        assertToken(JsonToken.START_OBJECT, p.nextToken());
        p.skipChildren();
        assertToken(JsonToken.END_OBJECT, p.currentToken());

        assertToken(JsonToken.START_ARRAY, p.nextToken());
        p.skipChildren();
        assertToken(JsonToken.END_ARRAY, p.currentToken());

        assertToken(JsonToken.START_OBJECT, p.nextToken());
        p.skipChildren();
        assertToken(JsonToken.END_OBJECT, p.currentToken());

        assertToken(JsonToken.END_ARRAY, p.nextToken());

        p.close();
    }

    @Test
    void nameEscaping() throws IOException
    {
        _testNameEscaping(MODE_INPUT_STREAM);
        _testNameEscaping(MODE_READER);
        _testNameEscaping(MODE_DATA_INPUT);
    }

    private void _testNameEscaping(int mode) throws IOException
    {
        final Map<String,String> NAME_MAP = new LinkedHashMap<>();
        NAME_MAP.put("", "");
        NAME_MAP.put("\\\"funny\\\"", "\"funny\"");
        NAME_MAP.put("\\\\", "\\");
        NAME_MAP.put("\\r", "\r");
        NAME_MAP.put("\\n", "\n");
        NAME_MAP.put("\\t", "\t");
        NAME_MAP.put("\\r\\n", "\r\n");
        NAME_MAP.put("\\\"\\\"", "\"\"");
        NAME_MAP.put("Line\\nfeed", "Line\nfeed");
        NAME_MAP.put("Yet even longer \\\"name\\\"!", "Yet even longer \"name\"!");

        int entry = 0;
        for (Map.Entry<String,String> en : NAME_MAP.entrySet()) {
            ++entry;
            String input = en.getKey();
            String expResult = en.getValue();
            final String DOC = "{ \""+input+"\":null}";
            JsonParser p = createParser(mode, DOC);

            assertToken(JsonToken.START_OBJECT, p.nextToken());
            assertToken(JsonToken.FIELD_NAME, p.nextToken());
            // first, sanity check (field name == getText()
            String act = p.currentName();
            assertEquals(act, getAndVerifyText(p));
            if (!expResult.equals(act)) {
                String msg = "Failed for name #"+entry+"/"+NAME_MAP.size();
                if (expResult.length() != act.length()) {
                    fail(msg+": exp length "+expResult.length()+", actual "+act.length());
                }
                assertEquals(expResult, act, msg);
            }
            assertToken(JsonToken.VALUE_NULL, p.nextToken());
            assertToken(JsonToken.END_OBJECT, p.nextToken());
            p.close();
        }
    }

    /**
     * Unit test that verifies that long text segments are handled
     * correctly; mostly to stress-test underlying segment-based
     * text buffer(s).
     */
    @Test
    void longText() throws Exception {
        // lengths chosen to tease out problems with buffer allocation...
        _testLongText(310);
        _testLongText(7700);
        _testLongText(49000);
        _testLongText(96000);
    }

    private void _testLongText(int LEN) throws Exception
    {
        StringBuilder sb = new StringBuilder(LEN + 100);
        Random r = new Random(LEN);
        while (sb.length() < LEN) {
            sb.append(r.nextInt());
            sb.append(" xyz foo");
            if (r.nextBoolean()) {
                sb.append(" and \"bar\"");
            } else if (r.nextBoolean()) {
                sb.append(" [whatever].... ");
            } else {
                // Let's try some more 'exotic' chars
                sb.append(" UTF-8-fu: try this {\u00E2/\u0BF8/\uA123!} (look funny?)");
            }
            if (r.nextBoolean()) {
                if (r.nextBoolean()) {
                    sb.append('\n');
                } else if (r.nextBoolean()) {
                    sb.append('\r');
                } else {
                    sb.append("\r\n");
                }
            }
        }
        final String VALUE = sb.toString();

        // Let's use real generator to get JSON done right
        StringWriter sw = new StringWriter(LEN + (LEN >> 2));
        JsonGenerator g = JSON_FACTORY.createGenerator(sw);
        g.writeStartObject();
        g.writeFieldName("doc");
        g.writeString(VALUE);
        g.writeEndObject();
        g.close();

        final String DOC = sw.toString();

        for (int type = 0; type < 4; ++type) {
            JsonParser p;
            switch (type) {
            case MODE_INPUT_STREAM:
            case MODE_READER:
            case MODE_DATA_INPUT:
                p = createParser(type, DOC);
                break;
            default:
                p = JSON_FACTORY.createParser(encodeInUTF32BE(DOC));
            }
            assertToken(JsonToken.START_OBJECT, p.nextToken());
            assertToken(JsonToken.FIELD_NAME, p.nextToken());
            assertEquals("doc", p.currentName());
            assertToken(JsonToken.VALUE_STRING, p.nextToken());

            String act = getAndVerifyText(p);
            if (act.length() != VALUE.length()) {
                fail("Expected length "+VALUE.length()+", got "+act.length()+" (mode = "+type+")");
            }
            if (!act.equals(VALUE)) {
                fail("Long text differs");
            }

            // should still know the field name
            assertEquals("doc", p.currentName());
            assertToken(JsonToken.END_OBJECT, p.nextToken());

            // InputDate somewhat special, so:
            if (type != MODE_DATA_INPUT) {
                assertNull(p.nextToken());
            }
            p.close();
        }
    }

    /**
     * Simple unit test that verifies that passing in a byte array
     * as source works as expected.
     */
    @Test
    void bytesAsSource() throws Exception
    {
        String JSON = "[ 1, 2, 3, 4 ]";
        byte[] b = JSON.getBytes("UTF-8");
        int offset = 50;
        int len = b.length;
        byte[] src = new byte[offset + len + offset];

        System.arraycopy(b, 0, src, offset, len);

        JsonParser p = JSON_FACTORY.createParser(src, offset, len);

        assertToken(JsonToken.START_ARRAY, p.nextToken());
        assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
        assertEquals(1, p.getIntValue());
        assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
        assertEquals(2, p.getIntValue());
        assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
        assertEquals(3, p.getIntValue());
        assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
        assertEquals(4, p.getIntValue());
        assertToken(JsonToken.END_ARRAY, p.nextToken());
        assertNull(p.nextToken());

        p.close();
    }

    @Test
    void utf8BOMHandling() throws Exception
    {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        // first, write BOM:
        bytes.write(0xEF);
        bytes.write(0xBB);
        bytes.write(0xBF);
        bytes.write("[ 1 ]".getBytes("UTF-8"));
        byte[] input = bytes.toByteArray();

        JsonParser p = JSON_FACTORY.createParser(input);
        assertEquals(JsonToken.START_ARRAY, p.nextToken());

        JsonLocation loc = p.currentTokenLocation();
        assertEquals(3, loc.getByteOffset());
        assertEquals(-1, loc.getCharOffset());
        assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken());
        assertEquals(JsonToken.END_ARRAY, p.nextToken());
        p.close();

        p = JSON_FACTORY.createParser(new MockDataInput(input));
        assertEquals(JsonToken.START_ARRAY, p.nextToken());
        // same BOM, but DataInput is more restrictive so can skip but offsets
        // are not reliable...
        loc = p.currentTokenLocation();
        assertNotNull(loc);
        assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken());
        assertEquals(JsonToken.END_ARRAY, p.nextToken());
        p.close();
    }

    // [core#48]
    @Test
    void spacesInURL() throws Exception
    {
        File f = File.createTempFile("pre fix&stuff", ".txt");
        BufferedWriter w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f), "UTF-8"));
        w.write("{ }");
        w.close();
        URL url = f.toURI().toURL();

        JsonParser p = JSON_FACTORY.createParser(url);
        assertToken(JsonToken.START_OBJECT, p.nextToken());
        assertToken(JsonToken.END_OBJECT, p.nextToken());
        p.close();
    }

    @Test
    void getValueAsTextBytes() throws Exception
    {
        _testGetValueAsText(MODE_INPUT_STREAM, false);
        _testGetValueAsText(MODE_INPUT_STREAM, true);
    }

    @Test
    void getValueAsTextDataInput() throws Exception
    {
        _testGetValueAsText(MODE_DATA_INPUT, false);
        _testGetValueAsText(MODE_DATA_INPUT, true);
    }

    @Test
    void getValueAsTextChars() throws Exception
    {
        _testGetValueAsText(MODE_READER, false);
        _testGetValueAsText(MODE_READER, true);
    }

    private void _testGetValueAsText(int mode, boolean delegate) throws Exception
    {
        String JSON = "{\"a\":1,\"b\":true,\"c\":null,\"d\":\"foo\"}";
        JsonParser p = createParser(mode, JSON);
        if (delegate) {
            p = new JsonParserDelegate(p);
        }

        assertToken(JsonToken.START_OBJECT, p.nextToken());
        assertNull(p.getValueAsString());
        assertEquals("foobar", p.getValueAsString("foobar"));

        assertToken(JsonToken.FIELD_NAME, p.nextToken());
        assertEquals("a", p.getText());
        assertEquals("a", p.getValueAsString());
        assertEquals("a", p.getValueAsString("default"));
        assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
        assertEquals("1", p.getValueAsString());

        assertToken(JsonToken.FIELD_NAME, p.nextToken());
        assertEquals("b", p.getValueAsString());
        assertToken(JsonToken.VALUE_TRUE, p.nextToken());
        assertEquals("true", p.getValueAsString());
        assertEquals("true", p.getValueAsString("foobar"));

        assertToken(JsonToken.FIELD_NAME, p.nextToken());
        assertEquals("c", p.getValueAsString());
        assertToken(JsonToken.VALUE_NULL, p.nextToken());
        // null token returned as Java null, as per javadoc
        assertNull(p.getValueAsString());

        assertToken(JsonToken.FIELD_NAME, p.nextToken());
        assertEquals("d", p.getValueAsString());
        assertToken(JsonToken.VALUE_STRING, p.nextToken());
        assertEquals("foo", p.getValueAsString("default"));
        assertEquals("foo", p.getValueAsString());

        assertToken(JsonToken.END_OBJECT, p.nextToken());
        assertNull(p.getValueAsString());

        // InputData can't peek into end-of-input so:
        if (mode != MODE_DATA_INPUT) {
            assertNull(p.nextToken());
        }
        p.close();
    }

    @Test
    void getTextViaWriter() throws Exception
    {
        for (int mode : ALL_MODES) {
            _testGetTextViaWriter(mode);
        }
    }

    private void _testGetTextViaWriter(int mode) throws Exception
    {
        final String INPUT_TEXT = "this is a sample text for json parsing using readText() method";
        final String JSON = "{\"a\":\""+INPUT_TEXT+"\",\"b\":true,\"c\":null,\"d\":\"foobar!\"}";
        JsonParser parser = createParser(mode, JSON);
        assertToken(JsonToken.START_OBJECT, parser.nextToken());
        assertToken(JsonToken.FIELD_NAME, parser.nextToken());
        assertEquals("a", parser.currentName());
        _getAndVerifyText(parser, "a");
        assertToken(JsonToken.VALUE_STRING, parser.nextToken());
        _getAndVerifyText(parser, INPUT_TEXT);
        assertToken(JsonToken.FIELD_NAME, parser.nextToken());
        assertEquals("b", parser.currentName());
        assertToken(JsonToken.VALUE_TRUE, parser.nextToken());
        _getAndVerifyText(parser, "true");
        assertToken(JsonToken.FIELD_NAME, parser.nextToken());
        assertEquals("c", parser.currentName());
        assertToken(JsonToken.VALUE_NULL, parser.nextToken());
        _getAndVerifyText(parser, "null");
        assertToken(JsonToken.FIELD_NAME, parser.nextToken());
        assertEquals("d", parser.currentName());
        assertToken(JsonToken.VALUE_STRING, parser.nextToken());
        _getAndVerifyText(parser, "foobar!");

        parser.close();
    }

    private void _getAndVerifyText(JsonParser p, String exp) throws Exception
    {
        Writer writer = new StringWriter();
        int len = p.getText(writer);
        String resultString = writer.toString();
        assertEquals(len, resultString.length());
        assertEquals(exp, resultString);
    }

    @Test
    void longerReadText() throws Exception
    {
        for (int mode : ALL_MODES) {
            _testLongerReadText(mode);
        }
    }

    private void _testLongerReadText(int mode) throws Exception
    {
        StringBuilder builder = new StringBuilder();
        for(int i= 0; i < 1000; i++) {
            builder.append("Sample Text"+i);
        }
        String longText = builder.toString();
        final String JSON = "{\"a\":\""+ longText +"\",\"b\":true,\"c\":null,\"d\":\"foo\"}";
        JsonParser parser = createParser(MODE_READER, JSON);
        assertToken(JsonToken.START_OBJECT, parser.nextToken());
        assertToken(JsonToken.FIELD_NAME, parser.nextToken());
        assertEquals("a", parser.currentName());
        assertToken(JsonToken.VALUE_STRING, parser.nextToken());

        Writer writer = new StringWriter();
        int len = parser.getText(writer);
        String resultString = writer.toString();
        assertEquals(len, resultString.length());
        assertEquals(longText, resultString);
        parser.close();
    }

    /*
    /**********************************************************
    /* Tests for Invalid input
    /**********************************************************
     */

    // [core#142]
    @Test
    void handlingOfInvalidSpaceByteStream() throws Exception {
        _testHandlingOfInvalidSpace(MODE_INPUT_STREAM);
        _testHandlingOfInvalidSpaceFromResource(true);
    }

    // [core#142]
    @Test
    void handlingOfInvalidSpaceChars() throws Exception {
        _testHandlingOfInvalidSpace(MODE_READER);
        _testHandlingOfInvalidSpaceFromResource(false);
    }

    // [core#142]
    @Test
    void handlingOfInvalidSpaceDataInput() throws Exception {
        _testHandlingOfInvalidSpace(MODE_DATA_INPUT);
    }

    private void _testHandlingOfInvalidSpace(int mode) throws Exception
    {
        final String JSON = "{ \u00A0 \"a\":1}";

        JsonParser p = createParser(mode, JSON);
        assertToken(JsonToken.START_OBJECT, p.nextToken());
        try {
            p.nextToken();
            fail("Should have failed");
        } catch (JsonParseException e) {
            verifyException(e, "unexpected character");
            // and correct error code
            verifyException(e, "code 160");
        }
        p.close();
    }

    private void _testHandlingOfInvalidSpaceFromResource(boolean useStream) throws Exception
    {
        InputStream in = getClass().getResourceAsStream("/test_0xA0.json");
        JsonParser p = useStream
                ? JSON_FACTORY.createParser(in)
                : JSON_FACTORY.createParser(new InputStreamReader(in, "UTF-8"));
        assertToken(JsonToken.START_OBJECT, p.nextToken());
        try {
            assertToken(JsonToken.FIELD_NAME, p.nextToken());
            assertEquals("request", p.currentName());
            assertToken(JsonToken.START_OBJECT, p.nextToken());
            assertToken(JsonToken.FIELD_NAME, p.nextToken());
            assertEquals("mac", p.currentName());
            assertToken(JsonToken.VALUE_STRING, p.nextToken());
            assertNotNull(p.getText());
            assertToken(JsonToken.FIELD_NAME, p.nextToken());
            assertEquals("data", p.currentName());
            assertToken(JsonToken.START_OBJECT, p.nextToken());

            // ... and from there on, just loop

            while (p.nextToken()  != null) { }
            fail("Should have failed");
        } catch (JsonParseException e) {
            verifyException(e, "unexpected character");
            // and correct error code
            verifyException(e, "code 160");
        }
        p.close();
    }

    @Test
    void invalidUtf8ValidUtf16() throws IOException {
        JsonFactory factory = new JsonFactoryBuilder()
                .disable(JsonFactory.Feature.CHARSET_DETECTION)
                .build();
        try (JsonParser parser = factory.createParser(new byte[]{0x22, 0x00, 0x22, 0x5b, 0x22, 0x00})) {
            try {
                //noinspection StatementWithEmptyBody
                while (parser.nextToken() != null) {
                }
                fail("Should have failed");
            } catch (JsonParseException e) {
                verifyException(e, "unquoted character");
            }
        }
    }

    /*
    /**********************************************************
    /* Helper methods
    /**********************************************************
     */

    private void _doTestSpec(boolean verify) throws IOException
    {
        JsonParser p;

        // First, using a StringReader:
        p = createParserUsingReader(JSON_FACTORY, SAMPLE_DOC_JSON_SPEC);
        verifyJsonSpecSampleDoc(p, verify);
        p.close();

        // Then with streams using supported encodings:
        p = createParserUsingStream(JSON_FACTORY, SAMPLE_DOC_JSON_SPEC, "UTF-8");
        verifyJsonSpecSampleDoc(p, verify);
        p.close();
        p = createParserUsingStream(JSON_FACTORY, SAMPLE_DOC_JSON_SPEC, "UTF-16BE");
        verifyJsonSpecSampleDoc(p, verify);
        p.close();
        p = createParserUsingStream(JSON_FACTORY, SAMPLE_DOC_JSON_SPEC, "UTF-16LE");
        verifyJsonSpecSampleDoc(p, verify);
        p.close();

        // Hmmh. UTF-32 is harder only because JDK doesn't come with
        // a codec for it. Can't test it yet using this method
        p = createParserUsingStream(JSON_FACTORY, SAMPLE_DOC_JSON_SPEC, "UTF-32");
        verifyJsonSpecSampleDoc(p, verify);
        p.close();

        // and finally, new (as of May 2016) source, DataInput:
        p = createParserForDataInput(JSON_FACTORY, new MockDataInput(SAMPLE_DOC_JSON_SPEC));
        verifyJsonSpecSampleDoc(p, verify);
        p.close();
    }

    protected void verifyJsonSpecSampleDoc(JsonParser p, boolean verifyContents)
        throws IOException
    {
        verifyJsonSpecSampleDoc(p, verifyContents, true);
    }

    protected void verifyJsonSpecSampleDoc(JsonParser p, boolean verifyContents,
            boolean requireNumbers)
        throws IOException
    {
        if (!p.hasCurrentToken()) {
            p.nextToken();
        }
        // first basic check, mostly for test coverage
        assertNull(p.getTypeId());
        assertNull(p.getObjectId());

        assertToken(JsonToken.START_OBJECT, p.currentToken()); // main object

        assertToken(JsonToken.FIELD_NAME, p.nextToken()); // 'Image'
        if (verifyContents) {
            verifyFieldName(p, "Image");
        }

        assertToken(JsonToken.START_OBJECT, p.nextToken()); // 'image' object

        assertToken(JsonToken.FIELD_NAME, p.nextToken()); // 'Width'
        if (verifyContents) {
            verifyFieldName(p, "Width");
        }

        verifyIntToken(p.nextToken(), requireNumbers);
        if (verifyContents) {
            verifyIntValue(p, SAMPLE_SPEC_VALUE_WIDTH);
        }

        assertToken(JsonToken.FIELD_NAME, p.nextToken()); // 'Height'
        if (verifyContents) {
            verifyFieldName(p, "Height");
        }

        verifyIntToken(p.nextToken(), requireNumbers);
        if (verifyContents) {
            verifyIntValue(p, SAMPLE_SPEC_VALUE_HEIGHT);
        }
        assertToken(JsonToken.FIELD_NAME, p.nextToken()); // 'Title'
        if (verifyContents) {
            verifyFieldName(p, "Title");
        }
        assertToken(JsonToken.VALUE_STRING, p.nextToken());
        assertEquals(SAMPLE_SPEC_VALUE_TITLE, getAndVerifyText(p));
        assertToken(JsonToken.FIELD_NAME, p.nextToken()); // 'Thumbnail'
        if (verifyContents) {
            verifyFieldName(p, "Thumbnail");
        }

        assertToken(JsonToken.START_OBJECT, p.nextToken()); // 'thumbnail' object
        assertToken(JsonToken.FIELD_NAME, p.nextToken()); // 'Url'
        if (verifyContents) {
            verifyFieldName(p, "Url");
        }
        assertToken(JsonToken.VALUE_STRING, p.nextToken());
        if (verifyContents) {
            assertEquals(SAMPLE_SPEC_VALUE_TN_URL, getAndVerifyText(p));
        }
        assertToken(JsonToken.FIELD_NAME, p.nextToken()); // 'Height'
        if (verifyContents) {
            verifyFieldName(p, "Height");
        }
        verifyIntToken(p.nextToken(), requireNumbers);
        if (verifyContents) {
            verifyIntValue(p, SAMPLE_SPEC_VALUE_TN_HEIGHT);
        }
        assertToken(JsonToken.FIELD_NAME, p.nextToken()); // 'Width'
        if (verifyContents) {
            verifyFieldName(p, "Width");
        }
        // Width value is actually a String in the example
        assertToken(JsonToken.VALUE_STRING, p.nextToken());
        if (verifyContents) {
            assertEquals(SAMPLE_SPEC_VALUE_TN_WIDTH, getAndVerifyText(p));
        }

        assertToken(JsonToken.END_OBJECT, p.nextToken()); // 'thumbnail' object
        assertToken(JsonToken.FIELD_NAME, p.nextToken()); // 'IDs'
        assertToken(JsonToken.START_ARRAY, p.nextToken()); // 'ids' array
        verifyIntToken(p.nextToken(), requireNumbers); // ids[0]
        if (verifyContents) {
            verifyIntValue(p, SAMPLE_SPEC_VALUE_TN_ID1);
        }
        verifyIntToken(p.nextToken(), requireNumbers); // ids[1]
        if (verifyContents) {
            verifyIntValue(p, SAMPLE_SPEC_VALUE_TN_ID2);
        }
        verifyIntToken(p.nextToken(), requireNumbers); // ids[2]
        if (verifyContents) {
            verifyIntValue(p, SAMPLE_SPEC_VALUE_TN_ID3);
        }
        verifyIntToken(p.nextToken(), requireNumbers); // ids[3]
        if (verifyContents) {
            verifyIntValue(p, SAMPLE_SPEC_VALUE_TN_ID4);
        }
        assertToken(JsonToken.END_ARRAY, p.nextToken()); // 'ids' array

        assertToken(JsonToken.END_OBJECT, p.nextToken()); // 'image' object

        assertToken(JsonToken.END_OBJECT, p.nextToken()); // main object
    }

    private void verifyIntToken(JsonToken t, boolean requireNumbers)
    {
        if (t == JsonToken.VALUE_NUMBER_INT) {
            return;
        }
        if (requireNumbers) { // to get error
            assertToken(JsonToken.VALUE_NUMBER_INT, t);
        }
        // if not number, must be String
        if (t != JsonToken.VALUE_STRING) {
            fail("Expected INT or STRING value, got "+t);
        }
    }

    protected void verifyFieldName(JsonParser p, String expName)
        throws IOException
    {
        assertEquals(expName, p.getText());
        assertEquals(expName, p.currentName());
    }

    protected void verifyIntValue(JsonParser p, long expValue)
        throws IOException
    {
        // First, via textual
        assertEquals(String.valueOf(expValue), p.getText());
    }
}