ParserMinimalBaseConstraintsTest.java

package tools.jackson.core.unittest.base;

import java.math.BigDecimal;
import java.math.BigInteger;

import org.junit.jupiter.api.Test;

import tools.jackson.core.*;
import tools.jackson.core.base.ParserMinimalBase;
import tools.jackson.core.exc.StreamConstraintsException;
import tools.jackson.core.unittest.JacksonCoreTestBase;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.fail;

/**
 * Tests for {@link ParserMinimalBase} constructor that takes explicit
 * {@link StreamReadConstraints}: constraints given need to be used both by
 * {@code streamReadConstraints()} and by inherited token count validation
 * (and not just the former).
 */
public class ParserMinimalBaseConstraintsTest extends JacksonCoreTestBase
{
    /**
     * Minimal parser that only produces a fixed number of tokens, to exercise
     * token counting; all value access is unsupported.
     */
    static class StubParser extends ParserMinimalBase
    {
        private int _left;

        StubParser(ObjectReadContext readCtxt, int tokenCount) {
            super(readCtxt);
            _left = tokenCount;
        }

        StubParser(ObjectReadContext readCtxt, StreamReadConstraints src, int tokenCount) {
            super(readCtxt, src);
            _left = tokenCount;
        }

        @Override
        public JsonToken nextToken() {
            if (_left-- <= 0) {
                return _updateTokenToNull();
            }
            return _updateToken(JsonToken.VALUE_TRUE);
        }

        @Override protected void _closeInput() { }
        @Override protected void _handleEOF() { }
        @Override protected void _releaseBuffers() { }

        @Override public Version version() { return Version.unknownVersion(); }
        @Override public TokenStreamContext streamReadContext() { return null; }
        @Override public Object streamReadInputSource() { return null; }
        @Override public String currentName() { return null; }
        @Override public TokenStreamLocation currentLocation() { return TokenStreamLocation.NA; }
        @Override public TokenStreamLocation currentTokenLocation() { return TokenStreamLocation.NA; }
        @Override public Object currentValue() { return null; }
        @Override public void assignCurrentValue(Object v) { }

        @Override public String getString() { return _unsupported(); }
        @Override public char[] getStringCharacters() { return _unsupported(); }
        @Override public int getStringLength() { return _unsupported(); }
        @Override public int getStringOffset() { return _unsupported(); }
        @Override public boolean hasStringCharacters() { return false; }
        @Override public byte[] getBinaryValue(Base64Variant b64) { return _unsupported(); }
        @Override public Number getNumberValue() { return _unsupported(); }
        @Override public NumberType getNumberType() { return _unsupported(); }
        @Override public int getIntValue() { return _unsupported(); }
        @Override public long getLongValue() { return _unsupported(); }
        @Override public float getFloatValue() { return _unsupported(); }
        @Override public double getDoubleValue() { return _unsupported(); }
        @Override public BigInteger getBigIntegerValue() { return _unsupported(); }
        @Override public BigDecimal getDecimalValue() { return _unsupported(); }
        @Override public boolean isNaN() { return false; }

        private <T> T _unsupported() { throw new UnsupportedOperationException(); }
    }

    private final static StreamReadConstraints CONSTRAINED = StreamReadConstraints.builder()
            .maxTokenCount(4L).build();

    @Test
    void explicitConstraintsExposedByAccessor() throws Exception
    {
        try (StubParser p = new StubParser(ObjectReadContext.empty(), CONSTRAINED, 2)) {
            assertSame(CONSTRAINED, p.streamReadConstraints());
            assertEquals(4L, p.streamReadConstraints().getMaxTokenCount());
        }
    }

    @Test
    void explicitConstraintsUsedForTokenCount() throws Exception
    {
        // Read context has defaults (no token limit), so a limit can only be
        // enforced if constraints passed explicitly are the ones actually used
        try (StubParser p = new StubParser(ObjectReadContext.empty(), CONSTRAINED, 10)) {
            while (p.nextToken() != null) { }
            fail("Should not pass; should fail on token count limit of 4");
        } catch (StreamConstraintsException e) {
            verifyException(e, "Token count");
        }
    }

    @Test
    void singleArgConstructorStillUsesReadContext() throws Exception
    {
        // Delegating constructor must keep behaving as before: constraints from
        // the read context, which by default impose no token count limit
        ObjectReadContext ctxt = ObjectReadContext.empty();
        try (StubParser p = new StubParser(ctxt, 10)) {
            assertSame(ctxt.streamReadConstraints(), p.streamReadConstraints());
            int count = 0;
            while (p.nextToken() != null) { ++count; }
            assertEquals(10, count);
        }
    }
}