at.spardat.xma.util.ByteArray Maven / Gradle / Ivy
The newest version!
// @(#) $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;
}
}