JsonFactoryTest.java

package com.fasterxml.jackson.core.json;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.json.async.NonBlockingJsonParser;
import com.fasterxml.jackson.core.type.ResolvedType;
import com.fasterxml.jackson.core.type.TypeReference;

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

class JsonFactoryTest
        extends com.fasterxml.jackson.core.JUnit5TestBase
{
    static class BogusCodec extends ObjectCodec {
        @Override
        public Version version() { return null; }

        @Override
        public <T> T readValue(JsonParser p, Class<T> valueType) throws IOException {
            return null;
        }

        @Override
        public <T> T readValue(JsonParser p, TypeReference<T> valueTypeRef) throws IOException {
            return null;
        }

        @Override
        public <T> T readValue(JsonParser p, ResolvedType valueType) throws IOException {
            return null;
        }

        @Override
        public <T> Iterator<T> readValues(JsonParser p, Class<T> valueType) throws IOException {
            return null;
        }

        @Override
        public <T> Iterator<T> readValues(JsonParser p, TypeReference<T> valueTypeRef) throws IOException {
            return null;
        }

        @Override
        public <T> Iterator<T> readValues(JsonParser p, ResolvedType valueType) throws IOException {
            return null;
        }

        @Override
        public void writeValue(JsonGenerator gen, Object value) throws IOException {
        }

        @Override
        public <T extends TreeNode> T readTree(JsonParser p) throws IOException {
            return null;
        }

        @Override
        public void writeTree(JsonGenerator gen, TreeNode tree) throws IOException {
        }

        @Override
        public TreeNode createObjectNode() {
            return null;
        }

        @Override
        public TreeNode createArrayNode() {
            return null;
        }

        @Override
        public JsonParser treeAsTokens(TreeNode n) {
            return null;
        }

        @Override
        public <T> T treeToValue(TreeNode n, Class<T> valueType) throws JsonProcessingException {
            return null;
        }

        @Override
        public TreeNode missingNode() {
            return null;
        }

        @Override
        public TreeNode nullNode() {
            return null;
        }
    }

    // for testing [core#460]
    @SuppressWarnings("serial")
    static class CustomFactory extends JsonFactory {
        public CustomFactory(JsonFactory f, ObjectCodec codec) {
            super(f, codec);
        }
    }

    static class BogusSchema implements FormatSchema
    {
        @Override
        public String getSchemaType() {
            return "test";
        }
    }

    /*
    /**********************************************************************
    /* Test methods
    /**********************************************************************
     */

    final JsonFactory JSON_F = sharedStreamFactory();

    @SuppressWarnings("deprecation")
    @Test
    void generatorFeatures() throws Exception
    {
        assertNull(JSON_F.getCodec());

        JsonFactory f = JsonFactory.builder()
                .configure(JsonWriteFeature.QUOTE_FIELD_NAMES, true)
                .build();
        // 24-Oct-2018, tatu: Until 3.x, we'll only have backwards compatible
        assertTrue(f.isEnabled(JsonGenerator.Feature.QUOTE_FIELD_NAMES));
        f = JsonFactory.builder()
                .configure(JsonWriteFeature.QUOTE_FIELD_NAMES, false)
                .build();
        assertFalse(f.isEnabled(JsonGenerator.Feature.QUOTE_FIELD_NAMES));
    }

    @Test
    void factoryFeatures() throws Exception
    {
        JsonFactory f = JsonFactory.builder()
                .configure(JsonFactory.Feature.INTERN_FIELD_NAMES, false)
                .build();
        assertFalse(f.isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));

        // by default, should be enabled
        assertTrue(f.isEnabled(JsonFactory.Feature.USE_THREAD_LOCAL_FOR_BUFFER_RECYCLING));

        assertFalse(JSON_F.requiresCustomCodec());
        assertFalse(JSON_F.canHandleBinaryNatively());
    }

    @Test
    void factoryMisc() throws Exception
    {
        assertNull(JSON_F.getInputDecorator());
        assertNull(JSON_F.getOutputDecorator());

        assertFalse(JSON_F.canUseSchema(null));
        assertFalse(JSON_F.canUseSchema(new BogusSchema()));

        assertNull(JSON_F.getFormatReadFeatureType());
        assertNull(JSON_F.getFormatWriteFeatureType());

        assertEquals(0, JSON_F.getFormatParserFeatures());
        assertEquals(0, JSON_F.getFormatGeneratorFeatures());
    }

    // for [core#189]: verify that it's ok to disable recycling
    // Basically simply exercises basic functionality, to ensure
    // there are no obvious problems; needed since testing never
    // disables this handling otherwise
    @Test
    void disablingBufferRecycling() throws Exception
    {
        JsonFactory f = JsonFactory.builder()
                .disable(JsonFactory.Feature.USE_THREAD_LOCAL_FOR_BUFFER_RECYCLING)
                .build();
        assertFalse(f.isEnabled(JsonFactory.Feature.USE_THREAD_LOCAL_FOR_BUFFER_RECYCLING));

        // First, generation
        for (int i = 0; i < 3; ++i) {
            StringWriter w = new StringWriter();
            JsonGenerator gen = f.createGenerator(w);
            gen.writeStartObject();
            gen.writeEndObject();
            gen.close();
            assertEquals("{}", w.toString());
        }

        for (int i = 0; i < 3; ++i) {
            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
            JsonGenerator gen = f.createGenerator(bytes);
            gen.writeStartArray();
            gen.writeEndArray();
            gen.close();
            assertEquals("[]", bytes.toString("UTF-8"));
        }

        // Then parsing:
        for (int i = 0; i < 3; ++i) {
            JsonParser p = f.createParser("{}");
            assertToken(JsonToken.START_OBJECT, p.nextToken());
            assertToken(JsonToken.END_OBJECT, p.nextToken());
            assertNull(p.nextToken());
            p.close();

            p = f.createParser("{}".getBytes("UTF-8"));
            assertToken(JsonToken.START_OBJECT, p.nextToken());
            assertToken(JsonToken.END_OBJECT, p.nextToken());
            assertNull(p.nextToken());
            p.close();
        }
    }

    @Test
    void jsonWithFiles() throws Exception
    {
        File file = File.createTempFile("jackson-test", null);
        file.deleteOnExit();

        JsonFactory f = new JsonFactory();

        // First: create file via generator.. and use an odd encoding
        JsonGenerator jg = f.createGenerator(file, JsonEncoding.UTF16_LE);
        jg.writeStartObject();
        jg.writeRaw("   ");
        jg.writeEndObject();
        jg.close();

        // Ok: first read file directly
        JsonParser jp = f.createParser(file);
        assertToken(JsonToken.START_OBJECT, jp.nextToken());
        assertToken(JsonToken.END_OBJECT, jp.nextToken());
        assertNull(jp.nextToken());
        jp.close();

        // Then via URL:
        jp = f.createParser(file.toURI().toURL());
        assertToken(JsonToken.START_OBJECT, jp.nextToken());
        assertToken(JsonToken.END_OBJECT, jp.nextToken());
        assertNull(jp.nextToken());
        jp.close();

        // ok, delete once we are done
        file.delete();
    }

    // #72
    @SuppressWarnings("deprecation")
    @Test
    void copy() throws Exception
    {
        JsonFactory jf = new JsonFactory();
        // first, verify defaults
        assertNull(jf.getCodec());
        assertTrue(jf.isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
        assertFalse(jf.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
        assertFalse(jf.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII));

        // then change, verify that changes "stick"
        jf = JsonFactory.builder()
                .disable(JsonFactory.Feature.INTERN_FIELD_NAMES)
                .enable(JsonReadFeature.ALLOW_JAVA_COMMENTS)
                .enable(JsonWriteFeature.ESCAPE_NON_ASCII)
                .build();
        ObjectCodec codec = new BogusCodec();
        jf.setCodec(codec);

        assertFalse(jf.isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
        assertTrue(jf.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
        assertTrue(jf.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII));
        assertSame(codec, jf.getCodec());

        JsonFactory jf2 = jf.copy();
        assertFalse(jf2.isEnabled(JsonFactory.Feature.INTERN_FIELD_NAMES));
        assertTrue(jf2.isEnabled(JsonParser.Feature.ALLOW_COMMENTS));
        assertTrue(jf2.isEnabled(JsonGenerator.Feature.ESCAPE_NON_ASCII));
        // 16-May-2018, tatu: But! Note that despited [core#460], this should NOT copy it back
        assertNull(jf2.getCodec());

        // However: real copy constructor SHOULD copy it
        JsonFactory jf3 = new CustomFactory(jf, codec);
        assertSame(codec, jf3.getCodec());
    }

    @Test
    void rootValues() throws Exception
    {
        JsonFactory f = new JsonFactory();
        assertEquals(" ", f.getRootValueSeparator());
        f.setRootValueSeparator("/");
        assertEquals("/", f.getRootValueSeparator());

        // but also test it is used
        StringWriter w = new StringWriter();
        JsonGenerator g = f.createGenerator(w);
        g.writeNumber(1);
        g.writeNumber(2);
        g.writeNumber(3);
        g.close();
        assertEquals("1/2/3", w.toString());
    }

    @Test
    void canonicalizationEnabled() throws Exception {
        doCanonicalizationTest(false);
    }

    @Test
    void canonicalizationDisabled() throws Exception {
        doCanonicalizationTest(false);
    }

    // Configure the JsonFactory as expected, and verify across common shapes of input
    // to cover common JsonParser implementations.
    private void doCanonicalizationTest(boolean canonicalize) throws Exception {
        String contents = "{\"a\":true,\"a\":true}";
        JsonFactory factory = JsonFactory.builder()
                .configure(JsonFactory.Feature.CANONICALIZE_FIELD_NAMES, canonicalize)
                .build();
        try (JsonParser parser = factory.createParser(contents)) {
            verifyCanonicalizationTestResult(parser, canonicalize);
        }
        try (JsonParser parser = factory.createParser(contents.getBytes(StandardCharsets.UTF_8))) {
            verifyCanonicalizationTestResult(parser, canonicalize);
        }
        try (JsonParser parser = factory.createParser(
                new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8)))) {
            verifyCanonicalizationTestResult(parser, canonicalize);
        }
        try (JsonParser parser = factory.createParser(new StringReader(contents))) {
            verifyCanonicalizationTestResult(parser, canonicalize);
        }
        try (JsonParser parser = factory.createParser((DataInput) new DataInputStream(
                new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8))))) {
            verifyCanonicalizationTestResult(parser, canonicalize);
        }
        try (NonBlockingJsonParser parser = (NonBlockingJsonParser) factory.createNonBlockingByteArrayParser()) {
            byte[] data = contents.getBytes(StandardCharsets.UTF_8);
            parser.feedInput(data, 0, data.length);
            parser.endOfInput();
            verifyCanonicalizationTestResult(parser, canonicalize);
        }
    }

    private void verifyCanonicalizationTestResult(JsonParser parser, boolean canonicalize) throws Exception {
        assertToken(JsonToken.START_OBJECT, parser.nextToken());
        String field1 = parser.nextFieldName();
        assertEquals("a", field1);
        assertToken(JsonToken.VALUE_TRUE, parser.nextToken());
        String field2 = parser.nextFieldName();
        assertEquals("a", field2);
        if (canonicalize) {
            assertSame(field1, field2);
        } else {
            // n.b. It's possible that this may flake if a garbage collector with string de-duplication
            // enabled is used. Such a failure is unlikely because younger GC generations are typically
            // not considered for de-duplication due to high churn, but under heavy memory pressure it
            // may be possible. I've left this comment in an attempt to simplify investigation in the
            // off-chance that such flakes eventually occur.
            assertNotSame(field1, field2);
        }
        assertToken(JsonToken.VALUE_TRUE, parser.nextToken());
        assertToken(JsonToken.END_OBJECT, parser.nextToken());
    }
}