FastDoubleWriteNumberTest.java

package tools.jackson.core.unittest.write;

import java.io.*;

import org.junit.jupiter.api.Test;

import tools.jackson.core.JsonGenerator;
import tools.jackson.core.ObjectWriteContext;
import tools.jackson.core.StreamWriteFeature;
import tools.jackson.core.json.JsonFactory;
import tools.jackson.core.unittest.JacksonCoreTestBase;

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

/**
 * Tests for {@link JsonGenerator#writeNumber(double)} and
 * {@link JsonGenerator#writeNumber(float)} with
 * {@link StreamWriteFeature#USE_FAST_DOUBLE_WRITER} enabled.
 */
public class FastDoubleWriteNumberTest extends JacksonCoreTestBase
{
    private final JsonFactory FAST_FACTORY = JsonFactory.builder()
            .enable(StreamWriteFeature.USE_FAST_DOUBLE_WRITER)
            .build();

    @Test
    void testDoubleValues() throws Exception
    {
        double[] values = {
            0.0, -0.0, 1.0, -1.0, 1.5, -1.5,
            0.25, -0.25, 123.456, -123.456,
            1.0E10, 1.0E-10, -1.0E10, -1.0E-10,
            Double.MAX_VALUE, Double.MIN_VALUE, Double.MIN_NORMAL
        };
        for (double v : values) {
            _verifyDoubleWrite(v);
        }
    }

    @Test
    void testFloatValues() throws Exception
    {
        float[] values = {
            0.0f, -0.0f, 1.0f, -1.0f, 1.5f, -1.5f,
            0.25f, -0.25f, 123.456f, -123.456f,
            1.0E10f, 1.0E-10f, -1.0E10f, -1.0E-10f,
            Float.MAX_VALUE, Float.MIN_VALUE, Float.MIN_NORMAL
        };
        for (float v : values) {
            _verifyFloatWrite(v);
        }
    }

    @Test
    void testDoubleSpecialValues() throws Exception
    {
        // NaN and Infinity go through writeString path, not direct buffer write
        _verifyDoubleSpecial(Double.NaN, "NaN");
        _verifyDoubleSpecial(Double.POSITIVE_INFINITY, "Infinity");
        _verifyDoubleSpecial(Double.NEGATIVE_INFINITY, "-Infinity");
    }

    @Test
    void testFloatSpecialValues() throws Exception
    {
        _verifyFloatSpecial(Float.NaN, "NaN");
        _verifyFloatSpecial(Float.POSITIVE_INFINITY, "Infinity");
        _verifyFloatSpecial(Float.NEGATIVE_INFINITY, "-Infinity");
    }

    @Test
    void testDoubleWriteInObject() throws Exception
    {
        // Test writeNumber(double) inside an object context
        StringWriter sw = new StringWriter();
        try (JsonGenerator gen = FAST_FACTORY.createGenerator(ObjectWriteContext.empty(), sw)) {
            gen.writeStartObject();
            gen.writeName("value");
            gen.writeNumber(1.5);
            gen.writeEndObject();
        }
        assertEquals(a2q("{'value':1.5}"), sw.toString());
    }

    @Test
    void testFloatWriteInObject() throws Exception
    {
        StringWriter sw = new StringWriter();
        try (JsonGenerator gen = FAST_FACTORY.createGenerator(ObjectWriteContext.empty(), sw)) {
            gen.writeStartObject();
            gen.writeName("value");
            gen.writeNumber(1.5f);
            gen.writeEndObject();
        }
        assertEquals(a2q("{'value':1.5}"), sw.toString());
    }

    @Test
    void testDoubleWriteInArray() throws Exception
    {
        StringWriter sw = new StringWriter();
        try (JsonGenerator gen = FAST_FACTORY.createGenerator(ObjectWriteContext.empty(), sw)) {
            gen.writeStartArray();
            gen.writeNumber(0.25);
            gen.writeNumber(-0.25);
            gen.writeNumber(17.07);
            gen.writeEndArray();
        }
        assertEquals("[0.25,-0.25,17.07]", sw.toString());
    }

