Skip to content

Commit

Permalink
Adds the ability to read an Ion int as a Java double, Decimal, or Big…
Browse files Browse the repository at this point in the history
…Decimal for consistency with the previous implementation.
  • Loading branch information
tgregg committed Dec 21, 2023
1 parent b0d3dcc commit 56bada5
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -41,13 +42,15 @@
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;
import java.util.List;
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;
Expand Down Expand Up @@ -361,6 +364,13 @@ static ExpectationProvider<IonReaderContinuableTopLevelBinary> decimalValue(BigD
));
}

static ExpectationProvider<IonReaderContinuableTopLevelBinary> bigDecimalValue(BigDecimal expectedValue) {
return consumer -> consumer.accept(new Expectation<>(
String.format("bigDecimal(%s)", expectedValue),
reader -> assertEquals(expectedValue, reader.bigDecimalValue())
));
}

static ExpectationProvider<IonReaderContinuableTopLevelBinary> stringValue(String expectedValue) {
return consumer -> consumer.accept(new Expectation<>(
String.format("string(%s)", expectedValue),
Expand Down Expand Up @@ -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 <T> void readIntsIntoOtherType(
boolean constructFromBytes,
Function<T, ExpectationProvider<IonReaderContinuableTopLevelBinary>> assertionFunction,
T... expectedValues
) throws Exception {
reader = readerFor(
"0 " +
"1 " +
"-1 " +
"2147483647 " +
"2147483648 " +
"-2147483648 " +
"-2147483649 " +
"9223372036854775807 " +
"9223372036854775808 " +
"-9223372036854775808 " +
"-9223372036854775809",
constructFromBytes
);
List<ExpectationProvider<IonReaderContinuableTopLevelBinary>> 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}")
Expand Down

0 comments on commit 56bada5

Please sign in to comment.