SimpleStreamReadContextTest.java

package tools.jackson.core.unittest.util;

import org.junit.jupiter.api.Test;

import tools.jackson.core.JsonParser;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.core.io.ContentReference;
import tools.jackson.core.json.DupDetector;
import tools.jackson.core.TokenStreamLocation;
import tools.jackson.core.unittest.JacksonCoreTestBase;
import tools.jackson.core.util.SimpleStreamReadContext;

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

/**
 * Unit tests for {@link SimpleStreamReadContext}.
 */
class SimpleStreamReadContextTest extends JacksonCoreTestBase
{
    @Test
    void createRootContext()
    {
        SimpleStreamReadContext root = SimpleStreamReadContext.createRootContext(null);

        assertTrue(root.inRoot());
        assertEquals("root", root.typeDesc());
        assertNull(root.getParent());
        assertEquals(0, root.getNestingDepth());
        assertEquals(0, root.getCurrentIndex()); // getCurrentIndex() returns 0 when _index is -1
        assertNull(root.currentName());
        assertFalse(root.hasCurrentName());
        assertNull(root.currentValue());
    }

    @Test
    void createRootContextWithLineAndColumn()
    {
        SimpleStreamReadContext root = SimpleStreamReadContext.createRootContext(5, 10, null);

        assertTrue(root.inRoot());
        assertNull(root.getParent());

        ContentReference srcRef = ContentReference.unknown();
        TokenStreamLocation loc = root.startLocation(srcRef);
        assertEquals(5, loc.getLineNr());
        assertEquals(10, loc.getColumnNr());
        assertEquals(-1L, loc.getByteOffset());
    }

    @Test
    void createRootContextWithDupDetector()
    {
        DupDetector dups = DupDetector.rootDetector((JsonParser) null);
        SimpleStreamReadContext root = SimpleStreamReadContext.createRootContext(1, 0, dups);

        assertNotNull(root.getDupDetector());
        assertSame(dups, root.getDupDetector());
    }

    @Test
    void createChildArrayContext()
    {
        SimpleStreamReadContext root = SimpleStreamReadContext.createRootContext(null);
        SimpleStreamReadContext array = root.createChildArrayContext(2, 5);

        assertTrue(array.inArray());
        assertEquals("Array", array.typeDesc());
        assertSame(root, array.getParent());
        assertEquals(1, array.getNestingDepth());
        assertEquals(0, array.getCurrentIndex()); // getCurrentIndex() returns 0 when _index is -1

        ContentReference srcRef = ContentReference.unknown();
        TokenStreamLocation loc = array.startLocation(srcRef);
        assertEquals(2, loc.getLineNr());
        assertEquals(5, loc.getColumnNr());
    }

    @Test
    void createChildObjectContext()
    {
        SimpleStreamReadContext root = SimpleStreamReadContext.createRootContext(null);
        SimpleStreamReadContext object = root.createChildObjectContext(3, 7);

        assertTrue(object.inObject());
        assertEquals("Object", object.typeDesc());
        assertSame(root, object.getParent());
        assertEquals(1, object.getNestingDepth());
        assertEquals(0, object.getCurrentIndex()); // getCurrentIndex() returns 0 when _index is -1

        ContentReference srcRef = ContentReference.unknown();
        TokenStreamLocation loc = object.startLocation(srcRef);
        assertEquals(3, loc.getLineNr());
        assertEquals(7, loc.getColumnNr());
    }

    @Test
    void childContextRecycling()
    {
        SimpleStreamReadContext root = SimpleStreamReadContext.createRootContext(null);

        // First child creation
        SimpleStreamReadContext array1 = root.createChildArrayContext(1, 0);
        assertNotNull(array1);

        // Simulate closing the array by going back to parent
        SimpleStreamReadContext parent1 = array1.clearAndGetParent();
        assertSame(root, parent1);

        // Second child creation should reuse the same instance
        SimpleStreamReadContext array2 = root.createChildArrayContext(2, 0);
        assertSame(array1, array2, "Context should be recycled");
        assertTrue(array2.inArray());
        assertEquals(0, array2.getCurrentIndex()); // getCurrentIndex() returns 0 when _index is -1

        // Verify reset worked correctly
        ContentReference srcRef = ContentReference.unknown();
        TokenStreamLocation loc = array2.startLocation(srcRef);
        assertEquals(2, loc.getLineNr());
    }

    @Test
    void valueRead()
    {
        SimpleStreamReadContext root = SimpleStreamReadContext.createRootContext(null);

        assertEquals(0, root.getCurrentIndex()); // getCurrentIndex() returns 0 when _index is -1

        int index1 = root.valueRead();
        assertEquals(0, index1);
        assertEquals(0, root.getCurrentIndex());

        int index2 = root.valueRead();
        assertEquals(1, index2);
        assertEquals(1, root.getCurrentIndex());
    }

    @Test
    void setCurrentName() throws Exception
    {
        SimpleStreamReadContext ctx = SimpleStreamReadContext.createRootContext(null);

        assertNull(ctx.currentName());
        assertFalse(ctx.hasCurrentName());

        ctx.setCurrentName("field1");
        assertEquals("field1", ctx.currentName());
        assertTrue(ctx.hasCurrentName());

        ctx.setCurrentName("field2");
        assertEquals("field2", ctx.currentName());

        ctx.setCurrentName(null);
        assertNull(ctx.currentName());
        assertFalse(ctx.hasCurrentName());
    }

