/*
* Circular Byte Buffer
* Copyright (C) 2002-2010 Stephen Ostermiller
* http://ostermiller.org/contact.pl?regarding=Java+Utilities
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* See LICENSE.txt for details.
*/
package glaze.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.BufferOverflowException;
/**
* Implements the Circular Buffer producer/consumer model for bytes. More
* information about this class is available from <a target="_top" href=
* "http://ostermiller.org/utils/CircularByteBuffer.html">ostermiller.org</a>.
* <p>
* Using this class is a simpler alternative to using a PipedInputStream and a
* PipedOutputStream. PipedInputStreams and PipedOutputStreams don't support the
* mark operation, don't allow you to control buffer sizes that they use, and
* have a more complicated API that requires instantiating two classes and
* connecting them.
* <p>
* This class is thread safe.
*
* @see CircularCharBuffer
* @see CircularObjectBuffer
*
* @author Stephen Ostermiller
* http://ostermiller.org/contact.pl?regarding=Java+Utilities
* @since ostermillerutils 1.00.00
*/
public class CircularByteBuffer
{
/**
* Class for reading from a circular byte buffer.
*
* @since ostermillerutils 1.00.00
*/
protected class CircularByteBufferInputStream extends InputStream
{
/**
* Returns the number of bytes that can be read (or skipped over) from
* this input stream without blocking by the next caller of a method for
* this input stream. The next caller might be the same thread or or
* another thread.
*
* @return the number of bytes that can be read from this input stream
* without blocking.
* @throws IOException
* if the stream is closed.
*
* @since ostermillerutils 1.00.00
*/
@Override
public int available() throws IOException
{
synchronized (CircularByteBuffer.this) {
if (inputStreamClosed)
throw new IOException("InputStream has been closed, it is not ready.");
return (CircularByteBuffer.this.available());
}
}
/**
* Close the stream. Once a stream has been closed, further read(),
* available(), mark(), or reset() invocations will throw an IOException.
* Closing a previously-closed stream, however, has no effect.
*
* @throws IOException
* never.
*
* @since ostermillerutils 1.00.00
*/
@Override
public void close() throws IOException
{
synchronized (CircularByteBuffer.this) {
inputStreamClosed = true;
}
}
/**
* Mark the present position in the stream. Subsequent calls to reset()
* will attempt to reposition the stream to this point.
* <p>
* The readAheadLimit must be less than the size of circular buffer,
* otherwise this method has no effect.
*
* @param readAheadLimit
* Limit on the number of bytes that may be read while still
* preserving the mark. After reading this many bytes,
* attempting to reset the stream will fail.
*
* @since ostermillerutils 1.00.00
*/
@Override
public void mark(int readAheadLimit)
{
synchronized (CircularByteBuffer.this) {
// if (inputStreamClosed) throw new
// IOException("InputStream has been closed; cannot mark a closed InputStream.");
if (buffer.length - 1 > readAheadLimit) {
markSize = readAheadLimit;
markPosition = readPosition;
}
}
}
/**
* Tell whether this stream supports the mark() operation.
*
* @return true, mark is supported.
*
* @since ostermillerutils 1.00.00
*/
@Override
public boolean markSupported()
{
return true;
}
/**
* Read a single byte. This method will block until a byte is available,
* an I/O error occurs, or the end of the stream is reached.
*
* @return The byte read, as an integer in the range 0 to 255 (0x00-0xff),
* or -1 if the end of the stream has been reached
* @throws IOException
* if the stream is closed.
*
* @since ostermillerutils 1.00.00
*/
@Override
public int read() throws IOException
{
while (true) {
synchronized (CircularByteBuffer.this) {
if (inputStreamClosed)
throw new IOException("InputStream has been closed; cannot read from a closed InputStream.");
int available = CircularByteBuffer.this.available();
if (available > 0) {
int result = buffer[readPosition] & 0xff;
readPosition++;
if (readPosition == buffer.length) {
readPosition = 0;
}
ensureMark();
return result;
} else if (outputStreamClosed) {
return -1;
}
}
try {
Thread.sleep(100);
} catch (Exception x) {
throw new IOException("Blocking read operation interrupted.");
}
}
}
/**
* Read bytes into an array. This method will block until some input is
* available, an I/O error occurs, or the end of the stream is reached.
*
* @param cbuf
* Destination buffer.
* @return The number of bytes read, or -1 if the end of the stream has
* been reached
* @throws IOException
* if the stream is closed.
*
* @since ostermillerutils 1.00.00
*/
@Override
public int read(byte[] cbuf) throws IOException
{
return read(cbuf, 0, cbuf.length);
}
/**
* Read bytes into a portion of an array. This method will block until
* some input is available, an I/O error occurs, or the end of the stream
* is reached.
*
* @param cbuf
* Destination buffer.
* @param off
* Offset at which to start storing bytes.
* @param len
* Maximum number of bytes to read.
* @return The number of bytes read, or -1 if the end of the stream has
* been reached
* @throws IOException
* if the stream is closed.
*
* @since ostermillerutils 1.00.00
*/
@Override
public int read(byte[] cbuf, int off, int len) throws IOException
{
while (true) {
synchronized (CircularByteBuffer.this) {
if (inputStreamClosed)
throw new IOException("InputStream has been closed; cannot read from a closed InputStream.");
int available = CircularByteBuffer.this.available();
if (available > 0) {
int length = Math.min(len, available);
int firstLen = Math.min(length, buffer.length - readPosition);
int secondLen = length - firstLen;
System.arraycopy(buffer, readPosition, cbuf, off, firstLen);
if (secondLen > 0) {
System.arraycopy(buffer, 0, cbuf, off + firstLen, secondLen);
readPosition = secondLen;
} else {
readPosition += length;
}
if (readPosition == buffer.length) {
readPosition = 0;
}
ensureMark();
return length;
} else if (outputStreamClosed) {
return -1;
}
}
try {
Thread.sleep(100);
} catch (Exception x) {
throw new IOException("Blocking read operation interrupted.");
}
}
}
/**
* Reset the stream. If the stream has been marked, then attempt to
* reposition i at the mark. If the stream has not been marked, or more
* bytes than the readAheadLimit have been read, this method has no
* effect.
*
* @throws IOException
* if the stream is closed.
*
* @since ostermillerutils 1.00.00
*/
@Override
public void reset() throws IOException
{
synchronized (CircularByteBuffer.this) {
if (inputStreamClosed)
throw new IOException("InputStream has been closed; cannot reset a closed InputStream.");
readPosition = markPosition;
}
}
/**
* Skip bytes. This method will block until some bytes are available, an
* I/O error occurs, or the end of the stream is reached.
*
* @param n
* The number of bytes to skip
* @return The number of bytes actually skipped
* @throws IllegalArgumentException
* if n is negative.
* @throws IOException
* if the stream is closed.
*
* @since ostermillerutils 1.00.00
*/
@Override
public long skip(long n) throws IOException, IllegalArgumentException
{
while (true) {
synchronized (CircularByteBuffer.this) {
if (inputStreamClosed)
throw new IOException("InputStream has been closed; cannot skip bytes on a closed InputStream.");
int available = CircularByteBuffer.this.available();
if (available > 0) {
int length = Math.min((int) n, available);
int firstLen = Math.min(length, buffer.length - readPosition);
int secondLen = length - firstLen;
if (secondLen > 0) {
readPosition = secondLen;
} else {
readPosition += length;
}
if (readPosition == buffer.length) {
readPosition = 0;
}
ensureMark();
return length;
} else if (outputStreamClosed) {
return 0;
}
}
try {
Thread.sleep(100);
} catch (Exception x) {
throw new IOException("Blocking read operation interrupted.");
}
}
}
}
/**
* Class for writing to a circular byte buffer. If the buffer is full, the
* writes will either block until there is some space available or throw an
* IOException based on the CircularByteBuffer's preference.
*
* @since ostermillerutils 1.00.00
*/
protected class CircularByteBufferOutputStream extends OutputStream
{
/**
* Close the stream, flushing it first. This will cause the InputStream
* associated with this circular buffer to read its last bytes once it
* empties the buffer. Once a stream has been closed, further write() or
* flush() invocations will cause an IOException to be thrown. Closing a
* previously-closed stream, however, has no effect.
*
* @throws IOException
* never.
*
* @since ostermillerutils 1.00.00
*/
@Override
public void close() throws IOException
{
synchronized (CircularByteBuffer.this) {
if (!outputStreamClosed) {
flush();
}
outputStreamClosed = true;
}
}
/**
* Flush the stream.
*
* @throws IOException
* if the stream is closed.
*
* @since ostermillerutils 1.00.00
*/
@Override
public void flush() throws IOException
{
synchronized (CircularByteBuffer.this) {
if (outputStreamClosed)
throw new IOException("OutputStream has been closed; cannot flush a closed OutputStream.");
if (inputStreamClosed)
throw new IOException("Buffer closed by inputStream; cannot flush.");
}
// this method needs to do nothing
}
/**
* Write an array of bytes. If the buffer allows blocking writes, this
* method will block until all the data has been written rather than throw
* an IOException.
*
* @param cbuf
* Array of bytes to be written
* @throws BufferOverflowException
* if buffer does not allow blocking writes and the buffer is
* full. If the exception is thrown, no data will have been
* written since the buffer was set to be non-blocking.
* @throws IOException
* if the stream is closed, or the write is interrupted.
*
* @since ostermillerutils 1.00.00
*/
@Override
public void write(byte[] cbuf) throws IOException
{
write(cbuf, 0, cbuf.length);
}
/**
* Write a portion of an array of bytes. If the buffer allows blocking
* writes, this method will block until all the data has been written
* rather than throw an IOException.
*
* @param cbuf
* Array of bytes
* @param off
* Offset from which to start writing bytes
* @param len
* - Number of bytes to write
* @throws BufferOverflowException
* if buffer does not allow blocking writes and the buffer is
* full. If the exception is thrown, no data will have been
* written since the buffer was set to be non-blocking.
* @throws IOException
* if the stream is closed, or the write is interrupted.
*
* @since ostermillerutils 1.00.00
*/
@Override
public void write(byte[] cbuf, int off, int len) throws IOException
{
while (len > 0) {
synchronized (CircularByteBuffer.this) {
if (outputStreamClosed)
throw new IOException("OutputStream has been closed; cannot write to a closed OutputStream.");
if (inputStreamClosed)
throw new IOException("Buffer closed by InputStream; cannot write to a closed buffer.");
int spaceLeft = spaceLeft();
while (infinite && spaceLeft < len) {
resize();
spaceLeft = spaceLeft();
}
if (!blockingWrite && spaceLeft < len)
throw new IOException("CircularByteBuffer is full; cannot write " + len + " bytes");
int realLen = Math.min(len, spaceLeft);
int firstLen = Math.min(realLen, buffer.length - writePosition);
int secondLen = Math.min(realLen - firstLen, buffer.length - markPosition - 1);
int written = firstLen + secondLen;
if (firstLen > 0) {
System.arraycopy(cbuf, off, buffer, writePosition, firstLen);
}
if (secondLen > 0) {
System.arraycopy(cbuf, off + firstLen, buffer, 0, secondLen);
writePosition = secondLen;
} else {
writePosition += written;
}
if (writePosition == buffer.length) {
writePosition = 0;
}
off += written;
len -= written;
}
if (len > 0) {
try {
Thread.sleep(100);
} catch (Exception x) {
throw new IOException("Waiting for available space in buffer interrupted.");
}
}
}
}
/**
* Write a single byte. The byte to be written is contained in the 8
* low-order bits of the given integer value; the 24 high-order bits are
* ignored. If the buffer allows blocking writes, this method will block
* until all the data has been written rather than throw an IOException.
*
* @param c
* number of bytes to be written
* @throws BufferOverflowException
* if buffer does not allow blocking writes and the buffer is
* full.
* @throws IOException
* if the stream is closed, or the write is interrupted.
*
* @since ostermillerutils 1.00.00
*/
@Override
public void write(int c) throws IOException
{
boolean written = false;
while (!written) {
synchronized (CircularByteBuffer.this) {
if (outputStreamClosed)
throw new IOException("OutputStream has been closed; cannot write to a closed OutputStream.");
if (inputStreamClosed)
throw new IOException("Buffer closed by InputStream; cannot write to a closed buffer.");
int spaceLeft = spaceLeft();
while (infinite && spaceLeft < 1) {
resize();
spaceLeft = spaceLeft();
}
if (!blockingWrite && spaceLeft < 1)
throw new IOException("CircularByteBuffer is full; cannot write 1 byte");
if (spaceLeft > 0) {
buffer[writePosition] = (byte) (c & 0xff);
writePosition++;
if (writePosition == buffer.length) {
writePosition = 0;
}
written = true;
}
}
if (!written) {
try {
Thread.sleep(100);
} catch (Exception x) {
throw new IOException("Waiting for available space in buffer interrupted.");
}
}
}
}
}
/**
* The default size for a circular byte buffer.
*
* @since ostermillerutils 1.00.00
*/
private final static int DEFAULT_SIZE = 1024;
/**
* A buffer that will grow as things are added.
*
* @since ostermillerutils 1.00.00
*/
public final static int INFINITE_SIZE = -1;
/**
* The circular buffer.
* <p>
* The actual capacity of the buffer is one less than the actual length of
* the buffer so that an empty and a full buffer can be distinguished. An
* empty buffer will have the markPostion and the writePosition equal to each
* other. A full buffer will have the writePosition one less than the
* markPostion.
* <p>
* There are three important indexes into the buffer: The readPosition, the
* writePosition, and the markPosition. If the InputStream has never been
* marked, the readPosition and the markPosition should always be the same.
* The bytes available to be read go from the readPosition to the
* writePosition, wrapping around the end of the buffer. The space available
* for writing goes from the write position to one less than the
* markPosition, wrapping around the end of the buffer. The bytes that have
* been saved to support a reset() of the InputStream go from markPosition to
* readPosition, wrapping around the end of the buffer.
*
* @since ostermillerutils 1.00.00
*/
protected byte[] buffer;
/**
* Index of the first byte available to be read.
*
* @since ostermillerutils 1.00.00
*/
protected volatile int readPosition = 0;
/**
* Index of the first byte available to be written.
*
* @since ostermillerutils 1.00.00
*/
protected volatile int writePosition = 0;
/**
* Index of the first saved byte. (To support stream marking.)
*
* @since ostermillerutils 1.00.00
*/
protected volatile int markPosition = 0;
/**
* Number of bytes that have to be saved to support mark() and reset() on the
* InputStream.
*
* @since ostermillerutils 1.00.00
*/
protected volatile int markSize = 0;
/**
* If this buffer is infinite (should resize itself when full)
*
* @since ostermillerutils 1.00.00
*/
protected volatile boolean infinite = false;
/**
* True if a write to a full buffer should block until the buffer has room,
* false if the write method should throw an IOException
*
* @since ostermillerutils 1.00.00
*/
protected boolean blockingWrite = true;
/**
* The InputStream that can empty this buffer.
*
* @since ostermillerutils 1.00.00
*/
protected InputStream in = new CircularByteBufferInputStream();
/**
* true if the close() method has been called on the InputStream
*
* @since ostermillerutils 1.00.00
*/
protected boolean inputStreamClosed = false;
/**
* The OutputStream that can fill this buffer.
*
* @since ostermillerutils 1.00.00
*/
protected OutputStream out = new CircularByteBufferOutputStream();
/**
* true if the close() method has been called on the OutputStream
*
* @since ostermillerutils 1.00.00
*/
protected boolean outputStreamClosed = false;
/**
* Create a new buffer with a default capacity. Writing to a full buffer will
* block until space is available rather than throw an exception.
*
* @since ostermillerutils 1.00.00
*/
public CircularByteBuffer()
{
this(DEFAULT_SIZE, true);
}
/**
* Create a new buffer with a default capacity and given blocking behavior.
*
* @param blockingWrite
* true writing to a full buffer should block until space is
* available, false if an exception should be thrown instead.
*
* @since ostermillerutils 1.00.00
*/
public CircularByteBuffer(boolean blockingWrite)
{
this(DEFAULT_SIZE, blockingWrite);
}
/**
* Create a new buffer with given capacity. Writing to a full buffer will
* block until space is available rather than throw an exception.
* <p>
* Note that the buffer may reserve some bytes for special purposes and
* capacity number of bytes may not be able to be written to the buffer.
* <p>
* Note that if the buffer is of INFINITE_SIZE it will neither block or throw
* exceptions, but rather grow without bound.
*
* @param size
* desired capacity of the buffer in bytes or
* CircularByteBuffer.INFINITE_SIZE.
*
* @since ostermillerutils 1.00.00
*/
public CircularByteBuffer(int size)
{
this(size, true);
}
/**
* Create a new buffer with the given capacity and blocking behavior.
* <p>
* Note that the buffer may reserve some bytes for special purposes and
* capacity number of bytes may not be able to be written to the buffer.
* <p>
* Note that if the buffer is of INFINITE_SIZE it will neither block or throw
* exceptions, but rather grow without bound.
*
* @param size
* desired capacity of the buffer in bytes or
* CircularByteBuffer.INFINITE_SIZE.
* @param blockingWrite
* true writing to a full buffer should block until space is
* available, false if an exception should be thrown instead.
*
* @since ostermillerutils 1.00.00
*/
public CircularByteBuffer(int size, boolean blockingWrite)
{
if (size == INFINITE_SIZE) {
buffer = new byte[DEFAULT_SIZE];
infinite = true;
} else {
buffer = new byte[size];
infinite = false;
}
this.blockingWrite = blockingWrite;
}
/**
* Make this buffer ready for reuse. The contents of the buffer will be
* cleared and the streams associated with this buffer will be reopened if
* they had been closed.
*
* @since ostermillerutils 1.00.00
*/
public void clear()
{
synchronized (this) {
readPosition = 0;
writePosition = 0;
markPosition = 0;
outputStreamClosed = false;
inputStreamClosed = false;
}
}
/**
* Get number of bytes that are available to be read.
* <p>
* Note that the number of bytes available plus the number of bytes free may
* not add up to the capacity of this buffer, as the buffer may reserve some
* space for other purposes.
*
* @return the size in bytes of this buffer
*
* @since ostermillerutils 1.00.00
*/
public int getAvailable()
{
synchronized (this) {
return available();
}
}
/**
* Retrieve a InputStream that can be used to empty this buffer.
* <p>
* This InputStream supports marks at the expense of the buffer size.
*
* @return the consumer for this buffer.
*
* @since ostermillerutils 1.00.00
*/
public InputStream getInputStream()
{
return in;
}
/**
* Retrieve a OutputStream that can be used to fill this buffer.
* <p>
* Write methods may throw a BufferOverflowException if the buffer is not
* large enough. A large enough buffer size must be chosen so that this does
* not happen or the caller must be prepared to catch the exception and try
* again once part of the buffer has been consumed.
*
*
* @return the producer for this buffer.
*
* @since ostermillerutils 1.00.00
*/
public OutputStream getOutputStream()
{
return out;
}
/**
* Get the capacity of this buffer.
* <p>
* Note that the number of bytes available plus the number of bytes free may
* not add up to the capacity of this buffer, as the buffer may reserve some
* space for other purposes.
*
* @return the size in bytes of this buffer
*
* @since ostermillerutils 1.00.00
*/
public int getSize()
{
synchronized (this) {
return buffer.length;
}
}
/**
* Get the number of bytes this buffer has free for writing.
* <p>
* Note that the number of bytes available plus the number of bytes free may
* not add up to the capacity of this buffer, as the buffer may reserve some
* space for other purposes.
*
* @return the available space in bytes of this buffer
*
* @since ostermillerutils 1.00.00
*/
public int getSpaceLeft()
{
synchronized (this) {
return spaceLeft();
}
}
/**
* Bytes available for reading.
*
* @since ostermillerutils 1.00.00
*/
private int available()
{
if (readPosition <= writePosition) {
// any space between the first read and
// the first write is available. In this case i
// is all in one piece.
return (writePosition - readPosition);
}
// space at the beginning and end.
return (buffer.length - (readPosition - writePosition));
}
/**
* If we have passed the markSize reset the mark so that the space can be
* used.
*
* @since ostermillerutils 1.00.00
*/
private void ensureMark()
{
if (marked() > markSize) {
markPosition = readPosition;
markSize = 0;
}
}
/**
* Bytes saved for supporting marks.
*
* @since ostermillerutils 1.00.00
*/
private int marked()
{
if (markPosition <= readPosition) {
// any space between the markPosition and
// the first write is marked. In this case i
// is all in one piece.
return (readPosition - markPosition);
}
// space at the beginning and end.
return (buffer.length - (markPosition - readPosition));
}
/**
* double the size of the buffer
*
* @since ostermillerutils 1.00.00
*/
private void resize()
{
byte[] newBuffer = new byte[buffer.length * 2];
int marked = marked();
int available = available();
if (markPosition <= writePosition) {
// any space between the mark and
// the first write needs to be saved.
// In this case it is all in one piece.
int length = writePosition - markPosition;
System.arraycopy(buffer, markPosition, newBuffer, 0, length);
} else {
int length1 = buffer.length - markPosition;
System.arraycopy(buffer, markPosition, newBuffer, 0, length1);
int length2 = writePosition;
System.arraycopy(buffer, 0, newBuffer, length1, length2);
}
buffer = newBuffer;
markPosition = 0;
readPosition = marked;
writePosition = marked + available;
}
/**
* Space available in the buffer which can be written.
*
* @since ostermillerutils 1.00.00
*/
private int spaceLeft()
{
if (writePosition < markPosition) {
// any space between the first write and
// the mark except one byte is available.
// In this case it is all in one piece.
return (markPosition - writePosition - 1);
}
// space at the beginning and end.
return ((buffer.length - 1) - (writePosition - markPosition));
}
}