com.amazon.ion.impl.IonReaderContinuableCoreBinary Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ion-java Show documentation
Show all versions of ion-java Show documentation
A Java implementation of the Amazon Ion data notation.
The newest version!
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package com.amazon.ion.impl;
import com.amazon.ion.Decimal;
import com.amazon.ion.IntegerSize;
import com.amazon.ion.IonBufferConfiguration;
import com.amazon.ion.IonException;
import com.amazon.ion.IonType;
import com.amazon.ion.Timestamp;
import com.amazon.ion.impl.bin.IntList;
import com.amazon.ion.impl.bin.utf8.Utf8StringDecoder;
import com.amazon.ion.impl.bin.utf8.Utf8StringDecoderPool;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Date;
/**
* An IonCursor capable of raw parsing of binary Ion streams.
*/
class IonReaderContinuableCoreBinary extends IonCursorBinary implements IonReaderContinuableCore {
// Isolates the highest bit in a byte.
private static final int HIGHEST_BIT_BITMASK = 0x80;
// Isolates the lowest seven bits in a byte.
private static final int LOWER_SEVEN_BITS_BITMASK = 0x7F;
private static final int SINGLE_BYTE_MASK = 0xFF;
// Isolates the lowest six bits in a byte.
private static final int LOWER_SIX_BITS_BITMASK = 0x3F;
// The number of significant bits in each UInt byte.
private static final int VALUE_BITS_PER_UINT_BYTE = 8;
// The number of significant bits in each VarUInt byte.
private static final int VALUE_BITS_PER_VARUINT_BYTE = 7;
// Single byte negative zero, represented as a VarInt. Often used in timestamp encodings to indicate unknown local
// offset.
private static final int VAR_INT_NEGATIVE_ZERO = 0xC0;
// The number of bytes occupied by a Java int.
private static final int INT_SIZE_IN_BYTES = 4;
// The number of bytes occupied by a Java long.
private static final int LONG_SIZE_IN_BYTES = 8;
// The smallest negative 8-byte integer that can fit in a long is 0x80_00_00_00_00_00_00_00 and the smallest
// negative 4-byte integer that can fit in an int is 0x80_00_00_00.
private static final int MOST_SIGNIFICANT_BYTE_OF_MIN_INTEGER = 0x80;
// The largest positive 8-byte integer that can fit in a long is 0x7F_FF_FF_FF_FF_FF_FF_FF and the largest positive
// 4-byte integer that can fit in an int is 0x7F_FF_FF_FF.
private static final int MOST_SIGNIFICANT_BYTE_OF_MAX_INTEGER = 0x7F;
// The second-most significant bit in the most significant byte of a VarInt is the sign.
private static final int VAR_INT_SIGN_BITMASK = 0x40;
// 32-bit floats must declare length 4.
private static final int FLOAT_32_BYTE_LENGTH = 4;
// Initial capacity of the ArrayList used to hold the symbol IDs of the annotations on the current value.
private static final int ANNOTATIONS_LIST_INITIAL_CAPACITY = 8;
// Converter between scalar types, allowing, for example, for a value encoded as an Ion float to be returned as a
// Java `long` via `IonReader.longValue()`.
private final _Private_ScalarConversions.ValueVariant scalarConverter;
final Utf8StringDecoder utf8Decoder = Utf8StringDecoderPool.getInstance().getOrCreate();
long peekIndex = -1;
// The number of bytes of a lob value that the user has consumed, allowing for piecewise reads.
private int lobBytesRead = 0;
// The symbol IDs for the annotations on the current value.
private final IntList annotationSids;
/**
* Constructs a new reader from the given byte array.
* @param configuration the configuration to use. The buffer size and oversized value configuration are unused, as
* the given buffer is used directly.
* @param bytes the byte array containing the bytes to read.
* @param offset the offset into the byte array at which the first byte of Ion data begins.
* @param length the number of bytes to be read from the byte array.
*/
IonReaderContinuableCoreBinary(IonBufferConfiguration configuration, byte[] bytes, int offset, int length) {
super(configuration, bytes, offset, length);
scalarConverter = new _Private_ScalarConversions.ValueVariant();
annotationSids = new IntList(ANNOTATIONS_LIST_INITIAL_CAPACITY);
}
/**
* Constructs a new reader from the given input stream.
* @param configuration the configuration to use.
* @param alreadyRead the byte array containing the bytes already read (often the IVM).
* @param alreadyReadOff the offset into `alreadyRead` at which the first byte that was already read exists.
* @param alreadyReadLen the number of bytes already read from `alreadyRead`.
*/
IonReaderContinuableCoreBinary(IonBufferConfiguration configuration, InputStream inputStream, byte[] alreadyRead, int alreadyReadOff, int alreadyReadLen) {
super(configuration, inputStream, alreadyRead, alreadyReadOff, alreadyReadLen);
scalarConverter = new _Private_ScalarConversions.ValueVariant();
annotationSids = new IntList(ANNOTATIONS_LIST_INITIAL_CAPACITY);
}
// Scratch space for various byte sizes. Only for use while computing a single value.
private final byte[][] scratchForSize = new byte[][] {
new byte[0],
new byte[1],
new byte[2],
new byte[3],
new byte[4],
new byte[5],
new byte[6],
new byte[7],
new byte[8],
new byte[9],
new byte[10],
new byte[11],
new byte[12],
};
/**
* Copy the requested number of bytes from the buffer into a scratch buffer of exactly the requested length.
* @param startIndex the start index from which to copy.
* @param length the number of bytes to copy.
* @return the scratch byte array.
*/
private byte[] copyBytesToScratch(long startIndex, int length) {
// Note: using reusable scratch buffers makes reading ints and decimals 1-5% faster and causes much less
// GC churn.
byte[] bytes = null;
if (length < scratchForSize.length) {
bytes = scratchForSize[length];
}
if (bytes == null) {
bytes = new byte[length];
}
// The correct number of bytes will be requested from the buffer, so the limit is set at the capacity to
// avoid having to calculate a limit.
System.arraycopy(buffer, (int) startIndex, bytes, 0, bytes.length);
return bytes;
}
/**
* Reads the VarUInt starting at `peekIndex`. When this method returns, `peekIndex` will point at the first byte
* that follows the VarUInt. NOTE: the VarUInt must fit in an `int`.
* @return the value.
*/
int readVarUInt_1_0() {
int currentByte = 0;
int result = 0;
while ((currentByte & HIGHEST_BIT_BITMASK) == 0) {
if (peekIndex >= limit) {
throw new IonException("Malformed data: declared length exceeds the number of bytes remaining in the stream.");
}
currentByte = buffer[(int)(peekIndex++)];
result = (result << VALUE_BITS_PER_VARUINT_BYTE) | (currentByte & LOWER_SEVEN_BITS_BITMASK);
}
return result;
}
/**
* Reads a 2+ byte VarInt, given the first byte. When called, `peekIndex` must point at the second byte in the
* VarInt. at `peekIndex`.When this method returns, `peekIndex` will point at the first byte that follows the
* VarInt. NOTE: the VarInt must fit in an `int`.
* @param firstByte the first byte of the VarInt representation, which has already been retrieved from the buffer.
* @return the value.
*/
private int readVarInt_1_0(int firstByte) {
int currentByte = firstByte;
int sign = (currentByte & VAR_INT_SIGN_BITMASK) == 0 ? 1 : -1;
int result = currentByte & LOWER_SIX_BITS_BITMASK;
while ((currentByte & HIGHEST_BIT_BITMASK) == 0) {
if (peekIndex >= limit) {
throw new IonException("Malformed data: declared length exceeds the number of bytes remaining in the stream.");
}
currentByte = buffer[(int)(peekIndex++)];
result = (result << VALUE_BITS_PER_VARUINT_BYTE) | (currentByte & LOWER_SEVEN_BITS_BITMASK);
}
return result * sign;
}
/**
* Reads the VarInt starting at `peekIndex`. When this method returns, `peekIndex` will point at the first byte
* that follows the VarUInt. NOTE: the VarInt must fit in an `int`.
* @return the value.
*/
private int readVarInt_1_0() {
return readVarInt_1_0(buffer[(int)(peekIndex++)]);
}
/**
* Reads into a BigInteger the UInt value that begins at `valueMarker.startIndex` and ends at
* `valueMarker.endIndex`.
* @param isNegative true if the resulting BigInteger value should be negative; false if it should be positive.
* @return the value.
*/
private BigInteger readUIntAsBigInteger(boolean isNegative) {
int length = (int) (valueMarker.endIndex - valueMarker.startIndex);
// NOTE: unfortunately, there is no BigInteger(int signum, byte[] bits, int offset, int length) constructor
// until JDK 9, so copying to scratch space is always required. Migrating to the new constructor will
// lead to a significant performance improvement.
byte[] magnitude = copyBytesToScratch(valueMarker.startIndex, length);
int signum = isNegative ? -1 : 1;
return new BigInteger(signum, magnitude);
}
/**
* Get and clear the most significant bit in the given byte array.
* @param intBytes bytes representing a signed int.
* @return -1 if the most significant bit was set; otherwise, 1.
*/
private int getAndClearSignBit_1_0(byte[] intBytes) {
boolean isNegative = (intBytes[0] & HIGHEST_BIT_BITMASK) != 0;
int signum = isNegative ? -1 : 1;
if (isNegative) {
intBytes[0] &= LOWER_SEVEN_BITS_BITMASK;
}
return signum;
}
/**
* Reads the Int value of the given length that begins at `peekIndex` into a BigInteger.
* @param length the length of the value.
* @return the value.
*/
private BigInteger readIntAsBigInteger_1_0(int length) {
BigInteger value;
if (length > 0) {
// NOTE: unfortunately, there is no BigInteger(int signum, byte[] bits, int offset, int length) constructor
// until JDK 9, so copying to scratch space is always required. Migrating to the new constructor will
// lead to a significant performance improvement.
byte[] bytes = copyBytesToScratch(peekIndex, length);
value = new BigInteger(getAndClearSignBit_1_0(bytes), bytes);
}
else {
value = BigInteger.ZERO;
}
return value;
}
/**
* Reads into a BigDecimal the decimal value that begins at `peekIndex` and ends at `valueMarker.endIndex`.
* @return the value.
*/
private BigDecimal readBigDecimal_1_0() {
int scale = -readVarInt_1_0();
BigDecimal value;
int length = (int) (valueMarker.endIndex - peekIndex);
if (length < LONG_SIZE_IN_BYTES) {
// No need to allocate a BigInteger to hold the coefficient.
long coefficient = 0;
int sign = 1;
if (peekIndex < valueMarker.endIndex) {
int firstByte = buffer[(int)(peekIndex++)] & SINGLE_BYTE_MASK;
sign = (firstByte & HIGHEST_BIT_BITMASK) == 0 ? 1 : -1;
coefficient = firstByte & LOWER_SEVEN_BITS_BITMASK;
}
while (peekIndex < valueMarker.endIndex) {
coefficient = (coefficient << VALUE_BITS_PER_UINT_BYTE) | buffer[(int)(peekIndex++)] & SINGLE_BYTE_MASK;
}
value = BigDecimal.valueOf(coefficient * sign, scale);
} else {
// The coefficient may overflow a long, so a BigInteger is required.
value = new BigDecimal(readIntAsBigInteger_1_0(length), scale);
}
return value;
}
/**
* Reads into a Decimal the decimal value that begins at `peekIndex` and ends at `valueMarker.endIndex`.
* @return the value.
*/
private Decimal readDecimal_1_0() {
int scale = -readVarInt_1_0();
BigInteger coefficient;
int length = (int) (valueMarker.endIndex - peekIndex);
if (length > 0) {
// NOTE: unfortunately, there is no BigInteger(int signum, byte[] bits, int offset, int length) constructor,
// so copying to scratch space is always required.
byte[] bits = copyBytesToScratch(peekIndex, length);
int signum = getAndClearSignBit_1_0(bits);
// NOTE: there is a BigInteger.valueOf(long unscaledValue, int scale) factory method that avoids allocating
// a BigInteger for coefficients that fit in a long. See its use in readBigDecimal() above. Unfortunately,
// it is not possible to use this for Decimal because the necessary BigDecimal constructor is
// package-private. If a compatible BigDecimal constructor is added in a future JDK revision, a
// corresponding factory method should be added to Decimal to enable this optimization.
coefficient = new BigInteger(signum, bits);
if (coefficient.signum() == 0 && signum < 0) {
return Decimal.negativeZero(scale);
}
}
else {
coefficient = BigInteger.ZERO;
}
return Decimal.valueOf(coefficient, scale);
}
/**
* Reads into a long the integer value that begins at `valueMarker.startIndex` and ends at `valueMarker.endIndex`.
* @return the value.
*/
private long readLong_1_0() {
long value = readUInt(valueMarker.startIndex, valueMarker.endIndex);
if (valueTid.isNegativeInt) {
if (value == 0) {
throw new IonException("Int zero may not be negative.");
}
value *= -1;
}
return value;
}
/**
* Reads into a BigInteger the integer value that begins at `valueMarker.startIndex` and ends at
* `valueMarker.endIndex`.
* @return the value.
*/
private BigInteger readBigInteger_1_0() {
BigInteger value = readUIntAsBigInteger(valueTid.isNegativeInt);
if (valueTid.isNegativeInt && value.signum() == 0) {
throw new IonException("Int zero may not be negative.");
}
return value;
}
/**
* Reads the timestamp that begins at `peekIndex` and ends at `valueMarker.endIndex`.
* @return the value.
*/
private Timestamp readTimestamp_1_0() {
int firstByte = buffer[(int)(peekIndex++)] & SINGLE_BYTE_MASK;
Integer offset = null;
if (firstByte != VAR_INT_NEGATIVE_ZERO) {
offset = readVarInt_1_0(firstByte);
}
int year = readVarUInt_1_0();
int month = 0;
int day = 0;
int hour = 0;
int minute = 0;
int second = 0;
BigDecimal fractionalSecond = null;
Timestamp.Precision precision = Timestamp.Precision.YEAR;
if (peekIndex < valueMarker.endIndex) {
month = readVarUInt_1_0();
precision = Timestamp.Precision.MONTH;
if (peekIndex < valueMarker.endIndex) {
day = readVarUInt_1_0();
precision = Timestamp.Precision.DAY;
if (peekIndex < valueMarker.endIndex) {
hour = readVarUInt_1_0();
if (peekIndex >= valueMarker.endIndex) {
throw new IonException("Timestamps may not specify hour without specifying minute.");
}
minute = readVarUInt_1_0();
precision = Timestamp.Precision.MINUTE;
if (peekIndex < valueMarker.endIndex) {
second = readVarUInt_1_0();
precision = Timestamp.Precision.SECOND;
if (peekIndex < valueMarker.endIndex) {
fractionalSecond = readBigDecimal_1_0();
}
}
}
}
}
try {
return Timestamp.createFromUtcFields(
precision,
year,
month,
day,
hour,
minute,
second,
fractionalSecond,
offset
);
} catch (IllegalArgumentException e) {
throw new IonException("Illegal timestamp encoding. ", e);
}
}
/**
* Reads the boolean value using the type ID of the current value.
* @return the value.
*/
private boolean readBoolean_1_0() {
return valueTid.lowerNibble == 1;
}
/**
* Determines whether the integer starting at `valueMarker.startIndex` and ending at `valueMarker.endIndex`
* crosses a type boundary. Callers must only invoke this method when the integer's size is known to be either
* 4 or 8 bytes.
* @return true if the value fits in the Java integer type that matches its Ion serialized size; false if it
* requires the next larger size.
*/
private boolean classifyInteger_1_0() {
if (valueTid.isNegativeInt) {
int firstByte = buffer[(int)(valueMarker.startIndex)] & SINGLE_BYTE_MASK;
if (firstByte < MOST_SIGNIFICANT_BYTE_OF_MIN_INTEGER) {
return true;
} else if (firstByte > MOST_SIGNIFICANT_BYTE_OF_MIN_INTEGER) {
return false;
}
for (long i = valueMarker.startIndex + 1; i < valueMarker.endIndex; i++) {
if (0x00 != buffer[(int)(i)]) {
return false;
}
}
return true;
}
return (buffer[(int) (valueMarker.startIndex)] & SINGLE_BYTE_MASK) <= MOST_SIGNIFICANT_BYTE_OF_MAX_INTEGER;
}
int readVarUInt_1_1() {
throw new UnsupportedOperationException();
}
private int readVarSym_1_1(Marker marker) {
throw new UnsupportedOperationException();
}
private BigDecimal readBigDecimal_1_1() {
throw new UnsupportedOperationException();
}
private Decimal readDecimal_1_1() {
throw new UnsupportedOperationException();
}
private long readLong_1_1() {
throw new UnsupportedOperationException();
}
private BigInteger readBigInteger_1_1() {
throw new UnsupportedOperationException();
}
private Timestamp readTimestamp_1_1() {
throw new UnsupportedOperationException();
}
private boolean readBoolean_1_1() {
throw new UnsupportedOperationException();
}
@Override
public Event nextValue() {
lobBytesRead = 0;
return super.nextValue();
}
/**
* Prepares the ByteBuffer to wrap a slice of the underlying buffer.
* @param startIndex the start of the slice.
* @param endIndex the end of the slice.
* @return the ByteBuffer.
*/
ByteBuffer prepareByteBuffer(long startIndex, long endIndex) {
// Setting the limit to the capacity first is required because setting the position will fail if the new
// position is outside the limit.
byteBuffer.limit(buffer.length);
byteBuffer.position((int) startIndex);
byteBuffer.limit((int) endIndex);
return byteBuffer;
}
/**
* Reads a UInt.
* @param startIndex the index of the first byte in the UInt value.
* @param endIndex the index of the first byte after the end of the UInt value.
* @return the value.
*/
private long readUInt(long startIndex, long endIndex) {
long result = 0;
for (long i = startIndex; i < endIndex; i++) {
result = (result << VALUE_BITS_PER_UINT_BYTE) | buffer[(int) i] & SINGLE_BYTE_MASK;
}
return result;
}
@Override
public boolean isNullValue() {
return valueTid != null && valueTid.isNull;
}
/**
* Performs any logic necessary to prepare a scalar value for parsing. Subclasses may wish to provide additional
* logic, such as ensuring that the value is present in the buffer.
*/
void prepareScalar() {
if (valueMarker.endIndex > limit) {
throw new IonException("Malformed data: declared length exceeds the number of bytes remaining in the stream.");
}
}
@Override
public IntegerSize getIntegerSize() {
if (valueTid == null || valueTid.type != IonType.INT || valueTid.isNull) {
return null;
}
prepareScalar();
if (valueTid.length < 0) {
return IntegerSize.BIG_INTEGER;
} else if (valueTid.length < INT_SIZE_IN_BYTES) {
return IntegerSize.INT;
} else if (valueTid.length == INT_SIZE_IN_BYTES) {
return (minorVersion != 0 || classifyInteger_1_0()) ? IntegerSize.INT : IntegerSize.LONG;
} else if (valueTid.length < LONG_SIZE_IN_BYTES) {
return IntegerSize.LONG;
} else if (valueTid.length == LONG_SIZE_IN_BYTES) {
return (minorVersion != 0 || classifyInteger_1_0()) ? IntegerSize.LONG : IntegerSize.BIG_INTEGER;
}
return IntegerSize.BIG_INTEGER;
}
private void throwDueToInvalidType(IonType type) {
throw new IllegalStateException(
String.format("Invalid type. Required %s but found %s.", type, valueTid == null ? null : valueTid.type)
);
}
@Override
public int byteSize() {
if (valueTid == null || !IonType.isLob(valueTid.type) || valueTid.isNull) {
throw new IonException("Reader must be positioned on a blob or clob.");
}
prepareScalar();
return (int) (valueMarker.endIndex - valueMarker.startIndex);
}
@Override
public byte[] newBytes() {
byte[] bytes = new byte[byteSize()];
// The correct number of bytes will be requested from the buffer, so the limit is set at the capacity to
// avoid having to calculate a limit.
System.arraycopy(buffer, (int) valueMarker.startIndex, bytes, 0, bytes.length);
return bytes;
}
@Override
public int getBytes(byte[] bytes, int offset, int len) {
int length = Math.min(len, byteSize() - lobBytesRead);
// The correct number of bytes will be requested from the buffer, so the limit is set at the capacity to
// avoid having to calculate a limit.
System.arraycopy(buffer, (int) (valueMarker.startIndex + lobBytesRead), bytes, offset, length);
lobBytesRead += length;
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() {
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) {
if (valueTid.isNull) {
return null;
}
prepareToConvertIntValue();
scalarConverter.cast(scalarConverter.get_conversion_fnid(_Private_ScalarConversions.AS_TYPE.decimal_value));
value = scalarConverter.getBigDecimal();
scalarConverter.clear();
} else if (valueTid.type == IonType.FLOAT) {
scalarConverter.addValue(doubleValue());
scalarConverter.setAuthoritativeType(_Private_ScalarConversions.AS_TYPE.double_value);
scalarConverter.cast(scalarConverter.get_conversion_fnid(_Private_ScalarConversions.AS_TYPE.decimal_value));
value = scalarConverter.getDecimal();
scalarConverter.clear();
} else {
throwDueToInvalidType(IonType.DECIMAL);
}
return value;
}
@Override
public Decimal decimalValue() {
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) {
if (valueTid.isNull) {
return null;
}
prepareToConvertIntValue();
scalarConverter.cast(scalarConverter.get_conversion_fnid(_Private_ScalarConversions.AS_TYPE.decimal_value));
value = scalarConverter.getDecimal();
scalarConverter.clear();
} else if (valueTid.type == IonType.FLOAT) {
scalarConverter.addValue(doubleValue());
scalarConverter.setAuthoritativeType(_Private_ScalarConversions.AS_TYPE.double_value);
scalarConverter.cast(scalarConverter.get_conversion_fnid(_Private_ScalarConversions.AS_TYPE.decimal_value));
value = scalarConverter.getDecimal();
scalarConverter.clear();
} else {
throwDueToInvalidType(IonType.DECIMAL);
}
return value;
}
@Override
public long longValue() {
long value;
if (valueTid.isNull) {
throwDueToInvalidType(IonType.INT);
}
if (valueTid.type == IonType.INT) {
if (valueTid.length == 0) {
return 0;
}
prepareScalar();
value = minorVersion == 0 ? readLong_1_0() : readLong_1_1();
} else if (valueTid.type == IonType.FLOAT) {
scalarConverter.addValue(doubleValue());
scalarConverter.setAuthoritativeType(_Private_ScalarConversions.AS_TYPE.double_value);
scalarConverter.cast(scalarConverter.get_conversion_fnid(_Private_ScalarConversions.AS_TYPE.long_value));
value = scalarConverter.getLong();
scalarConverter.clear();
} 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.long_value));
value = scalarConverter.getLong();
scalarConverter.clear();
} else {
throw new IllegalStateException("longValue() may only be called on non-null values of type int, float, or decimal.");
}
return value;
}
@Override
public BigInteger bigIntegerValue() {
BigInteger value;
if (valueTid.type == IonType.INT) {
if (valueTid.isNull) {
// NOTE: this mimics existing behavior, but should probably be undefined (as, e.g., longValue() is in this
// case).
return null;
}
if (valueTid.length == 0) {
return BigInteger.ZERO;
}
prepareScalar();
value = minorVersion == 0 ? readBigInteger_1_0() : readBigInteger_1_1();
} else if (valueTid.type == IonType.FLOAT) {
if (valueTid.isNull) {
value = null;
} else {
scalarConverter.addValue(doubleValue());
scalarConverter.setAuthoritativeType(_Private_ScalarConversions.AS_TYPE.double_value);
scalarConverter.cast(scalarConverter.get_conversion_fnid(_Private_ScalarConversions.AS_TYPE.bigInteger_value));
value = scalarConverter.getBigInteger();
scalarConverter.clear();
}
} else {
throw new IllegalStateException("longValue() may only be called on values of type int or float.");
}
return value;
}
@Override
public int intValue() {
return (int) longValue();
}
@Override
public double doubleValue() {
double value;
if (valueTid.isNull) {
throwDueToInvalidType(IonType.FLOAT);
}
if (valueTid.type == IonType.FLOAT) {
prepareScalar();
int length = (int) (valueMarker.endIndex - valueMarker.startIndex);
if (length == 0) {
return 0.0d;
}
ByteBuffer bytes = prepareByteBuffer(valueMarker.startIndex, valueMarker.endIndex);
if (length == FLOAT_32_BYTE_LENGTH) {
value = bytes.getFloat();
} else {
// 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) {
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 non-null values of type float or decimal.");
}
return value;
}
@Override
public Timestamp timestampValue() {
if (valueTid == null || IonType.TIMESTAMP != valueTid.type) {
throwDueToInvalidType(IonType.TIMESTAMP);
}
if (valueTid.isNull) {
return null;
}
prepareScalar();
peekIndex = valueMarker.startIndex;
return minorVersion == 0 ? readTimestamp_1_0() : readTimestamp_1_1();
}
@Override
public Date dateValue() {
Timestamp timestamp = timestampValue();
if (timestamp == null) {
return null;
}
return timestamp.dateValue();
}
@Override
public boolean booleanValue() {
if (valueTid == null || IonType.BOOL != valueTid.type || valueTid.isNull) {
throwDueToInvalidType(IonType.BOOL);
}
prepareScalar();
return minorVersion == 0 ? readBoolean_1_0() : readBoolean_1_1();
}
@Override
public String stringValue() {
if (valueTid == null || IonType.STRING != valueTid.type) {
throwDueToInvalidType(IonType.STRING);
}
if (valueTid.isNull) {
return null;
}
prepareScalar();
ByteBuffer utf8InputBuffer = prepareByteBuffer(valueMarker.startIndex, valueMarker.endIndex);
return utf8Decoder.decode(utf8InputBuffer, (int) (valueMarker.endIndex - valueMarker.startIndex));
}
@Override
public int symbolValueId() {
if (valueTid == null || IonType.SYMBOL != valueTid.type) {
throwDueToInvalidType(IonType.SYMBOL);
}
if (valueTid.isNull) {
return -1;
}
prepareScalar();
return (int) readUInt(valueMarker.startIndex, valueMarker.endIndex);
}
/**
* Gets the annotation symbol IDs for the current value, reading them from the buffer first if necessary.
* @return the annotation symbol IDs, or an empty list if the current value is not annotated.
*/
IntList getAnnotationSidList() {
annotationSids.clear();
long savedPeekIndex = peekIndex;
peekIndex = annotationSequenceMarker.startIndex;
if (minorVersion == 0) {
while (peekIndex < annotationSequenceMarker.endIndex) {
annotationSids.add(readVarUInt_1_0());
}
} else {
while (peekIndex < annotationSequenceMarker.endIndex) {
annotationSids.add(readVarUInt_1_1());
}
}
peekIndex = savedPeekIndex;
return annotationSids;
}
@Override
public int[] getAnnotationIds() {
getAnnotationSidList();
int[] annotationArray = new int[annotationSids.size()];
for (int i = 0; i < annotationArray.length; i++) {
annotationArray[i] = annotationSids.get(i);
}
return annotationArray;
}
@Override
public int getFieldId() {
return fieldSid;
}
@Override
public boolean isInStruct() {
return parent != null && parent.typeId.type == IonType.STRUCT;
}
@Override
public IonType getType() {
return valueTid == null ? null : valueTid.type;
}
@Override
public int getDepth() {
return containerIndex + 1;
}
@Override
public void close() {
utf8Decoder.close();
super.close();
}
}