FastDoubleBufferBoundaryTest.java

package tools.jackson.core.unittest.write;

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

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.json.JsonWriteFeature;
import tools.jackson.core.unittest.JacksonCoreTestBase;

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

/**
 * Verifies that the {@link StreamWriteFeature#USE_FAST_DOUBLE_WRITER} direct-to-buffer
 * path reserves enough room for the longest possible output, at every alignment of the
 * value against the end of the output buffer.
 */
public class FastDoubleBufferBoundaryTest extends JacksonCoreTestBase
{
    // Longest double output: 17 significant digits, sign, 3-digit negative exponent
    private final static double LONGEST_DOUBLE = -2.2250738585072014E-308;
    // Longest float output: 9 significant digits, sign, 2-digit negative exponent
    private final static float LONGEST_FLOAT = -1.01897876E-22f;

    private final JsonFactory FAST_FACTORY = JsonFactory.builder()
            .enable(StreamWriteFeature.USE_FAST_DOUBLE_WRITER)
            .build();

    private final JsonFactory FAST_AS_STRINGS_FACTORY = JsonFactory.builder()
            .enable(StreamWriteFeature.USE_FAST_DOUBLE_WRITER)
            .enable(JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS)
            .build();

    @Test
    void doubleAtEveryBufferOffsetBytes() throws Exception {
        _verifyAllOffsets(FAST_FACTORY, true, LONGEST_DOUBLE, String.valueOf(LONGEST_DOUBLE));
    }

    @Test
    void doubleAtEveryBufferOffsetChars() throws Exception {
        _verifyAllOffsets(FAST_FACTORY, false, LONGEST_DOUBLE, String.valueOf(LONGEST_DOUBLE));
    }

    @Test
    void floatAtEveryBufferOffsetBytes() throws Exception {
        _verifyAllOffsets(FAST_FACTORY, true, LONGEST_FLOAT, String.valueOf(LONGEST_FLOAT));
    }

    @Test
    void floatAtEveryBufferOffsetChars() throws Exception {
        _verifyAllOffsets(FAST_FACTORY, false, LONGEST_FLOAT, String.valueOf(LONGEST_FLOAT));
    }

    @Test
    void numbersAsStringsAtEveryBufferOffsetBytes() throws Exception {
        _verifyAllOffsets(FAST_AS_STRINGS_FACTORY, true, LONGEST_DOUBLE,
                q(String.valueOf(LONGEST_DOUBLE)));
        _verifyAllOffsets(FAST_AS_STRINGS_FACTORY, true, LONGEST_FLOAT,
                q(String.valueOf(LONGEST_FLOAT)));
    }

    @Test
    void numbersAsStringsAtEveryBufferOffsetChars() throws Exception {
        _verifyAllOffsets(FAST_AS_STRINGS_FACTORY, false, LONGEST_DOUBLE,
                q(String.valueOf(LONGEST_DOUBLE)));
        _verifyAllOffsets(FAST_AS_STRINGS_FACTORY, false, LONGEST_FLOAT,
                q(String.valueOf(LONGEST_FLOAT)));
    }

    // Writes padding of increasing length before the number so that the value starts at
    // every possible distance from the end of the generator's output buffer.
    private void _verifyAllOffsets(JsonFactory f, boolean bytes, Number value, String expected)
        throws Exception
    {
        // BufferRecycler sizes: byte write-encoding buffer 8000, char concat buffer 4000
        final int bufferSize = bytes ? 8000 : 4000;
        final int padStart = bufferSize - 100;

        for (int padLen = padStart; padLen < bufferSize; ++padLen) {
            String pad = _pad(padLen);
            String json = _write(f, bytes, pad, value);
            assertEquals("[\""+pad+"\","+expected+"]", json,
                    "padding length "+padLen+" (bytes="+bytes+")");
        }
    }

    private String _write(JsonFactory f, boolean bytes, String pad, Number value)
        throws Exception
    {
        if (bytes) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            try (JsonGenerator g = f.createGenerator(ObjectWriteContext.empty(), out)) {
                _writeDoc(g, pad, value);
            }
            return out.toString(StandardCharsets.UTF_8);
        }
        StringWriter out = new StringWriter();
        try (JsonGenerator g = f.createGenerator(ObjectWriteContext.empty(), out)) {
            _writeDoc(g, pad, value);
        }
        return out.toString();
    }

    private void _writeDoc(JsonGenerator g, String pad, Number value) {
        g.writeStartArray();
        g.writeString(pad);
        if (value instanceof Double) {
            g.writeNumber(value.doubleValue());
        } else {
            g.writeNumber(value.floatValue());
        }
        g.writeEndArray();
    }

    private String _pad(int len) {
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; ++i) {
            sb.append('x');
        }
        return sb.toString();
    }
}