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

com.rgi.geopackage.features.ByteOutputStream Maven / Gradle / Ivy

The newest version!
/* The MIT License (MIT)
 *
 * Copyright (c) 2015 Reinventing Geospatial, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package com.rgi.geopackage.features;

import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Objects;

/**
 * @author Luke Lambert
 */
public final class ByteOutputStream implements AutoCloseable
{
    /**
     * Constructor
     *
     * Uses a default initial capacity of 32 bytes
     */
    public ByteOutputStream()
    {
        this(DEFAULT_INITIAL_CAPACITY);
    }

    /**
     * Constructor
     *
     * @param capacity
     *             Initial allocation byte size of the backing buffer
     */
    public ByteOutputStream(final int capacity)
    {
        if(capacity < 0)
        {
            throw new IllegalArgumentException("Capacity must be positive");
        }

        this.buffer = new byte[capacity];

        this.position = 0;
        this.setByteOrder(DEFAULT_INITIAL_BYTE_ORDER);
    }

    /**
     * @return a copy of the backing buffer without any trailing unused bytes
     */
    public byte[] array()
    {
        return this.position <= 0 ? EMPTY_ARRAY
                                  : Arrays.copyOfRange(this.buffer, 0, this.position);
    }

    /**
     * Writes a byte to the stream
     *
     * @param b
     *             a byte
     */
    public void write(final byte b)
    {
        this.checkCapacity(BYTE_BYTE_SIZE);

        this.buffer[this.position] = b;

        this.position += BYTE_BYTE_SIZE;
    }

    /**
     * Writes a 2 byte short to the stream using the proscribed byte order
     *
     * @param s
     *             a short
     */
    public void write(final short s)
    {
        this.checkCapacity(SHORT_BYTE_SIZE);

        this.bytePutter.put(s);

        this.position += SHORT_BYTE_SIZE;
    }

    /**
     * Writes a 4 byte int to the stream using the proscribed byte order
     *
     * @param i
     *             an int
     */
    public void write(final int i)
    {
        this.checkCapacity(INT_BYTE_SIZE);

        this.bytePutter.put(i);

        this.position += INT_BYTE_SIZE;
    }

    /**
     * Writes a 4 byte float to the stream using the proscribed byte order
     *
     * @param f
     *             a float
     */
    public void write(final float f)
    {
        this.write(Float.floatToRawIntBits(f));
    }

    /**
     * Writes an 8 byte long to the stream using the proscribed byte order
     *
     * @param l
     *             a long
     */
    public void write(final long l)
    {
        this.checkCapacity(LONG_BYTE_SIZE);

        this.bytePutter.put(l);

        this.position += LONG_BYTE_SIZE;
    }

    /**
     * Writes an 8 byte double to the stream using the proscribed byte order
     *
     * @param d
     *             a double
     */
    public void write(final double d)
    {
        this.write(Double.doubleToLongBits(d));
    }

    /**
     * Writes a series of bytes to the stream
     *
     * @param bytes
     *             a byte array
     */
    public void write(final byte[] bytes)
    {
        if(bytes == null)
        {
            throw new IllegalArgumentException("Bytes may not be null");
        }

        this.checkCapacity(bytes.length);

        System.arraycopy(bytes, 0, this.buffer, this.position, bytes.length);

        this.position += bytes.length;
    }

    public ByteOrder getByteOrder()
    {
        return this.bytePutter.getByteOrder();
    }

    public void setByteOrder(final ByteOrder byteOrder)
    {
        if(byteOrder == null)
        {
            throw new IllegalArgumentException("Byte order may not be null");
        }

        this.bytePutter = Objects.equals(byteOrder, ByteOrder.BIG_ENDIAN) ? this.bigEndianBytePutter
                                                                          : this.littleEndianBytePutter;
    }

    private void checkCapacity(final int requestedBytes)
    {
        if(this.position + requestedBytes > this.buffer.length)
        {
            this.resize(requestedBytes);
        }
    }

    private void resize(final int requestedBytes)
    {
        final int minimum = this.buffer.length + requestedBytes;

        // Growing by 1.5 seems to be the most common strategy:
        // https://groups.google.com/forum/#!msg/comp.lang.c++.moderated/asH_VojWKJw/4jJHJXPzWJ0J
        @SuppressWarnings("NumericCastThatLosesPrecision")
        final int oneAndAHalf = (int)Math.ceil(this.buffer.length * 1.5);

        final int newSize = Math.max(minimum, oneAndAHalf);
        final byte[] newBuffer = new byte[newSize];

        System.arraycopy(this.buffer, 0, newBuffer, 0, this.buffer.length);

        this.buffer = newBuffer;
    }

    @SuppressWarnings({"MagicNumber", "NumericCastThatLosesPrecision"}) private static byte short1(final short x) { return (byte)(x >> 8); } // x >> (1 * 8)
    @SuppressWarnings({"MagicNumber", "NumericCastThatLosesPrecision"}) private static byte short0(final short x) { return (byte)(x     ); } // x >> (0 * 8)

