package com.limegroup.gnutella.util; import java.io.IOException; import java.io.OutputStream; import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; /** * A ByteArrayOutputStream that uses ByteBuffers internally and can optionally * grow or throw IOExceptions when the maximum size is reached and more is written. * * This exposes many methods to make using byte[]'s & ByteBuffers more efficient. */ public class BufferByteArrayOutputStream extends ByteArrayOutputStream { /** The backing ByteBuffer. If growth is enabled, the buffer may change. */ protected ByteBuffer buffer; /** Whether or not this can grow. */ protected boolean grow; /** * Creates an OutputStream initially sized at 32 that can grow. */ public BufferByteArrayOutputStream() { this(32); } /** * Creates an OutputStream of the given size that can grow. */ public BufferByteArrayOutputStream(int size) { this(ByteBuffer.allocate(size), true); } /** * Creates an OutputStream of the given size that can grow as needed. */ public BufferByteArrayOutputStream(int size, boolean grow) { this(ByteBuffer.allocate(size), grow); } /** * Creates an OutputStream with the given backing array that cannot grow. */ public BufferByteArrayOutputStream(byte[] backing) { this(ByteBuffer.wrap(backing), false); } /** * Creates an OutputStream using the given backing array, starting at position * 'pos' and allowing writes for the given length. The stream cannot grow. */ public BufferByteArrayOutputStream(byte[] backing, int pos, int length) { this(ByteBuffer.wrap(backing, pos, length), false); } /** * Creates an OutputStream backed by the given ByteBuffer. The stream cannot grow. */ public BufferByteArrayOutputStream(ByteBuffer backing) { this(backing, false); } /** * Creates an OutputStream backed by the given ByteBuffer. If 'grow' is true, * then the referenced ByteBuffer may change when the backing array is grown. */ public BufferByteArrayOutputStream(ByteBuffer backing, boolean grow) { this.buffer = backing; this.grow = grow; } /** Does nothing. */ public void close() throws IOException {} /** Resets the data so that the backing buffer can be reused. */ public void reset() { buffer.clear(); } /** Returns the amount of data currently stored. */ public int size() { return buffer.position(); } /** * Returns a byte[] of the valid bytes written to this stream. * * This _may_ return a reference to the backing array itself (but it is not * guaranteed to), so the BufferByteArrayOutputStream should not be used again * after this is called if you want to preserve the contents of the array. */ public byte[] toByteArray() { byte[] arr = buffer.array(); int offset = buffer.arrayOffset(); int position = buffer.position(); if(offset == 0 && position == arr.length) return arr; // no need to copy, the array is all filled up. byte[] out = new byte[position]; System.arraycopy(arr, offset, out, 0, position); return out; } /** * Returns the backing buffer. */ public ByteBuffer buffer() { return buffer; } /** * Writes the current data to the given buffer. * If the sink cannot hold all the data stored in this buffer, * nothing is written and a BufferOverflowException is thrown. * All written bytes are cleared. */ public void writeTo(ByteBuffer sink) { buffer.flip(); sink.put(buffer); buffer.compact(); } /** * Writes the current data to the given byte[]. * If the data is larger than the byte[], nothing is written * and a BufferOverflowException is thrown. * All written bytes are cleared. */ public void writeTo(byte[] out) { writeTo(out, 0, out.length); } /** * Writes the current data to the given byte[], starting at offset off and going * for length len. If the data is larger than the length, nothing is written and * a BufferOverflowException is thrown. * All written bytes are cleared. */ public void writeTo(byte[] out, int off, int len) { buffer.flip(); buffer.get(out, off, len); buffer.compact(); } /** * Converts the buffer's contents into a string, translating bytes into * characters according to the platform's default character encoding. */ public String toString() { return new String(buffer.array(), buffer.arrayOffset(), buffer.position()); } /** * Converts the buffer's contents into a string, translating bytes into * characters according to the specified character encoding. */ public String toString(String encoding) throws UnsupportedEncodingException { return new String(buffer.array(), buffer.arrayOffset(), buffer.position(), encoding); } /** Grows the buffer to accomodate the given size. */ private void grow(int len) { int size = buffer.capacity(); int newSize = Math.max(size << 1, size + len); ByteBuffer newBuffer = buffer.allocate(newSize); buffer.flip(); newBuffer.put(buffer); buffer = newBuffer; } /** * Writes the byte[] to the buffer, starting at off for len bytes. * If the buffer cannot grow and this exceeds the size of the buffer, a * BufferOverflowException is thrown and no data is written. * If the buffer can grow, a new buffer is created & data is written. */ public void write(byte[] b, int off, int len) { if(grow && len > buffer.remaining()) grow(len); buffer.put(b, off, len); } /** * Writes the given byte to the buffer. * If the buffer is already full and cannot grow, a BufferOverflowException is thrown * and no data is written. If the buffer can grow, a new buffer is created * & data is written. */ public void write(int b) { if(grow && !buffer.hasRemaining()) grow(1); buffer.put((byte)b); } /** * Writes the buffer to the given OutputStream. * All written bytes are cleared. */ public void writeTo(OutputStream out) throws IOException { out.write(buffer.array(), buffer.arrayOffset(), buffer.position()); buffer.clear(); } }