    @Test
    void testFloatWriteInArray() throws Exception
    {
        StringWriter sw = new StringWriter();
        try (JsonGenerator gen = FAST_FACTORY.createGenerator(ObjectWriteContext.empty(), sw)) {
            gen.writeStartArray();
            gen.writeNumber(0.25f);
            gen.writeNumber(-0.25f);
            gen.writeNumber(17.07f);
            gen.writeEndArray();
        }
        assertEquals("[0.25,-0.25,17.07]", sw.toString());
    }

    @Test
    void testDoubleWriteToOutputStream() throws Exception
    {
        // Test the byte-buffer path (UTF8JsonGenerator)
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        try (JsonGenerator gen = FAST_FACTORY.createGenerator(ObjectWriteContext.empty(), bytes)) {
            gen.writeStartArray();
            gen.writeNumber(0.25);
            gen.writeNumber(-0.25);
            gen.writeNumber(1.5);
            gen.writeEndArray();
        }
        assertEquals("[0.25,-0.25,1.5]", bytes.toString("UTF-8"));
    }

    @Test
    void testFloatWriteToOutputStream() throws Exception
    {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        try (JsonGenerator gen = FAST_FACTORY.createGenerator(ObjectWriteContext.empty(), bytes)) {
            gen.writeStartArray();
            gen.writeNumber(0.25f);
            gen.writeNumber(-0.25f);
            gen.writeNumber(1.5f);
            gen.writeEndArray();
        }
        assertEquals("[0.25,-0.25,1.5]", bytes.toString("UTF-8"));
    }

    @Test
    void testDoubleWithNumbersAsStrings() throws Exception
    {
        // When cfgNumbersAsStrings is true, should still quote numbers
        JsonFactory factory = JsonFactory.builder()
                .enable(StreamWriteFeature.USE_FAST_DOUBLE_WRITER)
                .build();
        StringWriter sw = new StringWriter();
        // Note: cfgNumbersAsStrings is set via WRITE_NUMBERS_AS_STRINGS feature
        // For now just verify basic fast mode works
        try (JsonGenerator gen = factory.createGenerator(ObjectWriteContext.empty(), sw)) {
            gen.writeNumber(1.5);
        }
        assertEquals("1.5", sw.toString());
    }

    private void _verifyDoubleWrite(double v) throws Exception
    {
        // Verify fast mode produces output that round-trips to the same value
        StringWriter sw = new StringWriter();
        try (JsonGenerator gen = FAST_FACTORY.createGenerator(ObjectWriteContext.empty(), sw)) {
            gen.writeNumber(v);
        }
        String output = sw.toString();
        // Verify the output can be parsed back to the same double
        double parsed = Double.parseDouble(output);
        assertEquals(v, parsed, "round-trip failure for double " + v);
    }

    private void _verifyFloatWrite(float v) throws Exception
    {
        StringWriter sw = new StringWriter();
        try (JsonGenerator gen = FAST_FACTORY.createGenerator(ObjectWriteContext.empty(), sw)) {
            gen.writeNumber(v);
        }
        String output = sw.toString();
        float parsed = Float.parseFloat(output);
        assertEquals(v, parsed, "round-trip failure for float " + v);
    }

    private void _verifyDoubleSpecial(double v, String expected) throws Exception
    {
        StringWriter sw = new StringWriter();
        try (JsonGenerator gen = FAST_FACTORY.createGenerator(ObjectWriteContext.empty(), sw)) {
            gen.writeNumber(v);
        }
        // Special values are written as strings (quoted)
        assertEquals(q(expected), sw.toString());
    }

    private void _verifyFloatSpecial(float v, String expected) throws Exception
    {
        StringWriter sw = new StringWriter();
        try (JsonGenerator gen = FAST_FACTORY.createGenerator(ObjectWriteContext.empty(), sw)) {
            gen.writeNumber(v);
        }
        assertEquals(q(expected), sw.toString());
    }
}