    @SuppressWarnings({"MagicNumber", "NumericCastThatLosesPrecision"}) private static byte int3(final int x) { return (byte)(x >> 24); } // x >> (3 * 8)
    @SuppressWarnings({"MagicNumber", "NumericCastThatLosesPrecision"}) private static byte int2(final int x) { return (byte)(x >> 16); } // x >> (2 * 8)
    @SuppressWarnings({"MagicNumber", "NumericCastThatLosesPrecision"}) private static byte int1(final int x) { return (byte)(x >>  8); } // x >> (1 * 8)
    @SuppressWarnings({"MagicNumber", "NumericCastThatLosesPrecision"}) private static byte int0(final int x) { return (byte)(x      ); } // x >> (0 * 8)

    @SuppressWarnings({"MagicNumber", "NumericCastThatLosesPrecision"}) private static byte long7(final long x) { return (byte)(x >> 56); } // x >> (7 * 8)
    @SuppressWarnings({"MagicNumber", "NumericCastThatLosesPrecision"}) private static byte long6(final long x) { return (byte)(x >> 48); } // x >> (6 * 8)
    @SuppressWarnings({"MagicNumber", "NumericCastThatLosesPrecision"}) private static byte long5(final long x) { return (byte)(x >> 40); } // x >> (5 * 8)
    @SuppressWarnings({"MagicNumber", "NumericCastThatLosesPrecision"}) private static byte long4(final long x) { return (byte)(x >> 32); } // x >> (4 * 8)
    @SuppressWarnings({"MagicNumber", "NumericCastThatLosesPrecision"}) private static byte long3(final long x) { return (byte)(x >> 24); } // x >> (3 * 8)
    @SuppressWarnings({"MagicNumber", "NumericCastThatLosesPrecision"}) private static byte long2(final long x) { return (byte)(x >> 16); } // x >> (2 * 8)
    @SuppressWarnings({"MagicNumber", "NumericCastThatLosesPrecision"}) private static byte long1(final long x) { return (byte)(x >>  8); } // x >> (1 * 8)
    @SuppressWarnings({"MagicNumber", "NumericCastThatLosesPrecision"}) private static byte long0(final long x) { return (byte)(x      ); } // x >> (0 * 8)

    private interface BytePutter
    {
        void put(final short s);
        void put(final int   i);
        void put(final long  l);

        ByteOrder getByteOrder();
    }

    private final BytePutter bigEndianBytePutter = new BytePutter()
    {
        @Override
        public void put(final short s)
        {
            ByteOutputStream.this.buffer[ByteOutputStream.this.position    ] = short1(s);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 1] = short0(s);
        }

        @Override
        public void put(final int i)
        {
            ByteOutputStream.this.buffer[ByteOutputStream.this.position    ] = int3(i);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 1] = int2(i);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 2] = int1(i);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 3] = int0(i);
        }

        @Override
        public void put(final long l)
        {
            ByteOutputStream.this.buffer[ByteOutputStream.this.position    ] = long7(l);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 1] = long6(l);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 2] = long5(l);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 3] = long4(l);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 4] = long3(l);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 5] = long2(l);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 6] = long1(l);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 7] = long0(l);
        }

        @Override
        public ByteOrder getByteOrder()
        {
            return ByteOrder.BIG_ENDIAN;
        }
    };

    private final BytePutter littleEndianBytePutter = new BytePutter()
    {
        @Override
        public void put(final short s)
        {
            ByteOutputStream.this.buffer[ByteOutputStream.this.position    ] = short0(s);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 1] = short1(s);
        }

        @Override
        public void put(final int i)
        {
            ByteOutputStream.this.buffer[ByteOutputStream.this.position    ] = int0(i);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 1] = int1(i);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 2] = int2(i);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 3] = int3(i);
        }

        @Override
        public void put(final long l)
        {
            ByteOutputStream.this.buffer[ByteOutputStream.this.position    ] = long0(l);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 1] = long1(l);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 2] = long2(l);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 3] = long3(l);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 4] = long4(l);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 5] = long5(l);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 6] = long6(l);
            ByteOutputStream.this.buffer[ByteOutputStream.this.position + 7] = long7(l);
        }

        @Override
        public ByteOrder getByteOrder()
        {
            return ByteOrder.LITTLE_ENDIAN;
        }
    };

    private byte[]     buffer;
    private int        position;
    private BytePutter bytePutter;

    private static final ByteOrder DEFAULT_INITIAL_BYTE_ORDER = ByteOrder.BIG_ENDIAN;
    private static final int       DEFAULT_INITIAL_CAPACITY   = 32;

    private static final int BYTE_BYTE_SIZE   = 1;
    private static final int SHORT_BYTE_SIZE  = 2;
    private static final int INT_BYTE_SIZE    = 4;
    private static final int FLOAT_BYTE_SIZE  = 4;
    private static final int LONG_BYTE_SIZE   = 8;
    private static final int DOUBLE_BYTE_SIZE = 8;

    private static final byte[] EMPTY_ARRAY = {};

    @Override
    public void close()
    {
        //noinspection AssignmentToNull
        this.buffer = null;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy