JsonFactoryTest.java

package tools.jackson.core.unittest.json;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;

import org.junit.jupiter.api.Test;

import tools.jackson.core.*;
import tools.jackson.core.io.SerializedString;
import tools.jackson.core.json.*;
import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser;
import tools.jackson.core.unittest.*;

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

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

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

    private final JsonFactory JSON_F = newStreamFactory();

    @Test
    public void testStreamWriteFeatures() throws Exception
    {
        JsonFactory f = JsonFactory.builder()
                .enable(StreamWriteFeature.IGNORE_UNKNOWN)
                .build();
        assertTrue(f.isEnabled(StreamWriteFeature.IGNORE_UNKNOWN));
        f = f.rebuild().configure(StreamWriteFeature.IGNORE_UNKNOWN, false)
                .build();
        assertFalse(f.isEnabled(StreamWriteFeature.IGNORE_UNKNOWN));
    }

    @Test
    public void testJsonWriteFeatures() throws Exception
    {
        JsonFactory f = JsonFactory.builder()
                .enable(JsonWriteFeature.QUOTE_PROPERTY_NAMES)
                .build();
        assertTrue(f.isEnabled(JsonWriteFeature.QUOTE_PROPERTY_NAMES));
        f = f.rebuild().configure(JsonWriteFeature.QUOTE_PROPERTY_NAMES, false)
                .build();
        assertFalse(f.isEnabled(JsonWriteFeature.QUOTE_PROPERTY_NAMES));
    }

    @Test
    public void testFactoryFeatures() throws Exception
    {
        JsonFactory f = JsonFactory.builder()
                .enable(TokenStreamFactory.Feature.INTERN_PROPERTY_NAMES)
                .build();
        assertTrue(f.isEnabled(JsonFactory.Feature.INTERN_PROPERTY_NAMES));

        f = f.rebuild()
                .disable(JsonFactory.Feature.INTERN_PROPERTY_NAMES)
                .build();
        assertFalse(f.isEnabled(JsonFactory.Feature.INTERN_PROPERTY_NAMES));

        assertFalse(f.canHandleBinaryNatively());
    }

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

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

        assertEquals(JsonReadFeature.class, JSON_F.getFormatReadFeatureType());
        assertEquals(JsonWriteFeature.class, JSON_F.getFormatWriteFeatureType());
    }

    @Test
    public void testJsonWithFiles() 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 g = f.createGenerator(ObjectWriteContext.empty(), file, JsonEncoding.UTF16_LE);
        g.writeStartObject();
        g.writeRaw("   ");
        g.writeEndObject();
        g.close();

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

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

    @Test
    public void testCopy() throws Exception
    {
        JsonFactory f = new JsonFactory();
        // first, verify defaults

        // since 3.0:
        assertFalse(f.isEnabled(JsonFactory.Feature.INTERN_PROPERTY_NAMES));
        assertFalse(f.isEnabled(JsonReadFeature.ALLOW_JAVA_COMMENTS));
        assertFalse(f.isEnabled(JsonWriteFeature.ESCAPE_NON_ASCII));

        f = f.rebuild()
                .enable(JsonFactory.Feature.INTERN_PROPERTY_NAMES)
                .enable(JsonReadFeature.ALLOW_JAVA_COMMENTS)
                .enable(JsonWriteFeature.ESCAPE_NON_ASCII)
                .build();
        // then change, verify that changes "stick"
        assertTrue(f.isEnabled(JsonFactory.Feature.INTERN_PROPERTY_NAMES));
        assertTrue(f.isEnabled(JsonReadFeature.ALLOW_JAVA_COMMENTS));
        assertTrue(f.isEnabled(JsonWriteFeature.ESCAPE_NON_ASCII));

        JsonFactory jf2 = f.copy();
        assertTrue(jf2.isEnabled(JsonFactory.Feature.INTERN_PROPERTY_NAMES));
        assertTrue(f.isEnabled(JsonReadFeature.ALLOW_JAVA_COMMENTS));
        assertTrue(f.isEnabled(JsonWriteFeature.ESCAPE_NON_ASCII));
    }

    @Test
    public void testRootValues() throws Exception
    {
        assertEquals(" ", JSON_F.getRootValueSeparator());
        JsonFactoryBuilder b = JsonFactory.builder()
                .rootValueSeparator("/");
        assertEquals(new SerializedString("/"), b.rootValueSeparator());
        JsonFactory f = b.build();
        assertEquals("/", f.getRootValueSeparator());

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

    @Test
    public void test_createGenerator_OutputStream() throws Exception
    {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        JsonGenerator jsonGenerator = new JsonFactory()
                .createGenerator(ObjectWriteContext.empty(), outputStream);

        jsonGenerator.writeString("value");
        jsonGenerator.close();

        assertEquals(new String(outputStream.toByteArray(), StandardCharsets.UTF_8), "\"value\"");

        // the stream has not been closed by close
        outputStream.write(1);
    }

    @Test
    public void test_createGenerator_File() throws Exception
    {
        Path path = Files.createTempFile("", "");
        JsonGenerator jsonGenerator = new JsonFactory()
                .createGenerator(ObjectWriteContext.empty(), path.toFile(), JsonEncoding.UTF8);

        jsonGenerator.writeString("value");
        jsonGenerator.close();

        assertEquals(new String(Files.readAllBytes(path), StandardCharsets.UTF_8), "\"value\"");
    }

    @Test
    public void test_createGenerator_Path() throws Exception
    {
        Path path = Files.createTempFile("", "");
        JsonGenerator jsonGenerator = new JsonFactory()
                .createGenerator(ObjectWriteContext.empty(), path, JsonEncoding.UTF8);

        jsonGenerator.writeString("value");
        jsonGenerator.close();

        assertEquals(new String(Files.readAllBytes(path), StandardCharsets.UTF_8), "\"value\"");
    }

    @Test
    public void test_createGenerator_Writer() throws Exception
    {
        Writer writer = new StringWriter();
        JsonGenerator jsonGenerator = new JsonFactory()
                .createGenerator(ObjectWriteContext.empty(), writer);

        jsonGenerator.writeString("value");
        jsonGenerator.close();

        assertEquals(writer.toString(), "\"value\"");

        // the writer has not been closed by close
        writer.append('1');
    }

    @Test
    public void test_createGenerator_DataOutput() throws Exception
    {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        DataOutput dataOutput = new DataOutputStream(outputStream);
        JsonGenerator jsonGenerator = new JsonFactory()
                .createGenerator(ObjectWriteContext.empty(), dataOutput);

        jsonGenerator.writeString("value");
        jsonGenerator.close();

        assertEquals(new String(outputStream.toByteArray(), StandardCharsets.UTF_8), "\"value\"");

        // the data output has not been closed by close
        dataOutput.write(1);
    }

    @Test
    public void test_createParser_InputStream() throws Exception
    {
        InputStream inputStream = new ByteArrayInputStream("\"value\"".getBytes(StandardCharsets.UTF_8));
        JsonParser jsonParser = new JsonFactory()
                .createParser(ObjectReadContext.empty(), inputStream);

        assertEquals(jsonParser.nextStringValue(), "value");
    }

    @Test
    public void test_createParser_File() throws Exception
    {
        Path path = Files.createTempFile("", "");
        Files.write(path, "\"value\"".getBytes(StandardCharsets.UTF_8));
        JsonParser jsonParser = new JsonFactory()
                .createParser(ObjectReadContext.empty(), path.toFile());

        assertEquals(jsonParser.nextStringValue(), "value");
    }

    @Test
    public void test_createParser_Path() throws Exception
    {
        Path path = Files.createTempFile("", "");
        Files.write(path, "\"value\"".getBytes(StandardCharsets.UTF_8));
        JsonParser jsonParser = new JsonFactory()
                .createParser(ObjectReadContext.empty(), path);

        assertEquals(jsonParser.nextStringValue(), "value");
    }

    @Test
    public void test_createParser_Reader() throws Exception
    {
        Reader reader = new StringReader("\"value\"");
        JsonParser jsonParser = new JsonFactory()
                .createParser(ObjectReadContext.empty(), reader);

        assertEquals(jsonParser.nextStringValue(), "value");
    }

    @Test
    public void test_createParser_ByteArray() throws Exception
    {
        byte[] bytes = "\"value\"".getBytes(StandardCharsets.UTF_8);
        JsonParser jsonParser = new JsonFactory()
                .createParser(ObjectReadContext.empty(), bytes);

        assertEquals(jsonParser.nextStringValue(), "value");
    }

    @Test
    public void test_createParser_String() throws Exception
    {
        String string = "\"value\"";
        JsonParser jsonParser = new JsonFactory()
                .createParser(ObjectReadContext.empty(), string);

        assertEquals(jsonParser.nextStringValue(), "value");
    }

    @Test
    public void test_createParser_CharArray() throws Exception
    {
        char[] chars = "\"value\"".toCharArray();
        JsonParser jsonParser = new JsonFactory()
                .createParser(ObjectReadContext.empty(), chars);

        assertEquals(jsonParser.nextStringValue(), "value");
    }

    @Test
    public void test_createParser_DataInput() throws Exception
    {
        InputStream inputStream = new ByteArrayInputStream("\"value\"".getBytes(StandardCharsets.UTF_8));
        DataInput dataInput = new DataInputStream(inputStream);
        JsonParser jsonParser = new JsonFactory()
                .createParser(ObjectReadContext.empty(), dataInput);

        assertEquals(jsonParser.nextStringValue(), "value");
    }

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

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

    @Test
    public void testBuilderWithJackson2Defaults() {
        JsonFactory factory = JsonFactory.builderWithJackson2Defaults().build();
        assertFalse(factory.isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
        assertFalse(factory.isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER));
        assertFalse(factory.isEnabled(JsonWriteFeature.ESCAPE_FORWARD_SLASHES));
        assertFalse(factory.isEnabled(JsonWriteFeature.COMBINE_UNICODE_SURROGATES_IN_UTF8));
    }

    // 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_PROPERTY_NAMES, canonicalize)
                .build();
        try (JsonParser parser = factory.createParser(ObjectReadContext.empty(), contents)) {
            verifyCanonicalizationTestResult(parser, canonicalize);
        }
        try (JsonParser parser = factory.createParser(ObjectReadContext.empty(), 
                contents.getBytes(StandardCharsets.UTF_8))) {
            verifyCanonicalizationTestResult(parser, canonicalize);
        }
        try (JsonParser parser = factory.createParser(ObjectReadContext.empty(),
                new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8)))) {
            verifyCanonicalizationTestResult(parser, canonicalize);
        }
        try (JsonParser parser = factory.createParser(ObjectReadContext.empty(), 
                new StringReader(contents))) {
            verifyCanonicalizationTestResult(parser, canonicalize);
        }
        try (JsonParser parser = factory.createParser(ObjectReadContext.empty(),
                (DataInput) new DataInputStream(
                new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8))))) {
            verifyCanonicalizationTestResult(parser, canonicalize);
        }
        try (NonBlockingByteArrayJsonParser parser = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty())) {
            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.nextName();
        assertEquals("a", field1);
        assertToken(JsonToken.VALUE_TRUE, parser.nextToken());
        String field2 = parser.nextName();
        assertEquals("a", field2);
        if (canonicalize) {
            assertSame(field1, field2);
        } else {
            // n.b. It's possible that this may flake if a garbage collector with string deduplication
            // enabled is used. Such a failure is unlikely because younger GC generations are typically
            // not considered for deduplication 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());
    }
}