    @Test
    void duplicateDetection() throws Exception
    {
        DupDetector dups = DupDetector.rootDetector((JsonParser) null);
        SimpleStreamReadContext ctx = SimpleStreamReadContext.createRootContext(1, 0, dups);

        ctx.setCurrentName("field1");
        assertEquals("field1", ctx.currentName());

        // Setting the same name again should trigger duplicate detection
        try {
            ctx.setCurrentName("field1");
            fail("Should have thrown StreamReadException for duplicate field");
        } catch (StreamReadException e) {
            verifyException(e, "Duplicate Object property");
            verifyException(e, "field1");
        }
    }

    @Test
    void duplicateDetectionInChildContext() throws Exception
    {
        DupDetector dups = DupDetector.rootDetector((JsonParser) null);
        SimpleStreamReadContext root = SimpleStreamReadContext.createRootContext(1, 0, dups);
        SimpleStreamReadContext object = root.createChildObjectContext(2, 0);

        // Child should have its own DupDetector
        assertNotNull(object.getDupDetector());
        assertNotSame(dups, object.getDupDetector());

        object.setCurrentName("prop1");

        try {
            object.setCurrentName("prop1");
            fail("Should have thrown StreamReadException for duplicate property");
        } catch (StreamReadException e) {
            verifyException(e, "Duplicate Object property");
            verifyException(e, "prop1");
        }
    }

    @Test
    void currentValue()
    {
        SimpleStreamReadContext ctx = SimpleStreamReadContext.createRootContext(null);

        assertNull(ctx.currentValue());

        Object value = new Object();
        ctx.assignCurrentValue(value);
        assertSame(value, ctx.currentValue());

        ctx.assignCurrentValue(null);
        assertNull(ctx.currentValue());
    }

    @Test
    void clearAndGetParent()
    {
        SimpleStreamReadContext root = SimpleStreamReadContext.createRootContext(null);
        SimpleStreamReadContext array = root.createChildArrayContext(1, 0);

        Object value = new Object();
        array.assignCurrentValue(value);
        assertSame(value, array.currentValue());

        SimpleStreamReadContext parent = array.clearAndGetParent();
        assertSame(root, parent);
        assertNull(array.currentValue(), "Value should be cleared");
    }

    @Test
    void nestedContexts()
    {
        SimpleStreamReadContext root = SimpleStreamReadContext.createRootContext(null);
        assertEquals(0, root.getNestingDepth());

        SimpleStreamReadContext array = root.createChildArrayContext(1, 0);
        assertEquals(1, array.getNestingDepth());
        assertSame(root, array.getParent());

        SimpleStreamReadContext object = array.createChildObjectContext(2, 0);
        assertEquals(2, object.getNestingDepth());
        assertSame(array, object.getParent());

        SimpleStreamReadContext innerArray = object.createChildArrayContext(3, 0);
        assertEquals(3, innerArray.getNestingDepth());
        assertSame(object, innerArray.getParent());
    }

    @Test
    void deprecatedConstructor()
    {
        // Test the deprecated constructor without nestingDepth parameter
        @SuppressWarnings("deprecation")
        SimpleStreamReadContext ctx = new SimpleStreamReadContext(
                SimpleStreamReadContext.TYPE_OBJECT,
                null,
                null,
                1,
                0
        );

        assertTrue(ctx.inObject());
        assertNull(ctx.getParent());
        assertEquals(-1, ctx.getNestingDepth()); // Should be -1 when using deprecated constructor
    }

    @Test
    void contextTypeDescriptions()
    {
        SimpleStreamReadContext root = SimpleStreamReadContext.createRootContext(null);
        assertEquals("root", root.typeDesc());

        SimpleStreamReadContext array = root.createChildArrayContext(1, 0);
        assertEquals("Array", array.typeDesc());

        SimpleStreamReadContext object = root.createChildObjectContext(1, 0);
        assertEquals("Object", object.typeDesc());
    }

    @Test
    void contextWithoutDupDetector()
    {
        SimpleStreamReadContext root = SimpleStreamReadContext.createRootContext(null);
        assertNull(root.getDupDetector());

        // Should allow duplicate names when no DupDetector is set
        root.setCurrentName("field");
        root.setCurrentName("field"); // Should not throw
    }

    @Test
    void childContextWithNoDupDetectorInParent()
    {
        SimpleStreamReadContext root = SimpleStreamReadContext.createRootContext(null);
        SimpleStreamReadContext child = root.createChildObjectContext(1, 0);

        assertNull(child.getDupDetector());
    }

    @Test
    void childContextRecyclingResetsState()
    {
        SimpleStreamReadContext root = SimpleStreamReadContext.createRootContext(null);

        SimpleStreamReadContext array1 = root.createChildArrayContext(1, 0);
        array1.setCurrentName("name1");
        array1.assignCurrentValue("value1");
        array1.valueRead();

        assertEquals("name1", array1.currentName());
        assertEquals("value1", array1.currentValue());
        assertEquals(0, array1.getCurrentIndex());

        // Go back to parent
        array1.clearAndGetParent();

        // Create new child (recycling previous one)
        SimpleStreamReadContext array2 = root.createChildArrayContext(2, 0);

        // State should be reset
        assertNull(array2.currentName());
        assertNull(array2.currentValue());
        assertEquals(0, array2.getCurrentIndex()); // getCurrentIndex() returns 0 when _index is -1
    }
}