JsonNodeLongValueTest.java

package tools.jackson.databind.node;

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

import org.junit.jupiter.api.Test;

import tools.jackson.databind.JsonNode;
import tools.jackson.databind.exc.JsonNodeException;
import tools.jackson.databind.util.RawValue;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

/**
 * Tests for [databind#4958], JsonNode.longValue() (and related) parts
 * over all types.
 */
public class JsonNodeLongValueTest
    extends NodeTestBase
{
    private final JsonNodeFactory NODES = newJsonMapper().getNodeFactory();

    // longValue() + Numbers/Integers

    @Test
    public void longValueFromNumberIntOk()
    {
        // First safe from `long`
        _assertLongValue(1L, NODES.numberNode(1L));
        _assertLongValue(Integer.MIN_VALUE, NODES.numberNode(Integer.MIN_VALUE));
        _assertLongValue(Integer.MAX_VALUE, NODES.numberNode(Integer.MAX_VALUE));

        // Then other integer types, byte/short/int
        _assertLongValue(1L, NODES.numberNode((byte) 1));
        _assertLongValue((long)Byte.MIN_VALUE, NODES.numberNode(Byte.MIN_VALUE));
        _assertLongValue((long)Byte.MAX_VALUE, NODES.numberNode(Byte.MAX_VALUE));

        _assertLongValue(1L, NODES.numberNode((short) 1));
        _assertLongValue((long)Short.MIN_VALUE, NODES.numberNode(Short.MIN_VALUE));
        _assertLongValue((long)Short.MAX_VALUE, NODES.numberNode(Short.MAX_VALUE));

        _assertLongValue(1L, NODES.numberNode(1));
        _assertLongValue((long) Integer.MIN_VALUE, NODES.numberNode(Integer.MIN_VALUE));
        _assertLongValue((long) Integer.MAX_VALUE, NODES.numberNode(Integer.MAX_VALUE));

        _assertLongValue(1L, NODES.numberNode(BigInteger.valueOf(1)));
        _assertLongValue(Long.MIN_VALUE, NODES.numberNode(BigInteger.valueOf(Long.MIN_VALUE)));
        _assertLongValue(Long.MAX_VALUE, NODES.numberNode(BigInteger.valueOf(Long.MAX_VALUE)));
    }

    @Test
    public void longValueFromNumberIntFailRange() {
        // Can only fail for underflow/overflow: and that only for Long / BigInteger
        final BigInteger underflow = BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.ONE);
        final BigInteger overflow = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE);
    
        _assertLongValueFailForValueRange(NODES.numberNode(underflow));
        _assertLongValueFailForValueRange(NODES.numberNode(overflow));
    }

    // longValue() + Numbers/FPs

    @Test
    public void longValueFromNumberFPOk()
    {
        _assertLongValue(1, NODES.numberNode(1.0f));
        _assertLongValue(100_000, NODES.numberNode(100_000.0f));
        _assertLongValue(-100_000, NODES.numberNode(-100_000.0f));

        _assertLongValue(1, NODES.numberNode(1.0d));
        _assertLongValue(100_000, NODES.numberNode(100_000.0d));
        _assertLongValue(-100_000, NODES.numberNode(-100_000.0d));
        _assertLongValue(Long.MIN_VALUE, NODES.numberNode((double) Long.MIN_VALUE));
        _assertLongValue(Long.MAX_VALUE, NODES.numberNode((double) Long.MAX_VALUE));

        _assertLongValue(1,
                NODES.numberNode(BigDecimal.valueOf(1.0d)));
        _assertLongValue(Long.MIN_VALUE,
                NODES.numberNode(new BigDecimal(Long.MIN_VALUE+".0")));
        _assertLongValue(Long.MAX_VALUE,
                NODES.numberNode(new BigDecimal(Long.MAX_VALUE+".0")));
    }

    @Test
    public void longValueFromNumberFPFailRange()
    {
        // For Float and Double both it's tricky to do too-big/too-small accurately so

        final double underflow_d = -Double.MAX_VALUE;
        final double overflow_d = Double.MAX_VALUE;

        _assertLongValueFailForValueRange(NODES.numberNode(underflow_d));
        _assertLongValueFailForValueRange(NODES.numberNode(overflow_d));

        _assertLongValueFailForValueRange(NODES.numberNode(-Float.MAX_VALUE));
        _assertLongValueFailForValueRange(NODES.numberNode(Float.MAX_VALUE));

        // But for BigDecimal can do exact check
        
        final BigDecimal underflow_big = BigDecimal.valueOf(Long.MIN_VALUE).subtract(BigDecimal.ONE);
        final BigDecimal overflow_big = BigDecimal.valueOf(Long.MAX_VALUE).add(BigDecimal.ONE);

        _assertLongValueFailForValueRange(NODES.numberNode(underflow_big));
        _assertLongValueFailForValueRange(NODES.numberNode(overflow_big));
    }

    @Test
    public void longValueFromNumberFPFailFraction()
    {
        _assertLongValueFailForFraction(NODES.numberNode(100.5f));
        _assertLongValueFailForFraction(NODES.numberNode(-0.25f));

        _assertLongValueFailForFraction(NODES.numberNode(100.5d));
        _assertLongValueFailForFraction(NODES.numberNode(-0.25d));

        _assertLongValueFailForFraction(NODES.numberNode(BigDecimal.valueOf(100.5d)));
        _assertLongValueFailForFraction(NODES.numberNode(BigDecimal.valueOf(-0.25d)));
    }

    @Test
    public void longValueFromNumberFPFailNaN()
    {
        _assertLongValueFailForNaN(NODES.numberNode(Float.NaN));
        _assertLongValueFailForNaN(NODES.numberNode(Float.NEGATIVE_INFINITY));
        _assertLongValueFailForNaN(NODES.numberNode(Float.POSITIVE_INFINITY));

        _assertLongValueFailForNaN(NODES.numberNode(Double.NaN));
        _assertLongValueFailForNaN(NODES.numberNode(Double.NEGATIVE_INFINITY));
        _assertLongValueFailForNaN(NODES.numberNode(Double.POSITIVE_INFINITY));
    }

    // longValue() + non-Numeric types

    @Test
    public void longValueFromNonNumberScalarFail()
    {
        _assertLongValueFailForNonNumber(NODES.booleanNode(true));
        _assertLongValueFailForNonNumber(NODES.binaryNode(new byte[3]));
        _assertLongValueFailForNonNumber(NODES.stringNode("123"));
        _assertLongValueFailForNonNumber(NODES.rawValueNode(new RawValue("abc")));
        _assertLongValueFailForNonNumber(NODES.pojoNode(Boolean.TRUE));
        _assertLongValueFailForNonNumber(NODES.pojoNode(456L));
    }

    @Test
    public void longValueFromStructuralFail()
    {
        _assertLongValueFailForNonNumber(NODES.arrayNode(3));
        _assertLongValueFailForNonNumber(NODES.objectNode());
    }

    @Test
    public void longValueFromMiscOtherFail()
    {
        _assertLongValueFailForNonNumber(NODES.nullNode());
        _assertLongValueFailForNonNumber(NODES.missingNode());
    }

    // // // asLong() tests

    @Test
    public void asLongFromNumberIntOk()
    {
        // First safe from `long`
        _assertAsLong(1L, NODES.numberNode(1L));
        _assertAsLong(Integer.MIN_VALUE, NODES.numberNode(Integer.MIN_VALUE));
        _assertAsLong(Integer.MAX_VALUE, NODES.numberNode(Integer.MAX_VALUE));

        // Then other integer types, byte/short/int
        _assertAsLong(1L, NODES.numberNode((byte) 1));
        _assertAsLong((long)Byte.MIN_VALUE, NODES.numberNode(Byte.MIN_VALUE));
        _assertAsLong((long)Byte.MAX_VALUE, NODES.numberNode(Byte.MAX_VALUE));

        _assertAsLong(1L, NODES.numberNode((short) 1));
        _assertAsLong((long)Short.MIN_VALUE, NODES.numberNode(Short.MIN_VALUE));
        _assertAsLong((long)Short.MAX_VALUE, NODES.numberNode(Short.MAX_VALUE));

        _assertAsLong(1L, NODES.numberNode(1));
        _assertAsLong((long) Integer.MIN_VALUE, NODES.numberNode(Integer.MIN_VALUE));
        _assertAsLong((long) Integer.MAX_VALUE, NODES.numberNode(Integer.MAX_VALUE));

        _assertAsLong(1L, NODES.numberNode(BigInteger.valueOf(1)));
        _assertAsLong(Long.MIN_VALUE, NODES.numberNode(BigInteger.valueOf(Long.MIN_VALUE)));
        _assertAsLong(Long.MAX_VALUE, NODES.numberNode(BigInteger.valueOf(Long.MAX_VALUE)));
    }

    @Test
    public void asLongFromNumberIntFailRange() {
        // Can only fail for underflow/overflow: and that only for Long / BigInteger
        final BigInteger underflow = BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.ONE);
        final BigInteger overflow = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE);
    
        _assertAsLongFailForValueRange(NODES.numberNode(underflow));
        _assertAsLongFailForValueRange(NODES.numberNode(overflow));
    }

    // longValue() + Numbers/FPs

    @Test
    public void asLongFromNumberFPOk()
    {
        _assertAsLong(1, NODES.numberNode(1.0f));
        _assertAsLong(100_000, NODES.numberNode(100_000.0f));
        _assertAsLong(-100_000, NODES.numberNode(-100_000.0f));

        _assertAsLong(1, NODES.numberNode(1.0d));
        _assertAsLong(100_000, NODES.numberNode(100_000.0d));
        _assertAsLong(-100_000, NODES.numberNode(-100_000.0d));
        _assertAsLong(Long.MIN_VALUE, NODES.numberNode((double) Long.MIN_VALUE));
        _assertAsLong(Long.MAX_VALUE, NODES.numberNode((double) Long.MAX_VALUE));

        _assertAsLong(1,
                NODES.numberNode(BigDecimal.valueOf(1.0d)));
        _assertAsLong(Long.MIN_VALUE,
                NODES.numberNode(new BigDecimal(Long.MIN_VALUE+".0")));
        _assertAsLong(Long.MAX_VALUE,
                NODES.numberNode(new BigDecimal(Long.MAX_VALUE+".0")));
    }

    @Test
    public void asLongFromNumberFPFailRange()
    {
        // For Float and Double both it's tricky to do too-big/too-small accurately so

        final double underflow_d = -Double.MAX_VALUE;
        final double overflow_d = Double.MAX_VALUE;

        _assertAsLongFailForValueRange(NODES.numberNode(underflow_d));
        _assertAsLongFailForValueRange(NODES.numberNode(overflow_d));

        _assertAsLongFailForValueRange(NODES.numberNode(-Float.MAX_VALUE));
        _assertAsLongFailForValueRange(NODES.numberNode(Float.MAX_VALUE));

        // But for BigDecimal can do exact check
        
        final BigDecimal underflow_big = BigDecimal.valueOf(Long.MIN_VALUE).subtract(BigDecimal.ONE);
        final BigDecimal overflow_big = BigDecimal.valueOf(Long.MAX_VALUE).add(BigDecimal.ONE);

        _assertAsLongFailForValueRange(NODES.numberNode(underflow_big));
        _assertAsLongFailForValueRange(NODES.numberNode(overflow_big));
    }

    @Test
    public void asLongFromNumberFPWithFraction()
    {
        _assertAsLong(100L, NODES.numberNode(100.75f));
        _assertAsLong(-1L, NODES.numberNode(-1.25f));

        _assertAsLong(100L, NODES.numberNode(100.75d));
        _assertAsLong(-1L, NODES.numberNode(-1.25d));

        _assertAsLong(100L, NODES.numberNode(BigDecimal.valueOf(100.75d)));
        _assertAsLong(-1L, NODES.numberNode(BigDecimal.valueOf(-1.25d)));
    }

    @Test
    public void asLongFromNumberFPFailNaN()
    {
        _assertAsLongFailForNaN(NODES.numberNode(Float.NaN));
        _assertAsLongFailForNaN(NODES.numberNode(Float.NEGATIVE_INFINITY));
        _assertAsLongFailForNaN(NODES.numberNode(Float.POSITIVE_INFINITY));

        _assertAsLongFailForNaN(NODES.numberNode(Double.NaN));
        _assertAsLongFailForNaN(NODES.numberNode(Double.NEGATIVE_INFINITY));
        _assertAsLongFailForNaN(NODES.numberNode(Double.POSITIVE_INFINITY));
    }

    // longValue() + non-Numeric types

    @Test
    public void asLongFromNonNumberScalarFail()
    {
        // Some fail;
        _assertAsLongFailForNonNumber(NODES.booleanNode(true));
        _assertAsLongFailForNonNumber(NODES.binaryNode(new byte[3]));
        _assertAsLongFailForNonNumber(NODES.rawValueNode(new RawValue("abc")));
        _assertAsLongFailForNonNumber(NODES.pojoNode(Boolean.TRUE));
        _assertAsLongFailForNonNumber(NODES.stringNode("abcdef"), "not a valid String representation of `long`");
        _assertAsLongFailForNonNumber(NODES.pojoNode(true));

        // Some pass
        _assertAsLong(1234L, NODES.stringNode("1234"));
        _assertAsLong(123456L, NODES.pojoNode(123456L));
        _assertAsLong(789L, NODES.pojoNode(BigInteger.valueOf(789)));
    }

    @Test
    public void asLongFromStructuralFail()
    {
        _assertAsLongFailForNonNumber(NODES.arrayNode(3));
        _assertAsLongFailForNonNumber(NODES.objectNode());
    }

    @Test
    public void asLongFromMiscOther()
    {
        // NullNode works, Missing fails
        _assertAsLong(0L, NODES.nullNode());

        _assertAsLongFailForNonNumber(NODES.missingNode());
    }
    
    // // // Shared helper methods, longValue()

    private void _assertLongValue(long expected, JsonNode node)
    {
        assertEquals(expected, node.longValue());

        // But also fallbacks
        assertEquals(expected, node.longValue(999999L));
        assertEquals(expected, node.longValueOpt().getAsLong());
    }

    private void _assertLongValueFailForValueRange(JsonNode node) {
        Exception e = assertThrows(JsonNodeException.class,
                () ->  node.longValue(),
                "For ("+node.getClass().getSimpleName()+") value: "+node);
        assertThat(e.getMessage())
            .contains("longValue()")
            .contains("cannot convert value")
            .contains("value not in 64-bit `long` range");

        // Verify default value handling
        assertEquals(1L, node.longValue(1L));
        assertFalse(node.longValueOpt().isPresent());
    }

    private void _assertLongValueFailForFraction(JsonNode node) {
        Exception e = assertThrows(JsonNodeException.class,
                () ->  node.longValue(),
                "For ("+node.getClass().getSimpleName()+") value: "+node);
        assertThat(e.getMessage())
            .contains("longValue()")
            .contains("cannot convert value")
            .contains("to `long`: value has fractional part");

        // Verify default value handling
        assertEquals(1L, node.longValue(1L));
        assertFalse(node.longValueOpt().isPresent());
    }

    private void _assertLongValueFailForNonNumber(JsonNode node) {
        Exception e = assertThrows(JsonNodeException.class,
                () ->  node.longValue(),
                "For ("+node.getClass().getSimpleName()+") value: "+node);
        assertThat(e.getMessage())
            .contains("longValue()")
            .contains("cannot convert value")
            .contains("value type not numeric");

        // Verify default value handling
        assertEquals(1L, node.longValue(1L));
        assertFalse(node.longValueOpt().isPresent());
    }

    private void _assertLongValueFailForNaN(JsonNode node) {
        Exception e = assertThrows(JsonNodeException.class,
                () ->  node.longValue(),
                "For ("+node.getClass().getSimpleName()+") value: "+node);
        assertThat(e.getMessage())
            .contains("longValue()")
            .contains("cannot convert value")
            .contains("value non-Finite");

        // Verify default value handling
        assertEquals(1L, node.longValue(1L));
        assertFalse(node.longValueOpt().isPresent());
    }

    // // // Shared helper methods, asLong()

    private void _assertAsLong(long expected, JsonNode node)
    {
        assertEquals(expected, node.asLong());

        // But also fallbacks
        assertEquals(expected, node.asLong(999999L));
        assertEquals(expected, node.asLongOpt().getAsLong());
    }

    private void _assertAsLongFailForValueRange(JsonNode node) {
        Exception e = assertThrows(JsonNodeException.class,
                () ->  node.asLong(),
                "For ("+node.getClass().getSimpleName()+") value: "+node);
        assertThat(e.getMessage())
            .contains("asLong()")
            .contains("cannot convert value")
            .contains("value not in 64-bit `long` range");

        // Verify default value handling
        assertEquals(1L, node.asLong(1L));
        assertFalse(node.asLongOpt().isPresent());
    }

    private void _assertAsLongFailForNonNumber(JsonNode node) {
        _assertAsLongFailForNonNumber(node, "value type not numeric");
    }

    private void _assertAsLongFailForNonNumber(JsonNode node, String extraMsg) {
        Exception e = assertThrows(JsonNodeException.class,
                () ->  node.asLong(),
                "For ("+node.getClass().getSimpleName()+") value: "+node);
        assertThat(e.getMessage())
            .contains("asLong()")
            .contains("cannot convert value")
            .contains(extraMsg);

        // Verify default value handling
        assertEquals(1L, node.asLong(1L));
        assertFalse(node.asLongOpt().isPresent());
    }

    private void _assertAsLongFailForNaN(JsonNode node) {
        Exception e = assertThrows(JsonNodeException.class,
                () ->  node.asLong(),
                "For ("+node.getClass().getSimpleName()+") value: "+node);
        assertThat(e.getMessage())
            .contains("asLong()")
            .contains("cannot convert value")
            .contains("value non-Finite");

        // Verify default value handling
        assertEquals(1L, node.asLong(1L));
        assertFalse(node.asLongOpt().isPresent());
    }
    
}