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

net.pwall.util.ByteArrayBuilder Maven / Gradle / Ivy

/*
 * @(#) ByteArrayBuilder.java
 *
 * javautil Java Utility Library
 * Copyright (c) 2013, 2014, 2017 Peter Wall
 *
 * 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 net.pwall.util;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

/**
 * A dynamic byte array class.  This class allows byte array to be created dynamically, similar
 * to a {@link StringBuilder} for strings.
 * {@link java.io.ByteArrayOutputStream ByteArrayOutputStream} also provides equivalent
 * functionality, but its write methods are {@code synchronized}.
 *
 * @author Peter Wall
 */
public class ByteArrayBuilder {

    public static final int MINIMUM_READ_BUFFER_SIZE = 4096;

    private byte[] buf;
    private int count;
    private int increment;

    /**
     * Construct a {@code ByteArrayBuilder} with the specified initial size and increment.
     *
     * @param   size        the initial size (may be zero)
     * @param   increment   the amount to increment the size by when necessary
     * @throws  IllegalArgumentException if the size is negative or the increment is not
     *          positive
     */
    public ByteArrayBuilder(int size, int increment) {
        if (size < 0)
            throw new IllegalArgumentException("Size must be >= 0");
        if (increment <= 0)
            throw new IllegalArgumentException("Increment must be > 0");
        buf = new byte[size];
        count = 0;
        this.increment = increment;
    }

    /**
     * Construct a {@code ByteArrayBuilder} with the specified initial size.
     *
     * @param   size        the initial size
     * @throws  IllegalArgumentException if the size is negative
     */
    public ByteArrayBuilder(int size) {
        this(size, Math.max(size / 2, 1));
    }

    /**
     * Construct a {@code ByteArrayBuilder} with the default initial size of 20.
     */
    public ByteArrayBuilder() {
        this(20);
    }

    /**
     * Construct a {@code ByteArrayBuilder} from an existing byte array.
     *
     * @param   array   the byte array
     */
    public ByteArrayBuilder(byte[] array) {
        this(array != null ? array.length : 20);
        append(array);
    }

    /**
     * Construct a {@code ByteArrayBuilder} from another {@code ByteArrayBuilder}.
     *
     * @param   bab     the other {@code ByteArrayBuilder}
     */
    public ByteArrayBuilder(ByteArrayBuilder bab) {
        this(bab != null ? bab.length() : 20);
        append(bab);
    }

    /**
     * Get the length of the {@code ByteArrayBuilder} (the number of bytes used).
     *
     * @return  the current length
     */
    public int length() {
        return count;
    }

    /**
     * Get a byte array of only the bytes used.
     *
     * @return  the byte array
     */
    public byte[] toByteArray() {
        return Arrays.copyOf(buf, count);
    }

    /**
     * Get a byte array of only the bytes used, starting from a nominated point.
     *
     * @param   start   the start index
     * @return  the byte array
     * @throws  IndexOutOfBoundsException if the start index is outside the bounds of the array
     */
    public byte[] toByteArray(int start) {
        if (start < 0 || start > count)
            throw new IndexOutOfBoundsException("start=" + start + "; count=" + count);
        return copyOf(start, count);
    }

    /**
     * Get a byte array of only the bytes used, using a nominated start and end point.
     *
     * @param   start   the start index
     * @param   end     the end index
     * @return  the byte array
     * @throws  IndexOutOfBoundsException if the start or end index is not valid
     */
    public byte[] toByteArray(int start, int end) {
        if (start < 0 || start > end || end > count)
            throw new IndexOutOfBoundsException("start=" + start + "; end=" + end +
                    "; count=" + count);
        return copyOf(start, end);
    }

    /**
     * Make a copy of a specified part of the array, assuming range checks already done.
     *
     * @param   start   the start index
     * @param   end     the end index
     * @return  the byte array
     */
    private byte[] copyOf(int start, int end) {
        int n = end - start;
        byte[] result = new byte[n];
        System.arraycopy(buf, start, result, 0, n);
        return result;
    }

    /**
     * Set a nominated byte in the array to a given value.
     *
     * @param   index   the index to be modified
     * @param   value   the new value
     * @return  the previous value at that index
     * @throws  IndexOutOfBoundsException if the index is not valid
     */
    public byte set(int index, int value) {
        if (index < 0 || index >= count)
            throw new IndexOutOfBoundsException("index=" + index + "; count=" + count);
        byte result = buf[index];
        buf[index] = (byte)value;
        return result;
    }

    /**
     * Get a nominated byte from the array.
     *
     * @param   index   the index of the byte
     * @return  the value at that index
     * @throws  IndexOutOfBoundsException if the index is not valid
     */
    public byte get(int index) {
        if (index < 0 || index >= count)
            throw new IndexOutOfBoundsException("index=" + index + "; count=" + count);
        return buf[index];
    }

    /**
     * Ensure the array has sufficient capacity for new data.
     *
     * @param   newCapacity     the capacity required
     */
    private void ensureCapacity(int newCapacity) {
        int oldCapacity = buf.length;
        if (newCapacity > oldCapacity) {
            int proposed = oldCapacity + increment;
            if (proposed < newCapacity)
                proposed = newCapacity;
            buf = Arrays.copyOf(buf, proposed);
        }
    }

