blob: 238aa6513904dbdfec61280bb12030154f063698 [file] [log] [blame]
/* $Id$
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License 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 org.apache.etch.util;
import java.io.EOFException;
import java.io.IOException;
/**
* A FlexBuffer wraps a byte array and manages the active region of
* it (0..length). It also supports dynamically extending the buffer
* as needed.
*
* A FlexBuffer also has an index (read or write cursor). The various
* get and put operations always occur at the current index, with the
* index adjusted appropriately afterward. Get will not move index past
* length. If put needs to move index past length, length is also
* adjusted. This may cause the byte array to be re-allocated to a
* larger size.
*/
public final class FlexBuffer
{
/**
* Constructs a FlexBuffer with internal buffer and index and length of 0.
*/
public FlexBuffer()
{
this( new byte[INIT_BUFFER_LEN], 0, 0 );
}
/**
* Constructs the FlexBuffer out of an existing buffer, ready to
* read, with the index set to 0 and length set to buffer.length.
* The buffer is adopted, not copied. If you want to copy, use
* one of the put methods instead.
*
* Note: this is the same as FlexBuffer( buffer, 0, buffer.length ).
*
* @param buffer the buffer to adopt.
*
* @see #put(byte[])
*/
public FlexBuffer( byte[] buffer )
{
this( buffer, 0, buffer.length );
}
/**
* Constructs the FlexBuffer out of an existing buffer, ready to
* read, with the index set to 0 and the specified length. The
* buffer is adopted, not copied. If you want to copy, use one
* of the put methods instead.
*
* Note: this is the same as FlexBuffer( buffer, 0, length ).
*
* @param buffer the buffer to adopt.
*
* @param length the length of the data in the buffer (index is
* presumed to be 0).
*
* @see #put(byte[], int, int)
*/
public FlexBuffer( byte[] buffer, int length )
{
this( buffer, 0, length );
}
/**
* Constructs the FlexBuffer out of an existing buffer, ready to
* read, with the specified index and length. The buffer is adopted,
* not copied. If you want to copy, use one of the put methods
* instead.
*
* Note: if you want to start off writing to the end of the buffer
* and not reading it, specify index as the place to start writing,
* and length as the amount of data that is valid after index (which
* might reasonably be 0).
*
* @param buffer the buffer to adopt.
*
* @param index the place to start reading or writing.
*
* @param length the length of the valid data in the buffer,
* starting at index.
*
* @see #put(byte[], int, int)
*/
public FlexBuffer( byte[] buffer, int index, int length )
{
if (buffer == null)
throw new NullPointerException( "buffer == null" );
if (index < 0)
throw new IllegalArgumentException( "index < 0" );
if (length < 0)
throw new IllegalArgumentException( "length < 0" );
if (index+length > buffer.length)
throw new IllegalArgumentException( "index+length > buffer.length" );
this.buffer = buffer;
this.index = index;
this.length = index+length;
}
/**
* @return the current byte array. Might change if any operation
* needs to extend length past the end of the array.
*/
final public byte[] getBuf()
{
return buffer;
}
final private void ensureLength( int len ) throws IOException
{
int n = buffer.length;
if (len <= n)
return;
// the buffer is not big enough, we have to expand it.
int k = n;
if (k < INIT_BUFFER_LEN)
k = INIT_BUFFER_LEN;
while (len > k && k < MAX_BUFFER_LEN)
k = k << 1;
if (len > k)
throw new IOException( "buffer overflow" );
byte[] b = new byte[k];
System.arraycopy( buffer, 0, b, 0, n );
buffer = b;
}
private byte[] buffer;
private final static int INIT_BUFFER_LEN = 32;
private final static int TRIM_BUFFER_LEN = 16*1024;
private final static int MAX_BUFFER_LEN = 4*1024*1024;
/**
* @return the current value of length. This is the last
* index of valid data in the buffer.
*/
final public int length()
{
return length;
}
/**
* @param length the new value of length. Length must be >= 0.
* If length < index, index is also set to length. If length
* is larger than the current buffer, the buffer is expanded.
* @return this flex buffer object.
* @throws IllegalArgumentException if length < 0.
* @throws IOException if the buffer overflows its max length.
*/
final public FlexBuffer setLength( int length ) throws IllegalArgumentException, IOException
{
if (length < 0)
throw new IllegalArgumentException( "length < 0" );
ensureLength( length );
this.length = length;
if (index > length)
index = length;
return this;
}
private int length;
/**
* @return the current value of index.
*/
final public int index()
{
return index;
}
/**
* @param index the new value of index. Index must be >= 0.
* @return this flex buffer object.
* @throws IllegalArgumentException if index < 0 or if index > length.
*/
final public FlexBuffer setIndex( int index ) throws IllegalArgumentException
{
if (index < 0 || index > length)
throw new IllegalArgumentException( "index < 0 || index > length" );
this.index = index;
return this;
}
private int index;
/**
* @return length() - index(). Result is always >= 0. This is the amount
* of data that could be read using the various forms of get. It doesn't
* really mean anything in relation to put.
*/
final public int avail()
{
return length - index;
}
/**
* Sets both index and length to 0. Trims the buffer length if it is past
* the recommended limit.
*
* Shorthand for setLength( 0 ).
*
* @return this flex buffer object.
*/
final public FlexBuffer reset()
{
index = 0;
length = 0;
if (buffer.length > TRIM_BUFFER_LEN)
buffer = new byte[TRIM_BUFFER_LEN];
return this;
}
/**
* Compacts the buffer by moving remaining data (from index to length)
* to the front of the buffer. Sets index to 0, and sets length to
* avail (before index was changed).
*
* @return this flex buffer object.
*/
final public FlexBuffer compact()
{
if (index == 0)
return this;
int n = avail();
if (n == 0)
{
reset();
return this;
}
System.arraycopy( buffer, index, buffer, 0, n );
index = 0;
length = n;
return this;
}
/**
* @return the unsigned byte value at index, and adjusts index
* by adding one.
* @throws IOException if avail() < 1.
*/
final public int get() throws IOException
{
checkAvail( 1 );
return buffer[index++] & 255;
}
/**
* Copies data from the byte array to buf as if by repeated
* calls to get().
* @param buf a buffer to receive the data. At most
* min( buf.length, avail() ) bytes are transferred.
* @return the amount of data transferred.
* @throws IOException if avail() < 1.
*/
final public int get( byte[] buf ) throws IOException
{
return get( buf, 0, buf.length );
}
/**
* Copies data from the byte array to buf as if by repeated
* calls to get().
* @param buf a buffer to receive the data. At most
* min( len, avail() ) bytes are transferred, starting
* at off.
* @param off the index in buf to receive the data.
* Off must be >= 0 && <= buf.length.
* @param len the max amount of data to transfer. Len
* must be >= 0 and <= buf.length - off.
* @return the amount of data transferred.
* @throws IOException if avail() < 1
*/
final public int get( byte[] buf, int off, int len ) throws IOException
{
checkBuf( buf, off, len );
if (len == 0)
return 0;
checkAvail( 1 );
int n = Math.min( len, avail() );
System.arraycopy( buffer, index, buf, off, n );
index += n;
return n;
}
/**
* @return the next available byte.
* @throws IOException if avail() < 1.
*/
final public byte getByte() throws IOException
{
checkAvail( 1 );
return buffer[index++];
}
private final static boolean littleEndian = false;
/**
* @return a short composed from the next 2 bytes. Little-endian.
* @throws IOException if avail() < 2.
*/
final public short getShort() throws IOException
{
checkAvail( 2 );
if (littleEndian)
{
// little-endian
int value = buffer[index++] & 255;
return (short) (value + ((buffer[index++] & 255) << 8));
}
// big-endian
int value = buffer[index++];
return (short) ((value << 8) + (buffer[index++] & 255));
}
/**
* @return an int composed from the next 4 bytes. Little-endian.
* @throws IOException if avail() < 4.
*/
final public int getInt() throws IOException
{
checkAvail( 4 );
if (littleEndian)
{
// little-endian
int value = buffer[index++] & 255;
value += (buffer[index++] & 255) << 8;
value += (buffer[index++] & 255) << 16;
return value + ((buffer[index++] & 255) << 24);
}
// big-endian
int value = buffer[index++];
value = (value << 8) + (buffer[index++] & 255);
value = (value << 8) + (buffer[index++] & 255);
return (value << 8) + (buffer[index++] & 255);
}
/**
* @return a long composed from the next 8 bytes. Little-endian.
* @throws IOException if avail() < 8.
*/
final public long getLong() throws IOException
{
checkAvail( 8 );
if (littleEndian)
{
// little-endian
long value = buffer[index++] & 255;
value += (long)(buffer[index++] & 255) << 8;
value += (long)(buffer[index++] & 255) << 16;
value += (long)(buffer[index++] & 255) << 24;
value += (long)(buffer[index++] & 255) << 32;
value += (long)(buffer[index++] & 255) << 40;
value += (long)(buffer[index++] & 255) << 48;
return value + ((long)(buffer[index++] & 255) << 56);
}
// big-endian
long value = buffer[index++];
value = (value << 8) + (buffer[index++] & 255);
value = (value << 8) + (buffer[index++] & 255);
value = (value << 8) + (buffer[index++] & 255);
value = (value << 8) + (buffer[index++] & 255);
value = (value << 8) + (buffer[index++] & 255);
value = (value << 8) + (buffer[index++] & 255);
return (value << 8) + (buffer[index++] & 255);
}
/**
* @return a float from the next available bytes.
* @throws IOException if avail() < 4.
*/
final public float getFloat() throws IOException
{
return Float.intBitsToFloat( getInt() );
}
/**
* @return a double from the next available bytes.
* @throws IOException if avail() < 8.
*/
final public double getDouble() throws IOException
{
return Double.longBitsToDouble( getLong() );
}
/**
* Fills a buffer fully from the next available bytes.
* @param b
* @throws IOException if avail() < b.length.
*/
final public void getFully( byte[] b ) throws IOException
{
checkAvail( b.length );
int n = get( b, 0, b.length );
assert n == b.length;
}
/**
* Puts a byte into the buffer at the current index,
* then adjusts the index by one. Adjusts the length
* as necessary. The buffer is expanded if needed.
* @param b
* @return this flex buffer object.
* @throws IOException if the buffer overflows its max length.
*/
final public FlexBuffer put( int b ) throws IOException
{
ensureLength( index+1 );
buffer[index++] = (byte) b;
fixLength();
return this;
}
/**
* Puts some bytes into the buffer as if by repeated
* calls to put().
* @param buf the source of the bytes to put. The entire
* array of bytes is put.
* @return this flex buffer object.
* @throws IOException if the buffer overflows its max length.
*/
final public FlexBuffer put( byte[] buf ) throws IOException
{
return put( buf, 0, buf.length );
}
/**
* Puts some bytes into the buffer as if by repeated
* calls to put().
* @param buf the source of the bytes to put.
* @param off the index to the first byte to put.
* @param len the number of bytes to put.
* @return this flex buffer object.
* @throws IOException if the buffer overflows its max length.
*/
final public FlexBuffer put( byte[] buf, int off, int len ) throws IOException
{
checkBuf( buf, off, len );
if (len == 0)
return this;
ensureLength( index+len );
System.arraycopy( buf, off, buffer, index, len );
index += len;
fixLength();
return this;
}
/**
* Copies the available bytes from buf into buffer as if by
* repeated execution of put( buf.get() ).
* @param buf the source of the bytes to put. All available
* bytes are copied.
* @return this flex buffer object.
* @throws IOException if the buffer overflows its max length.
*/
final public FlexBuffer put( FlexBuffer buf ) throws IOException
{
int n = buf.avail();
put( buf.buffer, buf.index, n );
buf.skip( n, false );
return this;
}
/**
* Copies the specified number of bytes from buf into buffer
* as if by repeated execution of put( buf.get() ).
* @param buf the source of the bytes to put.
* @param len the number of bytes to put. Len must be <=
* buf.avail().
* @return this flex buffer object.
* @throws IOException if the buffer overflows its max length.
*/
final public FlexBuffer put( FlexBuffer buf, int len ) throws IOException
{
if (len > buf.avail())
throw new IllegalArgumentException( "len > buf.avail()" );
put( buf.buffer, buf.index, len );
buf.skip( len, false );
return this;
}
/**
* Puts the byte.
* @param value
* @throws IOException
*/
final public void putByte( byte value ) throws IOException
{
ensureLength( index+1 );
buffer[index++] = value;
fixLength();
}
/**
* Puts the short as the next 2 bytes. Little-endian.
* @param value
* @throws IOException
*/
final public void putShort( short value ) throws IOException
{
ensureLength( index+2 );
if (littleEndian)
{
buffer[index++] = (byte) value;
buffer[index++] = (byte) (value >> 8);
}
else
{
buffer[index++] = (byte) (value >>> 8);
buffer[index++] = (byte) value;
}
fixLength();
}
/**
* Puts the int as the next 4 bytes. Little-endian.
* @param value
* @throws IOException
*/
final public void putInt( int value ) throws IOException
{
ensureLength( index+4 );
if (littleEndian)
{
buffer[index++] = (byte) value;
buffer[index++] = (byte) (value >> 8);
buffer[index++] = (byte) (value >> 16);
buffer[index++] = (byte) (value >> 24);
}
else
{
buffer[index++] = (byte) (value >>> 24);
buffer[index++] = (byte) (value >>> 16);
buffer[index++] = (byte) (value >>> 8);
buffer[index++] = (byte) value;
}
fixLength();
}
/**
* Puts the long as the next 8 bytes. Little-endian.
* @param value
* @throws IOException
*/
final public void putLong( long value ) throws IOException
{
ensureLength( index+8 );
if (littleEndian)
{
buffer[index++] = (byte) value;
buffer[index++] = (byte) (value >> 8);
buffer[index++] = (byte) (value >> 16);
buffer[index++] = (byte) (value >> 24);
buffer[index++] = (byte) (value >> 32);
buffer[index++] = (byte) (value >> 40);
buffer[index++] = (byte) (value >> 48);
buffer[index++] = (byte) (value >> 56);
}
else
{
buffer[index++] = (byte) (value >>> 56);
buffer[index++] = (byte) (value >>> 48);
buffer[index++] = (byte) (value >>> 40);
buffer[index++] = (byte) (value >>> 32);
buffer[index++] = (byte) (value >>> 24);
buffer[index++] = (byte) (value >>> 16);
buffer[index++] = (byte) (value >>> 8);
buffer[index++] = (byte) value;
}
fixLength();
}
/**
* Puts the float as the next 4 bytes. Little-endian.
* @param value
* @throws IOException
*/
final public void putFloat( float value ) throws IOException
{
putInt( Float.floatToIntBits( value ) );
}
/**
* Puts the double as the next 8 bytes. Little-endian.
* @param value
* @throws IOException
*/
final public void putDouble( double value ) throws IOException
{
putLong( Double.doubleToLongBits( value ) );
}
/**
* Adjusts index as if by a get or put but without transferring
* any data. This could be used to skip over a data item in an
* input or output buffer.
*
* @param len the number of bytes to skip over. Len must be
* >= 0. If put is false, it is an error if len > avail().
* If put is true, the buffer may be extended (and the buffer
* length adjusted).
*
* @param put if true it is ok to extend the length of the
* buffer.
*
* @return this flex buffer object.
*
* @throws EOFException if put is false and len > avail().
*
* @throws IOException if the max buffer size is exceeded.
*/
final public FlexBuffer skip( int len, boolean put ) throws IOException
{
if (len < 0)
throw new IllegalArgumentException( "len < 0" );
if (len == 0)
return this;
if (put)
{
ensureLength( index+len );
index += len;
fixLength();
return this;
}
checkAvail( len );
index += len;
return this;
}
/**
* If index has moved past length during a put, then adjust length
* to track index.
*/
final private void fixLength()
{
if (index > length)
length = index;
}
/**
* Checks buf, off, and len for being reasonable.
* @param buf
* @param off
* @param len
*/
final private void checkBuf( byte[] buf, int off, int len )
{
if (buf == null)
throw new NullPointerException( "buf == null" );
if (off < 0 || off > buf.length)
throw new IllegalArgumentException( "off < 0 || off > buf.length" );
if (len < 0)
throw new IllegalArgumentException( "len < 0" );
if (off+len > buf.length)
throw new IllegalArgumentException( "off+len > buf.length" );
}
/**
* @return the currently available bytes.
* @throws IOException
*/
final public byte[] getAvailBytes() throws IOException
{
byte[] buf = new byte[avail()];
get( buf );
return buf;
}
/**
* Checks that there are enough bytes to for a read.
* @param len the length required by a read operation.
* @throws EOFException if len > avail().
*/
final private void checkAvail( int len ) throws IOException
{
if (len > avail())
throw new EOFException( "len > avail()" );
}
}