diff --git a/src/main/java/com/amazon/ion/impl/IonReaderContinuableCoreBinary.java b/src/main/java/com/amazon/ion/impl/IonReaderContinuableCoreBinary.java index 811281e64c..b43bfe2071 100644 --- a/src/main/java/com/amazon/ion/impl/IonReaderContinuableCoreBinary.java +++ b/src/main/java/com/amazon/ion/impl/IonReaderContinuableCoreBinary.java @@ -579,36 +579,67 @@ public int getBytes(byte[] bytes, int offset, int len) { return length; } + /** + * Loads the scalar converter with an integer value that fits the Ion int on which the reader is positioned. + */ + private void prepareToConvertIntValue() { + if (getIntegerSize() == IntegerSize.BIG_INTEGER) { + scalarConverter.addValue(bigIntegerValue()); + scalarConverter.setAuthoritativeType(_Private_ScalarConversions.AS_TYPE.bigInteger_value); + } else { + scalarConverter.addValue(longValue()); + scalarConverter.setAuthoritativeType(_Private_ScalarConversions.AS_TYPE.long_value); + } + } + @Override public BigDecimal bigDecimalValue() { - if (valueTid == null || IonType.DECIMAL != valueTid.type) { + BigDecimal value = null; + if (valueTid.type == IonType.DECIMAL) { + if (valueTid.isNull) { + return null; + } + prepareScalar(); + peekIndex = valueMarker.startIndex; + if (peekIndex >= valueMarker.endIndex) { + value = BigDecimal.ZERO; + } else { + value = minorVersion == 0 ? readBigDecimal_1_0() : readBigDecimal_1_1(); + } + } else if (valueTid.type == IonType.INT) { + prepareToConvertIntValue(); + scalarConverter.cast(scalarConverter.get_conversion_fnid(_Private_ScalarConversions.AS_TYPE.decimal_value)); + value = scalarConverter.getBigDecimal(); + scalarConverter.clear(); + } else { throwDueToInvalidType(IonType.DECIMAL); } - if (valueTid.isNull) { - return null; - } - prepareScalar(); - peekIndex = valueMarker.startIndex; - if (peekIndex >= valueMarker.endIndex) { - return BigDecimal.ZERO; - } - return minorVersion == 0 ? readBigDecimal_1_0() : readBigDecimal_1_1(); + return value; } @Override public Decimal decimalValue() { - if (valueTid == null || IonType.DECIMAL != valueTid.type) { + Decimal value = null; + if (valueTid.type == IonType.DECIMAL) { + if (valueTid.isNull) { + return null; + } + prepareScalar(); + peekIndex = valueMarker.startIndex; + if (peekIndex >= valueMarker.endIndex) { + value = Decimal.ZERO; + } else { + value = minorVersion == 0 ? readDecimal_1_0() : readDecimal_1_1(); + } + } else if (valueTid.type == IonType.INT) { + prepareToConvertIntValue(); + scalarConverter.cast(scalarConverter.get_conversion_fnid(_Private_ScalarConversions.AS_TYPE.decimal_value)); + value = scalarConverter.getDecimal(); + scalarConverter.clear(); + } else { throwDueToInvalidType(IonType.DECIMAL); } - if (valueTid.isNull) { - return null; - } - prepareScalar(); - peekIndex = valueMarker.startIndex; - if (peekIndex >= valueMarker.endIndex) { - return Decimal.ZERO; - } - return minorVersion == 0 ? readDecimal_1_0() : readDecimal_1_1(); + return value; } @Override @@ -689,12 +720,17 @@ public double doubleValue() { // Note: there is no need to check for other lengths here; the type ID byte is validated during next(). value = bytes.getDouble(); } - } else if (valueTid.type == IonType.DECIMAL) { + } else if (valueTid.type == IonType.DECIMAL) { scalarConverter.addValue(decimalValue()); scalarConverter.setAuthoritativeType(_Private_ScalarConversions.AS_TYPE.decimal_value); scalarConverter.cast(scalarConverter.get_conversion_fnid(_Private_ScalarConversions.AS_TYPE.double_value)); value = scalarConverter.getDouble(); scalarConverter.clear(); + } else if (valueTid.type == IonType.INT) { + prepareToConvertIntValue(); + scalarConverter.cast(scalarConverter.get_conversion_fnid(_Private_ScalarConversions.AS_TYPE.double_value)); + value = scalarConverter.getDouble(); + scalarConverter.clear(); } else { throw new IllegalStateException("doubleValue() may only be called on values of type float or decimal."); } diff --git a/src/test/java/com/amazon/ion/impl/IonReaderContinuableTopLevelBinaryTest.java b/src/test/java/com/amazon/ion/impl/IonReaderContinuableTopLevelBinaryTest.java index 57a9d8c2ff..3d6140bc94 100644 --- a/src/test/java/com/amazon/ion/impl/IonReaderContinuableTopLevelBinaryTest.java +++ b/src/test/java/com/amazon/ion/impl/IonReaderContinuableTopLevelBinaryTest.java @@ -4,6 +4,7 @@ package com.amazon.ion.impl; import com.amazon.ion.BufferConfiguration; +import com.amazon.ion.Decimal; import com.amazon.ion.IonBufferConfiguration; import com.amazon.ion.IonDatagram; import com.amazon.ion.IonException; @@ -41,6 +42,7 @@ import java.io.InputStream; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; @@ -48,6 +50,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; +import java.util.function.Function; import java.util.zip.GZIPInputStream; import static com.amazon.ion.BitUtils.bytes; @@ -361,6 +364,13 @@ static ExpectationProvider decimalValue(BigD )); } + static ExpectationProvider bigDecimalValue(BigDecimal expectedValue) { + return consumer -> consumer.accept(new Expectation<>( + String.format("bigDecimal(%s)", expectedValue), + reader -> assertEquals(expectedValue, reader.bigDecimalValue()) + )); + } + static ExpectationProvider stringValue(String expectedValue) { return consumer -> consumer.accept(new Expectation<>( String.format("string(%s)", expectedValue), @@ -1664,13 +1674,97 @@ public void byteSizeNotOnLobFails(boolean constructFromBytes) throws Exception { reader.close(); } + /** + * Verifies that the reader can read Ion int values of all sizes into the Java type T. + */ + @SafeVarargs + private final void readIntsIntoOtherType( + boolean constructFromBytes, + Function> assertionFunction, + T... expectedValues + ) throws Exception { + reader = readerFor( + "0 " + + "1 " + + "-1 " + + "2147483647 " + + "2147483648 " + + "-2147483648 " + + "-2147483649 " + + "9223372036854775807 " + + "9223372036854775808 " + + "-9223372036854775808 " + + "-9223372036854775809", + constructFromBytes + ); + List> assertions = new ArrayList<>(); + for (T expectedValue : expectedValues) { + assertions.add(next(IonType.INT)); + assertions.add(assertionFunction.apply(expectedValue)); + } + assertions.add(next(null)); + assertSequence(assertions.toArray(new ExpectationProvider[0])); + closeAndCount(); + } + @ParameterizedTest(name = "constructFromBytes={0}") @ValueSource(booleans = {true, false}) - public void doubleValueOnIntFails(boolean constructFromBytes) throws Exception { - reader = readerFor(constructFromBytes, 0x20); - reader.next(); - assertThrows(IllegalStateException.class, () -> reader.doubleValue()); - reader.close(); + public void doubleValueOnInt(boolean constructFromBytes) throws Exception { + readIntsIntoOtherType( + constructFromBytes, + IonReaderContinuableTopLevelBinaryTest::doubleValue, + 0.0, + 1.0, + -1.0, + (double) Integer.MAX_VALUE, + (double) (((long) Integer.MAX_VALUE) + 1), + (double) Integer.MIN_VALUE, + (double) (((long) Integer.MIN_VALUE) - 1), + (double) Long.MAX_VALUE, + ((double) Long.MAX_VALUE) + 1, + (double) Long.MIN_VALUE, + ((double) Long.MIN_VALUE) - 1 + ); + } + + @ParameterizedTest(name = "constructFromBytes={0}") + @ValueSource(booleans = {true, false}) + public void decimalValueOnInt(boolean constructFromBytes) throws Exception { + readIntsIntoOtherType( + constructFromBytes, + IonReaderContinuableTopLevelBinaryTest::decimalValue, + Decimal.ZERO, + Decimal.ONE, + Decimal.valueOf(-1), + Decimal.valueOf(Integer.MAX_VALUE), + Decimal.valueOf(((long) Integer.MAX_VALUE) + 1), + Decimal.valueOf(Integer.MIN_VALUE), + Decimal.valueOf(((long) Integer.MIN_VALUE) - 1), + Decimal.valueOf(Long.MAX_VALUE), + Decimal.valueOf(Long.MAX_VALUE).add(Decimal.ONE), + Decimal.valueOf(Long.MIN_VALUE), + Decimal.valueOf(Long.MIN_VALUE).subtract(Decimal.ONE) + ); + } + + @ParameterizedTest(name = "constructFromBytes={0}") + @ValueSource(booleans = {true, false}) + public void bigDecimalValueOnInt(boolean constructFromBytes) throws Exception { + readIntsIntoOtherType( + constructFromBytes, + IonReaderContinuableTopLevelBinaryTest::bigDecimalValue, + BigDecimal.ZERO, + BigDecimal.ONE, + BigDecimal.valueOf(-1), + BigDecimal.valueOf(Integer.MAX_VALUE), + BigDecimal.valueOf(((long) Integer.MAX_VALUE) + 1), + BigDecimal.valueOf(Integer.MIN_VALUE), + BigDecimal.valueOf(((long) Integer.MIN_VALUE) - 1), + BigDecimal.valueOf(Long.MAX_VALUE), + BigDecimal.valueOf(Long.MAX_VALUE).add(BigDecimal.ONE), + BigDecimal.valueOf(Long.MIN_VALUE), + BigDecimal.valueOf(Long.MIN_VALUE).subtract(BigDecimal.ONE) + ); } @ParameterizedTest(name = "constructFromBytes={0}")