    /**
     * Append an {@code int} as a single byte.
     *
     * @param   b       the new value
     * @return  {@code this} (for chaining)
     */
    public ByteArrayBuilder append(int b) {
        ensureCapacity(count + 1);
        buf[count++] = (byte)b;
        return this;
    }

    /**
     * Append a byte array.
     *
     * @param   array   the byte array
     * @return  {@code this} (for chaining)
     */
    public ByteArrayBuilder append(byte[] array) {
        if (array != null) {
            int n = array.length;
            ensureCapacity(count + n);
            System.arraycopy(array, 0, buf, count, n);
            count += n;
        }
        return this;
    }

    /**
     * Append a specified range of bytes from a byte array.
     *
     * @param   array   the byte array
     * @param   start   the start index
     * @param   end     the end index
     * @return  {@code this} (for chaining)
     */
    public ByteArrayBuilder append(byte[] array, int start, int end) {
        if (array != null) {
            if (start < 0 || start > end || end > array.length)
                throw new IndexOutOfBoundsException("start=" + start + "; end=" + end +
                        "; length=" + array.length);
            int n = end - start;
            ensureCapacity(count + n);
            System.arraycopy(array, start, buf, count, n);
            count += n;
        }
        return this;
    }

    /**
     * Append another {@code ByteArrayBuilder}.
     *
     * @param   bab     the other {@code ByteArrayBuilder}
     * @return  {@code this} (for chaining)
     */
    public ByteArrayBuilder append(ByteArrayBuilder bab) {
        if (bab != null) {
            int n = bab.length();
            ensureCapacity(count + n);
            System.arraycopy(bab.buf, 0, buf, count, n);
            count += n;
        }
        return this;
    }

    /**
     * Append the contents of an {@link InputStream}.
     *
     * @param   is      the {@link InputStream}
     * @return  {@code this} (for chaining)
     * @throws  IOException if thrown by the {@link InputStream}
     */
    public ByteArrayBuilder append(InputStream is) throws IOException {
        if (is != null) {
            int readSize = Math.max(buf.length, MINIMUM_READ_BUFFER_SIZE);
            byte[] readBuf = new byte[readSize];
            for (;;) {
                int i = is.read(readBuf);
                if (i < 0)
                    break;
                append(readBuf, 0, i);
            }
        }
        return this;
    }

    /**
     * Insert an {@code int} as a single byte at the nominated point in the array.  All
     * following bytes will be moved up make room.
     *
     * @param   index   the index at which to insert
     * @param   b       the new value
     * @return  {@code this} (for chaining)
     * @throws  IndexOutOfBoundsException if the index is not valid
     */
    public ByteArrayBuilder insert(int index, int b) {
        if (index < 0 || index > count)
            throw new IndexOutOfBoundsException("index=" + index + "; count=" + count);
        ensureCapacity(count + 1);
        if (index < count)
            System.arraycopy(buf, index, buf, index + 1, count - index);
        buf[index] = (byte)b;
        count++;
        return this;
    }

    /**
     * Insert a byte array at the nominated point in the array.  All following bytes will be
     * moved up make room.
     *
     * @param   index   the index at which to insert
     * @param   array   the byte array
     * @return  {@code this} (for chaining)
     * @throws  IndexOutOfBoundsException if the index is not valid
     */
    public ByteArrayBuilder insert(int index, byte[] array) {
        if (index < 0 || index > count)
            throw new IndexOutOfBoundsException("index=" + index + "; count=" + count);
        if (array != null) {
            int n = array.length;
            ensureCapacity(count + n);
            if (index < count)
                System.arraycopy(buf, index, buf, index + n, count - index);
            System.arraycopy(array, 0, buf, index, n);
            count += n;
        }
        return this;
    }

    /**
     * Insert another {@code ByteArrayBuilder} at the nominated point in the array.  All
     * following bytes will be moved up make room.
     *
     * @param   index   the index at which to insert
     * @param   bab     the other {@code ByteArrayBuilder}
     * @return  {@code this} (for chaining)
     * @throws  IndexOutOfBoundsException if the index is not valid
     */
    public ByteArrayBuilder insert(int index, ByteArrayBuilder bab) {
        if (index < 0 || index > count)
            throw new IndexOutOfBoundsException("index=" + index + "; count=" + count);
        if (bab != null) {
            int n = bab.count;
            ensureCapacity(count + n);
            if (index < count)
                System.arraycopy(buf, index, buf, index + n, count - index);
            System.arraycopy(bab.buf, 0, buf, index, n);
            count += n;
        }
        return this;
    }

    /**
     * Convenience method to create a {@code ByteArrayBuilder}.  Supports the idiom:
     * 
     *     ByteArrayBuilder bab = ByteArrayBuilder.create().append(0).append(1).append(2);
     * 
* * @return the new {@code ByteArrayBuilder} */ public ByteArrayBuilder create() { return new ByteArrayBuilder(); } /** * Convenience method to create a {@code ByteArrayBuilder}, specifying the initial size. * Supports the idiom (for example): *
     *     InputStream is = new FileInputStream("data.bin");
     *     byte[] fileContents = ByteArrayBuilder.create(1000).append(is).toByteArray();
     * 
* * @param size the initial size * @return the new {@code ByteArrayBuilder} * @throws IllegalArgumentException if the size is negative */ public ByteArrayBuilder create(int size) { return new ByteArrayBuilder(size); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy