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

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

There is a newer version: 1.11.9
Show newest version
/*
 * Copyright 2007-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package com.amazon.ion.impl;

import com.amazon.ion.Decimal;
import com.amazon.ion.IonException;
import com.amazon.ion.Timestamp;
import com.amazon.ion.Timestamp.Precision;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;

/**
 * Manages a very simple byte buffer that is a single contiguous
 * byte array, without resize ability.
 */
final class SimpleByteBuffer
    implements ByteBuffer
{
    byte[]  _bytes;
    int     _start;
    int     _eob;
    boolean _is_read_only;


    /**
     * Creates a read-write buffer.
     *
     * @param bytes assumed to be owned by this new instance.
     */
    public SimpleByteBuffer(byte[] bytes) {
        this(bytes, 0, bytes.length, false);
    }

    /**
     *
     * @param bytes assumed to be owned by this new instance.
     */
    public SimpleByteBuffer(byte[] bytes, boolean isReadOnly) {
        this(bytes, 0, bytes.length, isReadOnly);
    }

    /**
     * Creates a read-write buffer.
     *
     * @param bytes assumed to be owned by this new instance.
     */
    public SimpleByteBuffer(byte[] bytes, int start, int length) {
        this(bytes, start, length, false);
    }

    /**
     *
     * @param bytes assumed to be owned by this new instance.
     */
    public SimpleByteBuffer(byte[] bytes, int start, int length, boolean isReadOnly) {
        if (bytes == null || start < 0 || start > bytes.length || length < 0 || start + length > bytes.length) {
            throw new IllegalArgumentException();
        }
        _bytes = bytes;
        _start = start;
        _eob   = start + length;
        _is_read_only = isReadOnly;
    }

    public int getLength()
    {
        int length = _eob - _start;
        return length;
    }

    /**
     * Makes a copy of the internal byte array.
     */
    public byte[] getBytes()
    {
        int length = _eob - _start;
        byte[] copy = new byte[length];
        System.arraycopy(_bytes, _start, copy, 0, length);
        return copy;
    }

    public int getBytes(byte[] buffer, int offset, int length)
    {
        if (buffer == null || offset < 0 || offset > buffer.length || length < 0 || offset + length > buffer.length) {
            throw new IllegalArgumentException();
        }

        int datalength = _eob - _start;
        if (datalength > length) {
            throw new IllegalArgumentException("insufficient space in destination buffer");
        }

        System.arraycopy(_bytes, _start, buffer, offset, datalength);

        return datalength;

    }
    public ByteReader getReader()
    {
        ByteReader reader = new SimpleByteReader(this);
        return reader;
    }

    public ByteWriter getWriter()
    {
        if (_is_read_only) {
            throw new IllegalStateException("this buffer is read only");
        }
        SimpleByteWriter writer = new SimpleByteWriter(this);
        return writer;
    }

    public void writeBytes(OutputStream out) throws IOException
    {
        int length = _eob - _start;
        out.write(_bytes, _start, length);
    }

    static final class SimpleByteReader implements ByteReader
    {
        SimpleByteBuffer _buffer;
        int              _position;

        SimpleByteReader(SimpleByteBuffer bytebuffer) {
            _buffer = bytebuffer;
            _position = bytebuffer._start;
        }

        public int position()
        {
            int pos = _position - _buffer._start;
            return pos;
        }

        public void position(int newPosition)
        {
            if (newPosition < 0) {
                throw new IllegalArgumentException("position must be non-negative");
            }
            int pos = newPosition + _buffer._start;
            if (pos > _buffer._eob) {
               throw new IllegalArgumentException("position is past end of buffer");
            }
            _position = pos;
        }

        public void skip(int length)
        {
            if (length < 0) {
                throw new IllegalArgumentException("length to skip must be non-negative");
            }
            int pos = _position + length;
            if (pos > _buffer._eob) {
               throw new IllegalArgumentException("skip would skip past end of buffer");
            }
            _position = pos;
        }

        public int read()
        {
            if (_position >= _buffer._eob) return EOF;
            int b = _buffer._bytes[_position++];
            return (b & 0xff);
        }

        public int read(byte[] dst, int start, int len)
        {
            if (dst == null || start < 0 || len < 0 || start + len > dst.length) {
                // no need to test this start >= dst.length ||
                // since we test start+len > dst.length which is the correct test
                throw new IllegalArgumentException();
            }

            if (_position >= _buffer._eob) return 0;
            int readlen = len;
            if (readlen + _position > _buffer._eob) readlen = _buffer._eob - _position;
            System.arraycopy(_buffer._bytes, _position, dst, start, readlen);
            _position += readlen;
            return readlen;
        }

        public int readTypeDesc()
        {
            return read();
        }

        public long readULong(int len) throws IOException
        {
            long    retvalue = 0;
            int b;

            switch (len) {
            default:
                throw new IonException("value too large for Java long");
            case 8:
                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 8) | b;
            case 7:
                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 8) | b;
            case 6:
                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 8) | b;
            case 5:
                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 8) | b;
            case 4:
                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 8) | b;
            case 3:
                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 8) | b;
            case 2:
                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 8) | b;
            case 1:
                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 8) | b;
            case 0:
                // do nothing, it's just a 0 length is a 0 value
            }
            return retvalue;
        }


        public int readVarInt() throws IOException
        {
            int     retvalue = 0;
            boolean is_negative = false;
            int     b;

            // synthetic label "done" (yuck)
done:       for (;;) {
                // read the first byte - it has the sign bit
                if ((b = read()) < 0) throwUnexpectedEOFException();
                if ((b & 0x40) != 0) {
                    is_negative = true;
                }
                retvalue = (b & 0x3F);
                if ((b & 0x80) != 0) break done;

                // for the second byte we shift our eariler bits just as much,
                // but there are fewer of them there to shift
                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 7) | (b & 0x7F);
                if ((b & 0x80) != 0) break done;

                // for the rest, they're all the same
                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 7) | (b & 0x7F);
                if ((b & 0x80) != 0) break done;

                // for the rest, they're all the same
                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 7) | (b & 0x7F);
                if ((b & 0x80) != 0) break done;

                // for the rest, they're all the same
                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 7) | (b & 0x7F);
                if ((b & 0x80) != 0) break done;

                // if we get here we have more bits than we have room for :(
                throwIntOverflowExeption();
            }
            if (is_negative) {
                retvalue = -retvalue;
            }
            return retvalue;
        }

        public long readVarLong() throws IOException
        {
            long    retvalue = 0;
            boolean is_negative = false;
            int     b;

            // synthetic label "done" (yuck)
done:       for (;;) {
                // read the first byte - it has the sign bit
                if ((b = read()) < 0) throwUnexpectedEOFException();
                if ((b & 0x40) != 0) {
                    is_negative = true;
                }
                retvalue = (b & 0x3F);
                if ((b & 0x80) != 0) break done;

                // for the second byte we shift our eariler bits just as much,
                // but there are fewer of them there to shift
                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 7) | (b & 0x7F);
                if ((b & 0x80) != 0) break done;

                // for the rest, they're all the same
                for (;;) {
                    if ((b = read()) < 0) throwUnexpectedEOFException();
                    if ((retvalue & 0xFE00000000000000L) != 0) throwIntOverflowExeption();
                    retvalue = (retvalue << 7) | (b & 0x7F);
                    if ((b & 0x80) != 0) break done;
                }
            }
            if (is_negative) {
                retvalue = -retvalue;
            }
            return retvalue;
        }

        /**
         * Reads an integer value, returning null to mean -0.
         * @throws IOException
         */
        public Integer readVarInteger() throws IOException
        {
            int     retvalue = 0;
            boolean is_negative = false;
            int     b;

            // sythetic label "done" (yuck)
done:       for (;;) {
                // read the first byte - it has the sign bit
                if ((b = read()) < 0) throwUnexpectedEOFException();
                if ((b & 0x40) != 0) {
                    is_negative = true;
                }
                retvalue = (b & 0x3F);
                if ((b & 0x80) != 0) break done;

                // for the second byte we shift our eariler bits just as much,
                // but there are fewer of them there to shift
                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 7) | (b & 0x7F);
                if ((b & 0x80) != 0) break done;

                // for the rest, they're all the same
                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 7) | (b & 0x7F);
                if ((b & 0x80) != 0) break done;

                // for the rest, they're all the same
                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 7) | (b & 0x7F);
                if ((b & 0x80) != 0) break done;

                // for the rest, they're all the same
                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 7) | (b & 0x7F);
                if ((b & 0x80) != 0) break done;

                // if we get here we have more bits than we have room for :(
                throwIntOverflowExeption();
            }

            Integer retInteger = null;
            if (is_negative) {
                if (retvalue != 0) {
                    retInteger = new Integer(-retvalue);
                }
            }
            else {
                retInteger = new Integer(retvalue);
            }
            return retInteger;
        }

        public int readVarUInt() throws IOException
        {
            int retvalue = 0;
            int  b;

            for (;;) { // fake loop to create a "goto done"
                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 7) | (b & 0x7F);
                if ((b & 0x80) != 0) break;

                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 7) | (b & 0x7F);
                if ((b & 0x80) != 0) break;

                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 7) | (b & 0x7F);
                if ((b & 0x80) != 0) break;

                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 7) | (b & 0x7F);
                if ((b & 0x80) != 0) break;

                if ((b = read()) < 0) throwUnexpectedEOFException();
                retvalue = (retvalue << 7) | (b & 0x7F);
                if ((b & 0x80) != 0) break;

                // if we get here we have more bits than we have room for :(
                throwIntOverflowExeption();
            }
            return retvalue;
        }

        public double readFloat(int len) throws IOException
        {
            if (len == 0)
            {
                // special case, return pos zero
                return 0.0d;
            }

            if (len != 8)
            {
                throw new IOException("Length of float read must be 0 or 8");
            }

            long dBits = this.readULong(len);
            return Double.longBitsToDouble(dBits);
        }

        public long readVarULong() throws IOException
        {
            long retvalue = 0;
            int  b;

            for (;;) {
                if ((b = read()) < 0) throwUnexpectedEOFException();
                if ((retvalue & 0xFE00000000000000L) != 0) throwIntOverflowExeption();
                retvalue = (retvalue << 7) | (b & 0x7F);
                if ((b & 0x80) != 0) break;
            }
            return retvalue;
        }

        /**
         * Near clone of {@link IonReaderBinaryRawX#readDecimal(int)}
         * and {@link IonBinary.Reader#readDecimalValue(IonDecimalImpl, int)}
         * so keep them in sync!
         */
        public Decimal readDecimal(int len) throws IOException
        {
            MathContext mathContext = MathContext.UNLIMITED;

            Decimal bd;

            // we only write out the '0' value as the nibble 0
            if (len == 0) {
                bd = Decimal.valueOf(0, mathContext);
            }
            else {
                // otherwise we to it the hard way ....
                int         startpos = this.position();
                int         exponent = this.readVarInt();
                int         bitlen = len - (this.position() - startpos);

                BigInteger value;
                int signum;
                if (bitlen > 0)
                {
                    byte[] bits = new byte[bitlen];
                    this.read(bits, 0, bitlen);

                    signum = 1;
                    if (bits[0] < 0)
                    {
                        // value is negative, clear the sign
                        bits[0] &= 0x7F;
                        signum = -1;
                    }
                    value = new BigInteger(signum, bits);
                }
                else {
                    signum = 0;
                    value = BigInteger.ZERO;
                }

                // Ion stores exponent, BigDecimal uses the negation "scale"
                int scale = -exponent;
                if (value.signum() == 0 && signum == -1)
                {
                    assert value.equals(BigInteger.ZERO);
                    bd = Decimal.negativeZero(scale, mathContext);
                }
                else
                {
                    bd = Decimal.valueOf(value, scale, mathContext);
                }
            }
            return bd;
        }

        public Timestamp readTimestamp(int len) throws IOException
        {
            if (len < 1) {
                // nothing to do here - and the timestamp will be NULL
                return null;
            }

            Timestamp val;
            Precision   p = null; // FIXME remove
            Integer     offset = null;
            int         year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
            BigDecimal  frac = null;
            int         remaining, end = this.position() + len;

            // first up is the offset, which requires a special int reader
            // to return the -0 as a null Integer
            offset = this.readVarInteger();

            // now we'll read the struct values from the input stream
            assert position() < end;
            if (position() < end) {  // FIXME remove
                // year is from 0001 to 9999
                // or 0x1 to 0x270F or 14 bits - 1 or 2 bytes
                year  = readVarUInt();
                p = Precision.YEAR; // our lowest significant option

                // now we look for hours and minutes
                if (position() < end) {
                    month = readVarUInt();
                    p = Precision.MONTH;

                    // now we look for hours and minutes
                    if (position() < end) {
                        day   = readVarUInt();
                        p = Precision.DAY; // our lowest significant option

                        // now we look for hours and minutes
                        if (position() < end) {
                            hour   = readVarUInt();
                            minute = readVarUInt();
                            p = Precision.MINUTE;

                            if (position() < end) {
                                second = readVarUInt();
                                p = Precision.SECOND;

                                remaining = end - position();
                                if (remaining > 0) {
                                    // now we read in our actual "milliseconds since the epoch"
                                    frac = this.readDecimal(remaining);
                                }
                            }
                        }
                    }
                }
            }

            // now we let timestamp put it all together
            val = Timestamp.createFromUtcFields(p, year, month, day, hour, minute, second, frac, offset);
            return val;
        }

        public String readString(int len) throws IOException
        {
            // len is bytes, which is greater than or equal to java
            // chars even after utf8 to utf16 decoding nonsense
            // the char array is way faster than using string buffer
            char[] chars = new char[len];
            int ii = 0;
            int c;
            int endPosition = this.position() + len;

            while (this.position() < endPosition) {
                c = readUnicodeScalar();
                if (c < 0) throwUnexpectedEOFException();
                if (c < 0x10000) {
                    chars[ii++] = (char)c;
                }
                else { // when c is >= 0x10000 we need surrogate encoding
                    chars[ii++] = (char)_Private_IonConstants.makeHighSurrogate(c);
                    chars[ii++] = (char)_Private_IonConstants.makeLowSurrogate(c);
                }
            }
            if (this.position() < endPosition) throwUnexpectedEOFException();

            return new String(chars, 0, ii);
        }

        public final int readUnicodeScalar() throws IOException {
            int b;

            b = read();
            // ascii is all good, even -1 (eof)
            if (b >= 0x80) {
                b = readUnicodeScalar_helper(b);

            }
            return b;
        }
        private final  int readUnicodeScalar_helper(int b) throws IOException
        {
            int c = -1;

            // now we start gluing the multi-byte value together
            if ((b & 0xe0) == 0xc0) {
                // for values from 0x80 to 0x7FF (all legal)
                c = (b & ~0xe0);
                b = read();
                if ((b & 0xc0) != 0x80) throwUTF8Exception();
                c <<= 6;
                c |= (b & ~0x80);
            }
            else if ((b & 0xf0) == 0xe0) {
                // for values from 0x800 to 0xFFFFF (NOT all legal)
                c = (b & ~0xf0);
                b = read();
                if ((b & 0xc0) != 0x80) throwUTF8Exception();
                c <<= 6;
                c |= (b & ~0x80);
                b = read();
                if ((b & 0xc0) != 0x80) throwUTF8Exception();
                c <<= 6;
                c |= (b & ~0x80);
                if (c > 0x00D7FF && c < 0x00E000) {
                    throw new IonException("illegal surrogate value encountered in input utf-8 stream");
                }
            }
            else if ((b & 0xf8) == 0xf0) {
                // for values from 0x010000 to 0x1FFFFF (NOT all legal)
                c = (b & ~0xf8);
                b = read();
                if ((b & 0xc0) != 0x80) throwUTF8Exception();
                c <<= 6;
                c |= (b & ~0x80);
                b = read();
                if ((b & 0xc0) != 0x80) throwUTF8Exception();
                c <<= 6;
                c |= (b & ~0x80);
                b = read();
                if ((b & 0xc0) != 0x80) throwUTF8Exception();
                c <<= 6;
                c |= (b & ~0x80);
                if (c > 0x10FFFF) {
                    throw new IonException("illegal utf value encountered in input utf-8 stream");
                }
            }
            else {
                throwUTF8Exception();
            }
            return c;
        }
        void throwUTF8Exception() throws IOException
        {
            throw new IOException("Invalid UTF-8 character encounter in a string at pos " + this.position());
        }
        void throwUnexpectedEOFException() throws IOException {
            throw new IOException("unexpected EOF in value at offset " + this.position());
        }
        void throwIntOverflowExeption() throws IOException {
            throw new IOException("int in stream is too long for a Java int 32 use readLong()");
        }
    }

    static final class UserByteWriter extends OutputStream implements ByteWriter
    {
        SimpleByteWriter _simple_writer;
        OutputStream     _user_stream;
        int              _position;
        int              _limit;
        int              _buffer_size;

        // constants used for the various write routines to verify
        // sufficient room in the output buffer for writing
        private final static int MAX_UINT7_BINARY_LENGTH = 5; // (1 + (32 / 7))
        private final static int MAX_FLOAT_BINARY_LENGTH = 8; // ieee 64 bit float

        private final static int REQUIRED_BUFFER_SPACE   = 8; // max of the required lengths

        UserByteWriter(OutputStream userOuputStream, byte[] buf)
        {
            if (buf == null || buf.length < REQUIRED_BUFFER_SPACE) {
                throw new IllegalArgumentException("requires a buffer at least "+REQUIRED_BUFFER_SPACE+" bytes long");
            }

            SimpleByteBuffer bytebuffer = new SimpleByteBuffer(buf);
            _simple_writer =  new SimpleByteWriter(bytebuffer);
            _user_stream = userOuputStream;
            _buffer_size = buf.length;
            _limit = _buffer_size;
        }

        private final void checkForSpace(int needed) {
            if (_position + needed > _limit) {
                flush();
            }
        }

        /**
         * this flushes the current buffer to the users output
         * stream and resets the buffer.  It is called automatically
         * as the buffer fills.  It can be called at any time if more
         * frequent flushing is desired.
         */
        @Override
        public void flush() {
            // is there anything to flush?
            if (_position + _buffer_size > _limit) {
                try {
                    _simple_writer.flushTo(_user_stream);
                }
                catch (IOException e) {
                    throw new IonException(e);
                }
                _limit = _position + _buffer_size;
            }
        }

        public void insert(int length)
        {
            throw new UnsupportedOperationException("use a SimpleByteWriter if you need to insert");
        }

        public int position()
        {
            return _position;
        }

        public void position(int newPosition)
        {
            throw new UnsupportedOperationException("use a SimpleByteWriter if you need to set your position");
        }

        public void remove(int length)
        {
            throw new UnsupportedOperationException("use a SimpleByteWriter if you need to remove bytes");
        }

        @Override
        public void write(int b) throws IOException
        {
            write((byte)b);
        }

        public void write(byte b) throws IOException
        {
            checkForSpace(1);
            _simple_writer.write(b);
            _position++;
        }

        // cloned from SimpleByteBuffer as we need to check the
        // length and this may require flushing more often than
        // the default writeDecimal supports.
        public int writeDecimal(BigDecimal value) throws IOException
        {
            int returnlen = _simple_writer.writeDecimal(value, this);
            return returnlen;
        }

        public int writeFloat(double value) throws IOException
        {
            checkForSpace(MAX_FLOAT_BINARY_LENGTH);
            int returnlen = _simple_writer.writeFloat(value);
            return returnlen;
        }

        public int writeIonInt(long value, int len) throws IOException
        {
            checkForSpace(len);
            int returnlen = _simple_writer.writeIonInt(value, len);
            return returnlen;
        }

        public int writeIonInt(int value, int len) throws IOException
        {
            checkForSpace(len);
            int returnlen = _simple_writer.writeIonInt(value, len);
            return returnlen;
        }

        public int writeString(String value) throws IOException
        {
            int returnlen = _simple_writer.writeString(value, this);
            return returnlen;
        }

        public void writeTypeDesc(int typeDescByte) throws IOException
        {
            checkForSpace(1);
            _simple_writer.writeTypeDesc(typeDescByte);
        }

        public int writeTypeDescWithLength(int typeid, int lenOfLength,
                                           int valueLength) throws IOException
        {
            checkForSpace(1 + MAX_UINT7_BINARY_LENGTH);
            int returnlen = _simple_writer.writeTypeDescWithLength(typeid, lenOfLength, valueLength);
            return returnlen;
        }

        public int writeTypeDescWithLength(int typeid, int valueLength) throws IOException
        {
            checkForSpace(1 + MAX_UINT7_BINARY_LENGTH);
            int returnlen = _simple_writer.writeTypeDescWithLength(typeid, valueLength);
            return returnlen;
        }

        public int writeVarInt(long value, int len, boolean forceZeroWrite)
            throws IOException
        {
            checkForSpace(len);
            int returnlen = _simple_writer.writeVarInt(value, len, forceZeroWrite);
            return returnlen;
        }

        public int writeVarInt(int value, int len, boolean forceZeroWrite)
            throws IOException
        {
            checkForSpace(len);
            int returnlen = _simple_writer.writeVarInt(value, len, forceZeroWrite);
            return returnlen;
        }

        public int writeVarUInt(int value, int len, boolean forceZeroWrite)
            throws IOException
        {
            checkForSpace(len);
            int returnlen = _simple_writer.writeVarUInt(value, len, forceZeroWrite);
            return returnlen;
        }

        public int writeVarUInt(long value, int len, boolean forceZeroWrite)
            throws IOException
        {
            checkForSpace(len);
            int returnlen = _simple_writer.writeVarUInt(value, len, forceZeroWrite);
            return returnlen;
        }
    }

    static final class SimpleByteWriter extends OutputStream implements ByteWriter
    {
        private static final int _ib_FLOAT64_LEN         =    8;
        private static final Double DOUBLE_POS_ZERO = Double.valueOf(0.0);

        SimpleByteBuffer _buffer;
        int              _position;

        SimpleByteWriter(SimpleByteBuffer bytebuffer) {
            _buffer = bytebuffer;
            _position = bytebuffer._start;
        }

        protected void flushTo(OutputStream userOutput) throws IOException {
            _buffer.writeBytes(userOutput);
            _position = 0;
        }

        public int position()
        {
            return _position - _buffer._start;
        }

        public void position(int newPosition)
        {
            if (newPosition < 0) {
                throw new IllegalArgumentException("position must be non-negative");
            }
            int pos = newPosition + _buffer._start;
            if (pos > _buffer._eob) {
               throw new IllegalArgumentException("position is past end of buffer");
            }
            _position = pos;
        }

        public void insert(int length)
        {
            if (length < 0) {
                throw new IllegalArgumentException("insert length must be non negative");
            }
            int remaining = _buffer._eob - _position;
            System.arraycopy(_buffer._bytes, _position, _buffer._bytes, _position + length, remaining);
            _buffer._eob += length;
        }

        public void remove(int length)
        {
            if (length < 0) {
                throw new IllegalArgumentException("remove length must be non negative");
            }
            int remaining = _buffer._eob - _position;
            System.arraycopy(_buffer._bytes, _position + length, _buffer._bytes, _position, remaining);
            _buffer._eob -= length;
        }

        @Override
        final public void write(int arg0)
            throws IOException
        {
            write((byte)arg0);
        }

        final public void write(byte b)
        {
            _buffer._bytes[_position++] = b;
            if (_position > _buffer._eob) _buffer._eob = _position;
        }

        @Override
        public void write(byte[] bytes, int start, int len)
        {
            if (bytes == null || start < 0 || start >= bytes.length || len < 0 || start + len > bytes.length) {
                throw new IllegalArgumentException();
            }

            System.arraycopy(bytes, start, _buffer._bytes, _position, len);
            _position += len;
            if (_position > _buffer._eob) _buffer._eob = _position;

            return;
        }

        public void writeTypeDesc(int typeDescByte)
        {
            write((byte)(typeDescByte & 0xff));
        }

        public int writeTypeDescWithLength(int typeid, int lenOfLength, int valueLength)
        {
            int written_len = 1;

            int td = ((typeid & 0xf) << 4);
            if (valueLength >= _Private_IonConstants.lnIsVarLen) {
                td |= _Private_IonConstants.lnIsVarLen;
                writeTypeDesc(td);
                written_len += writeVarUInt(valueLength, lenOfLength, true);
            }
            else {
                td |= (valueLength & 0xf);
                writeTypeDesc(td);
            }
            return written_len;
        }

        public int writeTypeDescWithLength(int typeid, int valueLength)
        {
            int written_len = 1;

            int td = ((typeid & 0xf) << 4);
            if (valueLength >= _Private_IonConstants.lnIsVarLen) {
                td |= _Private_IonConstants.lnIsVarLen;
                writeTypeDesc(td);
                int lenOfLength = IonBinary.lenVarUInt(valueLength);
                written_len += writeVarUInt(valueLength, lenOfLength, true);
            }
            else {
                td |= (valueLength & 0xf);
                writeTypeDesc(td);
            }
            return written_len;
        }

        public int writeVarInt(int value, int len, boolean force_zero_write)
        {
            // int len = 0;

            if (value != 0) {
                int mask = 0x7F;
                boolean is_negative = false;

                assert len == IonBinary.lenVarInt(value);
                if (is_negative = (value < 0)) {
                    value = -value;
                }

                // we write the first "byte" separately as it has the sign
                // changed shift operator from >> to >>>, we don't want to
                // sign extend, this is only a problem with MIN_VALUE where
                // negating the value (above) still results in a negative number
                int b = (byte)((value >>> (7*(len-1))) & mask);
                if (is_negative)  b |= 0x40; // the sign bit only on the first byte
                if (len == 1)     b |= 0x80; // the terminator in case the first "byte" is the last
                write((byte)b);

                // write the rest
                switch (len) {  // we already wrote 1 byte
                case 5: write((byte)((value >> (7*3)) & mask));
                case 4: write((byte)((value >> (7*2)) & mask));
                case 3: write((byte)((value >> (7*1)) & mask));
                case 2: write((byte)((value & mask) | 0x80));
                case 1: // do nothing
                }
            }
            else if (force_zero_write) {
                write((byte)0x80);
                assert len == 1;

            }
            else {
                assert len == 0;
            }
            return len;
        }

        public int writeVarInt(int value, boolean force_zero_write)
        {
            int len = IonBinary.lenVarInt(value);
            len = writeVarInt(value, len, force_zero_write);
            return len;
        }

        public int writeVarUInt(int value, int len, boolean force_zero_write)
        {
            int mask = 0x7F;
            if (value < 0) {
                throw new IllegalArgumentException("signed int where unsigned (>= 0) was expected");
            }
            assert len == IonBinary.lenVarUInt(value);

            switch (len - 1) {
            case 4: write((byte)((value >> (7*4)) & mask));
            case 3: write((byte)((value >> (7*3)) & mask));
            case 2: write((byte)((value >> (7*2)) & mask));
            case 1: write((byte)((value >> (7*1)) & mask));
            case 0: write((byte)((value & mask) | 0x80L));
                    break;
            case -1: // or 0
                if (force_zero_write) {
                    write((byte)0x80);
                    len = 1;
                }
                break;
            }
            return len;
        }

        public int writeVarUInt(int value, boolean force_zero_write)
        {
            int len = IonBinary.lenVarUInt(value);
            len = writeVarUInt(value, len, force_zero_write);
            return len;
        }

        public int writeIonInt(int value, int len)
        {
            return writeIonInt((long)value, len);
        }

        public int writeIonInt(long value, int len)
        {
            // we shouldn't be writing out 0's as an Ion int value
            if (value == 0) {
                assert len == 0;
                return len;  // aka 0
            }

            // figure out how many we have bytes we have to write out
            long mask = 0xffL;
            boolean is_negative = (value < 0);

            assert len == IonBinary.lenIonInt(value);

            if (is_negative) {
                value = -value;
                // note for Long.MIN_VALUE the negation returns
                // itself as a value, but that's also the correct
                // "positive" value to write out anyway, so it
                // all works out
            }

            // write the rest
            switch (len) {  // we already wrote 1 byte
            case 8: write((byte)((value >> (8*7)) & mask));
            case 7: write((byte)((value >> (8*6)) & mask));
            case 6: write((byte)((value >> (8*5)) & mask));
            case 5: write((byte)((value >> (8*4)) & mask));
            case 4: write((byte)((value >> (8*3)) & mask));
            case 3: write((byte)((value >> (8*2)) & mask));
            case 2: write((byte)((value >> (8*1)) & mask));
            case 1: write((byte)(value & mask));
            }

            return len;
        }

        public int writeVarInt(long value, int len, boolean forceZeroWrite)
        {
            //int len = 0;

            if (value != 0) {
                long mask = 0x7fL;
                assert len == IonBinary.lenInt(value);
                int b;
                if (value < 0) {
                    value = -value;
                    // we write the first "byte" separately as it has the sign
                    // and we have to deal with the oddball MIN_VALUE case
                    if (value == Long.MIN_VALUE) {
                        // we use the shift without sign extension as this
                        // represents a positive value (even though it's neg)
                        b = (byte)((value >>> (7*len)) & mask);
                        // len must be greater than 1 so we don't need to set the high bit
                    }
                    else {
                        // here, and hereafter, we don't care about sign extension
                        b = (byte)((value >>> (7*len)) & mask);
                        if (len == 1) b |= 0x80; // the terminator in case the first "byte" is the last
                    }
                    // we don't worry about stepping on a data bit here as
                    // we have extra bits at the max size and below that we've
                    // already taken the sign bit into account
                    b |= 0x40; // the sign bit
                    write((byte)b);
                }
                else {
                    // we write the first "byte" separately as it has the sign
                    b = (byte)((value >>> (7*len)) & mask);
                    if (len == 1) b |= 0x80; // the terminator in case the first "byte" is the last
                    write((byte)b);
                }

                // write the rest
                switch (len - 1) {  // we already wrote 1 byte
                case 9: write((byte)((value >>> (7*9)) & mask));
                case 8: write((byte)((value >> (7*8)) & mask));
                case 7: write((byte)((value >> (7*7)) & mask));
                case 6: write((byte)((value >> (7*6)) & mask));
                case 5: write((byte)((value >> (7*5)) & mask));
                case 4: write((byte)((value >> (7*4)) & mask));
                case 3: write((byte)((value >> (7*3)) & mask));
                case 2: write((byte)((value >> (7*2)) & mask));
                case 1: write((byte)((value >> (7*1)) & mask));
                case 0: write((byte)((value & mask) | 0x80L));
                }
            }
            else if (forceZeroWrite) {
                write((byte)0x80);
                assert len == 1;
            }
            else {
                assert len == 0;
            }
            return len;
        }

        public int writeVarUInt(long value, int len, boolean force_zero_write)
        {
            int mask = 0x7F;
            assert len == IonBinary.lenVarUInt(value);
            assert value > 0;

            switch (len - 1) {
            case 9: write((byte)((value >> (7*9)) & mask));
            case 8: write((byte)((value >> (7*8)) & mask));
            case 7: write((byte)((value >> (7*7)) & mask));
            case 6: write((byte)((value >> (7*6)) & mask));
            case 5: write((byte)((value >> (7*5)) & mask));
            case 4: write((byte)((value >> (7*4)) & mask));
            case 3: write((byte)((value >> (7*3)) & mask));
            case 2: write((byte)((value >> (7*2)) & mask));
            case 1: write((byte)((value >> (7*1)) & mask));
            case 0: write((byte)((value & mask) | 0x80L));
                    break;
            case -1: // or len == 0
                if (force_zero_write) {
                    write((byte)0x80);
                    assert len == 1;
                }
                else {
                    assert len == 0;
                }
                break;
            }
            return len;
        }

        public int writeULong(long value, int lenToWrite) throws IOException
        {
            switch (lenToWrite) {
            case 8: write((byte)((value >> 56) & 0xffL));
            case 7: write((byte)((value >> 48) & 0xffL));
            case 6: write((byte)((value >> 40) & 0xffL));
            case 5: write((byte)((value >> 32) & 0xffL));
            case 4: write((byte)((value >> 24) & 0xffL));
            case 3: write((byte)((value >> 16) & 0xffL));
            case 2: write((byte)((value >>  8) & 0xffL));
            case 1: write((byte)((value >>  0) & 0xffL));
            }
            return lenToWrite;
        }

        public int writeFloat(double value) throws IOException
        {
            if (Double.valueOf(value).equals(DOUBLE_POS_ZERO))
            {
                // pos zero special case
                return 0;
            }

            // TODO write "custom" serialization or verify that
            //      the java routine is doing the right thing
            long dBits = Double.doubleToRawLongBits(value);
            return this.writeULong(dBits, _ib_FLOAT64_LEN);
        }

        public int writeDecimal(BigDecimal value) throws IOException
        {
            int returnlen = writeDecimal(value, null);
            return returnlen;
        }

        // this is a private version that supports both the buffer write
        // and the output to the user stream.  This is slower (since there
        // are extra tests, an extra call, and an extra paramenter), but
        // it avoids duplicating this code.  And decimals are generally
        // expensive anyway.
        private int writeDecimal(BigDecimal value, UserByteWriter userWriter) throws IOException
        {
            int returnlen = 0;
            // we only write out the '0' value as the nibble 0
            if (value != null && !BigDecimal.ZERO.equals(value)) {
                // otherwise we do it the hard way ....
                BigInteger mantissa = value.unscaledValue();

                boolean isNegative = (mantissa.compareTo(BigInteger.ZERO) < 0);
                if (isNegative) {
                    mantissa = mantissa.negate();
                }

                byte[] bits  = mantissa.toByteArray();
                int    scale = value.scale();

                // Ion stores exponent, BigDecimal uses the negation "scale"
                int exponent = -scale;
                if (userWriter != null) {
                    returnlen += userWriter.writeIonInt(exponent, IonBinary.lenVarUInt(exponent));
                }
                else {
                    returnlen += this.writeIonInt(exponent, IonBinary.lenVarUInt(exponent));
                }

                // If the first bit is set, we can't use it for the sign,
                // and we need to write an extra byte to hold it.
                boolean needExtraByteForSign = ((bits[0] & 0x80) != 0);
                if (needExtraByteForSign)
                {
                    if (userWriter != null) {
                        userWriter.write((byte)(isNegative ? 0x80 : 0x00));
                    }
                    else {
                        this.write((byte)(isNegative ? 0x80 : 0x00));
                    }
                    returnlen++;
                }
                else if (isNegative)
                {
                    bits[0] |= 0x80;
                }
                // if we have a userWriter to write to, we really don't care about
                // the value in our local buffer.
                if (userWriter != null) {
                    userWriter.write(bits, 0, bits.length);
                }
                else {
                    this.write(bits, 0, bits.length);
                }
                returnlen += bits.length;
            }
            return returnlen;
        }

        public int writeTimestamp(Timestamp di)
        throws IOException
        {
            if (di == null) return 0;

            int returnlen = 0;
            Precision precision = di.getPrecision();

            Integer offset = di.getLocalOffset();
            if (offset == null) {
                // TODO don't use magic numbers!
                this.write((byte)(0xff & (0x80 | 0x40))); // negative 0 (no timezone)
                returnlen ++;
            }
            else {
                int value = offset.intValue();
                returnlen += this.writeVarInt(value, true);
            }

            // now the date - year, month, day as varUint7's
            // if we have a non-null value we have at least the date
            if (precision.includes(Precision.YEAR)) {
                returnlen += this.writeVarUInt(di.getZYear(), true);
            }
            if (precision.includes(Precision.MONTH)) {
                returnlen += this.writeVarUInt(di.getZMonth(), true);
            }
            if (precision.includes(Precision.DAY)) {
                returnlen += this.writeVarUInt(di.getZDay(), true);
            }

            // now the time part
            if (precision.includes(Precision.MINUTE)) {
                returnlen += this.writeVarUInt(di.getZHour(), true);
                returnlen += this.writeVarUInt(di.getZMinute(), true);
            }
            if (precision.includes(Precision.SECOND)) {
                returnlen += this.writeVarUInt(di.getZSecond(), true);
                BigDecimal fraction = di.getZFractionalSecond();
                if (fraction != null) {
                    // and, finally, any fractional component that is known
                    returnlen += this.writeDecimal(di.getZFractionalSecond());
                }
            }
            return returnlen;
        }

        final public int writeString(String value) throws IOException
        {
            int returnlen = writeString(value, null);
            return returnlen;
        }

        final private int writeString(String value, UserByteWriter userWriter) throws IOException
        {
            int len = 0;

            for (int ii=0; ii 127) {
                    if (c >= 0xD800 && c <= 0xDFFF) {
                        if (_Private_IonConstants.isHighSurrogate(c)) {
                            ii++;
                            // houston we have a high surrogate (let's hope it has a partner
                            if (ii >= value.length()) {
                                throw new IonException("invalid string, unpaired high surrogate character");
                            }
                            int c2 = value.charAt(ii);
                            if (!_Private_IonConstants.isLowSurrogate(c2)) {
                                throw new IonException("invalid string, unpaired high surrogate character");
                            }
                            c = _Private_IonConstants.makeUnicodeScalar(c, c2);
                        }
                        else if (_Private_IonConstants.isLowSurrogate(c)) {
                            // it's a loner low surrogate - that's an error
                            throw new IonException("invalid string, unpaired low surrogate character");
                        }
                        // from 0xE000 up the _writeUnicodeScalar will check for us
                    }
                    c = IonBinary.makeUTF8IntFromScalar(c);
                }

                // write it here - try to test userWriter as little as possible
                if (userWriter == null) {
                    for (;;) {
                        write((byte)(c & 0xff));
                        len++;
                        if ((c & 0xffffff00) == 0) {
                            break;
                        }
                        c = c >>> 8;
                    }
                }
                else {
                    for (;;) {
                        userWriter.write((byte)(c & 0xff));
                        len++;
                        if ((c & 0xffffff00) == 0) {
                            break;
                        }
                        c = c >>> 8;
                    }
                }
            }
            return len;
        }
        void throwUTF8Exception() throws IOException
        {
            throwException("Invalid UTF-8 character encounter in a string at pos " + this.position());
        }
        void throwException(String msg) throws IOException {
            throw new IOException(msg);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy