All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazon.ion.impl.IonReaderContinuableCoreBinary Maven / Gradle / Ivy

There is a newer version: 1.11.9
Show 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 {
            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 {
            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();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy