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

at.spardat.xma.util.ByteArray Maven / Gradle / Ivy

// @(#) $Id: ByteArray.java 1607 2005-02-13 19:24:49Z s2266 $
package at.spardat.xma.util;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import at.spardat.enterprise.exc.SysException;

/**
 * Implements an efficient, growing byte array. The array may have an optional
 * header that consumes the first 5 bytes. The first 4 bytes reserve place for
 * the total length, the 5th byte indicates if this is compressed or not.
 * 
 * @author YSD, 28.05.2003 10:51:41
 */
public class ByteArray {
    
    /** 
     * The buffer where data is stored. 
     */
    private byte            buf[];

    /**
     * The number of valid bytes in the buffer. 
     */
    private int             count;
    
    /**
     * Holds a current position into the byte array
     */
    private int             position;
    
    /**
     * Length of the header
     */
    public static final int HEADER_LEN = 5;
    
    /**
     * Indicates if the first HEADER_LEN bytes contain header information.
     */
    private boolean         hasHeader = false;
    
    /**
     * Only relevant if hasHeader is true. Then, this varible
     * indicates if the first 4 bytes should store the length
     * of this ByteArray. Otherwise, four '0'-bytes are stored
     * in the first 4 bytes.
     */
    private boolean         fComputeHeaderLength = true;

    

    /**
     * Constructs with an internal buffer of size initialSize.
     */
    public ByteArray (int initialSize) {
        if (initialSize < 0) throw new IllegalArgumentException();
        buf = new byte[initialSize];
    }
    
    /**
     * Constructor that constructs by providing the array from outside.
     * The array is not copied, instead the provided one is used inside.
     */
    public ByteArray (byte[] array) {
        buf = array;
        count = array.length;
    }
    
    /**
     * Constructor that constructs by providing the array from outside.
     * The array is not copied, instead the provided one is used inside.
     * Only the first length bytes in array are
     * treated as data.
     */
    public ByteArray (byte[] array, int length) {
        buf = array;
        count = length;
    }
     
    /**
     * Returns how many bytes this ByteArray stores.
     */
    public int size () {
        return count;
    }
    
    /**
     * Appends the specified byte to the end of this byte array 
     *
     * @param   b   the byte to be appended
     */
    public void write (int b) {
        int newcount = count + 1;
        if (newcount > buf.length) {
            byte newbuf[] = new byte[Math.max(buf.length << 1, newcount)];
            System.arraycopy(buf, 0, newbuf, 0, count);
            buf = newbuf;
        }
        buf[count] = (byte)b;
        count = newcount;
    }

    /**
     * Appends len bytes from the specified byte array 
     * starting at offset off to this byte array.
     *
     * @param   b     the data.
     * @param   off   the start offset in the data.
     * @param   len   the number of bytes to write.
     */
    public void write (byte b[], int off, int len) {
        if ((off < 0) || (off > b.length) || (len < 0) ||
                ((off + len) > b.length) || ((off + len) < 0)) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }
        int newcount = count + len;
        if (newcount > buf.length) {
            byte newbuf[] = new byte[Math.max(buf.length << 1, newcount)];
            System.arraycopy(buf, 0, newbuf, 0, count);
            buf = newbuf;
        }
        System.arraycopy(b, off, buf, count, len);
        count = newcount;
    }
    
    /**
     * Appends all bytes from b to this byte array.
     */
    public void write (byte[] b) {
        write (b, 0, b.length);
    }
    
    /**
     * Extracts srcLen bytes at offset srcOffset from
     * b and copies them to the offset dstOffset in this.
     * The size is adjusted if necessary. 
     */
    public void set (byte[] b, int srcOffset, int srcLen, int dstOffset) {
        int newMaxCount = dstOffset + srcLen;
        if (newMaxCount > buf.length) {
            byte newbuf[] = new byte[Math.max(buf.length << 1, newMaxCount)];
            System.arraycopy(buf, 0, newbuf, 0, count);
            buf = newbuf;
        }
        System.arraycopy(b, srcOffset, buf, dstOffset, srcLen);
        count = Math.max (count, newMaxCount);
    }
    
    /**
     * All accumulated output in this is discarded. size returns
     * zero after this call if this did not have a header. Otherwise, 
     * it contains the bytes for the header. The current position
     * is reset to zero.
     */
    public void reset() {
        count = 0;
        if (hasHeader) {
            hasHeader = false;
            addHeader();
        }
        position = 0;
    }
    
    /**
     * Returns the internally stored byte array. The array may be longer then
     * size(), so you may only use the first size() bytes.
     * If you modify the returned byte [], you are modifying this. 
     */
    public byte [] getBuffer () {
        setLengthInHeader();
        return buf;
    }
    
    /**
     * Returns a copy of the bytes stored in this. The length of the returned
     * array is size().
     */
    public byte [] getBytes () {
        setLengthInHeader();
        byte [] toReturn = new byte[count];
        System.arraycopy(buf, 0, toReturn, 0, count);
        return toReturn;
    }
    
    /**
     * Reads all bytes from the provided inputstream in until an EOF
     * or an error is detected and stores the bytes at the end of this buffer.
     * 
     * @param in input stream to read from
     */
    public void readFrom (InputStream in) throws IOException {
        byte []                 buffer = new byte[1024];
        int read = in.read (buffer);
        while (read != -1) {
            write (buffer, 0, read);
            read = in.read (buffer);
        }
    }
    
    /**
     * Reads bytes from the provided inputstream in until either
     * EOF or the size of this is maxSize. Bytes are stored
     * at the end of this buffer.
     * 
     * @param in input stream to read from
     * @param maxSize maximum size of this
     */
    public void readFrom (InputStream in, int maxSize) throws IOException {
        byte []                 buffer = new byte[1024];
        int left = maxSize - size();
        if (left <= 0) return;
        int read = in.read (buffer, 0, Math.min (1024, left));
        while (read != -1) {
            write (buffer, 0, read);
            left = maxSize - size();
            if (left <= 0) return;
            read = in.read (buffer, 0, Math.min (1024, left));
        }
    }
    
    /**
     * Returns the byte at the current position and increments the position indicator.
     */
    public byte get () {
        return buf[position++];
    }
    
    /**
     * Returns the byte at (position+offset) without changing the current position.
     */
    public byte getRel (int offset) {
        return buf[position+offset];
    }
    
    /**
     * Sets the current position to the provided index.
     */
    public void setPosition (int pos) {
        position = pos;
    }
    
    /**
     * Current position is set to zero.
     */
    public void rewind () {
        position = 0;
    }
    
    /**
     * Returns current position
     */
    public int getPosition () {
        return position;
    }
    
    /**
     * Returns the number of bytes from the current position until the end.
     */
    public int remaining () {
        return Math.max(count-position, 0);
    }
    
    /**
     * Returns true if there are bytes from the current position until the end.
     */
    public boolean hasRemaining () {
        return position < count;
    }
    
    /**
     * Writes a hex-dump of data to the given printwriter.
     * For debugging.
     */
    public static void dump (byte [] data, int len, PrintWriter pw) {
        for (int i=0; i= 0x20 && ch <= 0x7E) { pw.print (' '); pw.print((char)ch); }
            else {
                // write as two hex bytes
                String      hex = Integer.toHexString(ch & 0xFF);
                if (hex.length() == 1) pw.print('0');
                pw.print(hex);
            }
            if ((i%32)==0 && i>0) pw.println();
        }
    }
    
    /**
     * Writes hex-dump to System.out.
     */
    public static void dump (byte [] data) {
        PrintWriter     pw = null;
        try {
            pw = new PrintWriter (System.out);
            dump (data, data.length, pw);
        } finally {
            pw.flush();
            pw.close();
        }
    }
    
    /**
     * Inner nested class to write to the end of the buffer by
     * using an OutputStream interface.
     * 
     * @author YSD, 13.02.2005
     */
    public class ByteArrayOS extends OutputStream {
        /**
         * @see java.io.OutputStream#write(int)
         */
        public void write (int b) throws IOException {
            ByteArray.this.write (b);
        }
        /**
         * @see java.io.OutputStream#write(byte[])
         */
        public void write (byte[] b) throws IOException {
            ByteArray.this.write (b);
        }
        /**
         * @see java.io.OutputStream#write(byte[], int, int)
         */
        public void write(byte[] b, int off, int len) throws IOException {
            ByteArray.this.write (b, off, len);
        }
    }
    
    /**
     * Returns an OutputStream that allows to append to this.
     */
    public OutputStream getOutputStream () {
        return new ByteArrayOS ();
    }
    
    /**
     * Indicates if this has a header.
     */
    public boolean hasHeader () {
        return hasHeader;
    }
    
    /**
     * Adds header at the beginning of this. The size of this must
     * be zero. This must not already have a header.
     */
    public void addHeader () {
        if (hasHeader) throw new IllegalStateException();
        if (count != 0) throw new IllegalStateException();
        hasHeader = true;
        write (new byte[] { '0', '0', '0', '0', 'U'});
    }
    
    /**
     * Sets the header-status of this. If true, 
     * it is the responsibility of the caller that this 
     * has at least HEADER_LEN bytes stored.
     */
    public void setHeader (boolean what) {
        hasHeader = what;
    }
    
    /**
     * Forces the header to indicate the length of this or not.
     * 
     * @exception IllegalStateException is not hasHeader() 
     */
    public void setComputeHeaderLength (boolean value) {
        if (!hasHeader) throw new IllegalStateException();
        fComputeHeaderLength = value;
    }
    
    /**
     * Returns the length stored in the header. Warning: The length is not
     * updated if data is appended. If you want to compute the length in 
     * the header, call setLengthInHeader().
     * 
     * @return    -1 if the length is unknown (first 4 bytes are ASCII-zeros). Otherwise
     *             the length as stored in the header.
     * @exception IllegalStateException if this does not have a header
     */
    public int getLengthInHeader () {
        if (!hasHeader) throw new IllegalStateException();
        if (buf[0] == '0' && buf[1] == '0' && buf[2] == '0' && buf[3] == '0') return -1;
        return getInt (buf, 0);
    }
    
    /**
     * Internal method that sets the length information in the header
     */
    private void setLengthInHeader () {
        if (!hasHeader) return;
        if (fComputeHeaderLength) putInt (buf, 0, count);
        else {
            buf[0] = '0'; buf[1] = '0'; buf[2] = '0'; buf[3] = '0';
        }
    }
    
    /**
     * Expects 4 bytes in b at offset off as produced by putInt
     * and returns the corresponding integer.
     */
    private static int getInt (byte[] b, int off) {
        return ((b[off + 3] & 0xFF) << 0) +
               ((b[off + 2] & 0xFF) << 8) +
               ((b[off + 1] & 0xFF) << 16) +
               ((b[off + 0] & 0xFF) << 24);
    }
    
    /**
     * Stores val as four bytes in the array b at offset off
     * using big endian byte order.
     */
    private static void putInt (byte[] b, int off, int val) {
        b[off + 3] = (byte) (val >>> 0);
        b[off + 2] = (byte) (val >>> 8);
        b[off + 1] = (byte) (val >>> 16);
        b[off + 0] = (byte) (val >>> 24);
    }
    
    /**
     * Returns if the stream is compressed. This information is stored in the 5th byte.
     */
    public boolean isCompressed () {
        if (!hasHeader) throw new IllegalStateException();
        return buf[4] == 'C';
    }
    
    /**
     * Sets the compression status.
     * 
     * @param what    compression boolean
     */
    private void setCompressed (boolean what) {
        buf[4] = (byte)(what ? 'C' : 'U');
    }
    
    /**
     * Creates a new ByteArray by compressing the contents of this.
     * This (the receiver of this method) is not changed by this operation. 
     */
    public ByteArray getCompressed () {
        if (isCompressed()) throw new SysException ("already compressed");
        ByteArray               result = new ByteArray (size());
        result.addHeader();
        result.setComputeHeaderLength(fComputeHeaderLength);
        GZIPOutputStream        gZipOut = null;
        try {
            gZipOut = new GZIPOutputStream (result.getOutputStream());
            gZipOut.write (buf, HEADER_LEN, count-HEADER_LEN);
            gZipOut.close();
        } catch (IOException x) {
            throw new SysException (x);
        }
        result.setCompressed(true);
        return result;
    }
    
    /**
     * Creates a new ByteArray by uncompressing the contents of this.
     * This (the receiver of this method) is not changed by this operation. 
     */
    public ByteArray getUncompressed () {
        if (!isCompressed()) throw new SysException ("already uncompressed");
        ByteArray               result = new ByteArray (size() * 2);
        result.addHeader();
        result.setComputeHeaderLength(fComputeHeaderLength);
        GZIPInputStream         gZipIn = null;
        try {
            gZipIn = new GZIPInputStream (new ByteArrayInputStream (buf, HEADER_LEN, count-HEADER_LEN));
            byte[]              buf = new byte[1024];
            int                 read = 0;
            while ((read = gZipIn.read(buf)) > 0) {
                result.write (buf, 0, read);
            }
            gZipIn.close();
        } catch (IOException x) {
            throw new SysException (x);
        }
        result.setCompressed(false);
        return result;
    }
    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy