/* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* <p/>
* This library 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
* Lesser General Public License for more details.
*/
package org.rzo.yajsw.io;
import java.io.IOException;
import java.io.Reader;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// TODO: Auto-generated Javadoc
/**
* A Synchronized circular byte buffer. This buffer orders elements FIFO
* (first-in-first-out). Writers block if the buffer is full. Attempts to
* retrieve an element from an empty buffer will block.
*/
public class CircularBuffer extends Reader
{
/** Default buffer size. */
public final static int DEFAULT_BUFFER_SIZE = 512;
/** The synchronization lock. */
private Lock lock = new ReentrantLock();
/** Sync condition indicating that buffer is not empty. */
private Condition notEmpty = lock.newCondition();
/** The not full. */
private Condition notFull = lock.newCondition();
/** The buffer. */
private byte[] buffer;
/** The number of valid elements in the buffe. */
private int size = 0;
/** The index to put next value. */
private int putIndex = 0;
/** The index to get next value. */
private int getIndex = 0;
/** The blocking. */
boolean blocking = true;
byte[] fullIndicator;
boolean fullIndocatorWritten = false;
boolean writeBlocking;
int fullIndicatorIndex = -1;
/**
* Instantiates a new circular buffer.
*
* @param bufferSize
* the buffer size
* @param blocking
* the blocking
*/
public CircularBuffer(int bufferSize, boolean blocking)
{
buffer = new byte[bufferSize];
this.blocking = blocking;
writeBlocking = blocking;
}
public void setWriteBlocking(boolean blocking)
{
writeBlocking = blocking;
}
/**
* Instantiates a new circular buffer with default size.
*/
public CircularBuffer()
{
buffer = new byte[DEFAULT_BUFFER_SIZE];
}
/**
* Put a value into the buffer. If buffer is full older values are
* overwritten.
*
* @param value
* the value
*/
public void put(byte value)
{
lock.lock(); // lock this object
// while no empty locations, place thread in waiting state
try
{
while (size == buffer.length)
{
if (writeBlocking)
{
// System.out.println("wait write");
notFull.await();// await until a buffer element is free
}
else
{
lock.unlock();
return;
}
} // end while
}
catch (Exception ex)
{
ex.printStackTrace();
}
buffer[putIndex] = value; // set new buffer value
putByte(value);
notEmpty.signal(); // signal threads waiting to read from buffer
lock.unlock(); // unlock this object
} // end method put
private void writeFullIndicator()
{
fullIndocatorWritten = true;
}
/**
* Put the values of a byte array into the buffer. In case of overflow put
* blocks
*
* @param buf
* the buf
* @param off
* the off
* @param len
* the len
*/
public void put(byte[] buf, int off, int len)
{
lock.lock(); // lock this object
for (int i = off; i < off + len - 1; i++)
putByte(buf[i]);
notEmpty.signal(); // signal threads waiting to read from buffer
lock.unlock(); // unlock this object
}
/**
* Put a single byte into the buffer.
*
* @param value
* the value
*/
private void putByte(byte value)
{
buffer[putIndex] = value; // set new buffer value
// update circular write index
putIndex++;
if (putIndex >= buffer.length)
putIndex = putIndex - buffer.length;
size++; // one more buffer element is full
// if buffer overflow
if (size > buffer.length)
{
getIndex++;
if (getIndex >= buffer.length)
getIndex = getIndex - buffer.length;
size = buffer.length;
// System.out.println("overflow");
if (fullIndicator != null && !fullIndocatorWritten)
writeFullIndicator();
}
}
/**
* Get next value from the buffer. Blocks indefinitely if buffer is empty.
*
* @return the byte
*/
public byte get()
{
byte result = 0; // initialize value read from buffer
lock.lock(); // lock this object
// wait until buffer has data, then read value
try
{
// while no data to read, place thread in waiting state
while (size == 0)
{
if (blocking)
notEmpty.await(); // await until a buffer element is
// filled
else
{
lock.unlock();
return 0;
}
} // end while
result = getByte();
notFull.signal();
} // end try
// if waiting thread interrupted, print stack trace
catch (InterruptedException exception)
{
exception.printStackTrace();
Thread.currentThread().interrupt();
} // end catch
finally
{
lock.unlock(); // unlock this object
} // end finally
return result;
} // end method get
/**
* Get bytes from the buffer and return these in an array. Blocks if the
* buffer is empty and no values have yet been added to the array.
*
* @param buf
* array to return bytes
* @param off
* the off
* @param len
* the len
*
* @return the number of bytes returned in the array
*/
public int get(byte[] buf, int off, int len)
{
lock.lock(); // lock this object
int i = 0;
try
{
// while no data to read, place thread in waiting state
while (size == 0)
{
// System.out.println("read wait");
notEmpty.await(); // await until a buffer element is
// filled
} // end while
} // end try
// if waiting thread interrupted, print stack trace
catch (Exception exception)
{
exception.printStackTrace();
} // end catch
for (; i < len && i < size; i++)
{
buf[off + i] = getByte();
}
notFull.signal();
lock.unlock(); // unlock this object
return i;
}
/**
* Gets the byte.
*
* @return the byte
*/
private byte getByte()
{
if (fullIndocatorWritten)
{
fullIndicatorIndex++;
if (fullIndicatorIndex < fullIndicator.length)
{
return fullIndicator[fullIndicatorIndex];
}
else
{
fullIndicatorIndex = -1;
fullIndocatorWritten = false;
}
}
if (size == 0)
return 0;
byte result;
result = buffer[getIndex]; // read value from buffer
// update circular read index
getIndex++;
if (getIndex >= buffer.length)
getIndex = getIndex - buffer.length;
size--; // one more buffer element is empty
return result;
}
/**
* Size.
*
* @return the int
*/
public int size()
{
return size;
}
/*
* (non-Javadoc)
*
* @see java.io.Reader#close()
*/
public void close()
{
lock.lock();
notFull.signal();
notEmpty.signal();
size = 0;
putIndex = 0;
getIndex = 0;
lock.unlock();
}
/**
* The main method.
*
* @param args
* the arguments
*/
public static void main(String[] args)
{
CircularBuffer b = new CircularBuffer(2, true);
b.put((byte) 1);
b.put((byte) 2);
b.put((byte) 3);
System.out.println(b.get());
System.out.println(b.get());
b.put(new byte[]
{ 1, 2, 3, 4 }, 0, 4);
System.out.println(b.get(new byte[10], 0, 10));
System.out.println(b.get(new byte[10], 0, 10));
}
/*
* (non-Javadoc)
*
* @see java.io.Reader#read(char[], int, int)
*/
@Override
public int read(char[] cbuf, int off, int len) throws IOException
{
lock.lock(); // lock this object
int i = 0;
try
{
// while no data to read, place thread in waiting state
while (size == 0)
{
if (blocking)
notEmpty.await(); // await until a buffer element is
// filled
else
{
lock.unlock();
return -1;
}
} // end while
} // end try
// if waiting thread interrupted, print stack trace
catch (Exception exception)
{
exception.printStackTrace();
} // end catch
for (; i < len && i < size; i++)
{
cbuf[off + i] = (char) getByte();
}
notFull.signal();
lock.unlock(); // unlock this object
return i;
}
/**
* Write.
*
* @param cbuf
* the cbuf
* @param off
* the off
* @param len
* the len
*/
public void write(char[] cbuf, int off, int len)
{
lock.lock(); // lock this object
for (int i = off; i < off + len - 1; i++)
putByte((byte) cbuf[i]);
notEmpty.signal(); // signal threads waiting to read from buffer
lock.unlock(); // unlock this object
}
/**
* Write.
*
* @param str
* the str
*/
public void write(String str)
{
char[] dst = new char[str.length() + 2];
str.getChars(0, str.length(), dst, 0);
dst[str.length()] = '\r';
dst[str.length()] = '\n';
write(dst, 0, dst.length);
}
public void setFullIndicator(String text)
{
fullIndicator = text.getBytes();
}
} // end class CircularBuffer