package multimonster.common.pipe;
import org.apache.log4j.Logger;
/**
* A Channel to transfer data which uses a RingBuffer.
*
* @author J�rg Meier
*/
public class BufferedChannel extends Channel {
// TODO if reader and writer both are waiting -> Deadlock!! - This can
// happen if there is not enough to read and not enough space to write
/* Ideas for later enhancements
*
private int percentageFilled;
private int highLevelLimit;
private int lowLevelLimit;
private int channelCapacity;
*/
/** the Pipe this Channel belongs to */
private Pipe myPipe;
/**
* the default BufferSize for a BufferedChannel
*/
private static int defaultBufferSize = 10 * 128 * 1024;
private RingBuffer buffer;
private int bufferSize;
private boolean isClosed;
private boolean readerIsNotified = false;
private boolean writerIsNotified = false;
private static int TIMEOUT = 1000 * 60; //60s
private Logger log;
/**
* Constructs a Channel with default BufferSize.
*
*/
public BufferedChannel(Pipe p){
this(p, defaultBufferSize);
}
/**
* Constructs a Channel with the given BufferSize.
*
* @param bufferSize
*/
public BufferedChannel(Pipe p, int bufferSize){
log = Logger.getLogger(this.getClass());
this.myPipe = p;
this.bufferSize = bufferSize;
this.buffer = new RingBuffer(this.bufferSize);
this.isClosed = false;
}
public boolean isClosed() {
return isClosed;
}
public synchronized void close() {
//log.debug("close()");
this.isClosed = true;
readerIsNotified = true;
writerIsNotified = true;
this.notifyAll();
}
public synchronized byte[] read(int length) throws ChannelClosedException {
byte[] buf = new byte[length];
byte[] result = null;
int nRead = 0;
if (length > bufferSize) {
throw new IllegalArgumentException(
"length > bufferSize; bufferSize = " + bufferSize);
}
while (true) {
if (isClosed && buffer.isEmpty()) {
// channel is closed and has nothing to read
throw new ChannelClosedException();
} else if ((length > buffer.length()) && !isClosed) {
// channel is open and has not enough to read
try {
//log.debug("sleep - read");
readerIsNotified = false;
this.wait(TIMEOUT);
if (!readerIsNotified) {
// a real timeout occured, so closing channel
log.warn("channel-Timeout");
close();
}
//log.debug("wake - read");
} catch (InterruptedException e) {
log.error(e);
}
} else
break;
}
nRead = buffer.get(buf, length);
if (nRead == length) {
result = buf;
} else {
//log.debug("returning smaller buffer than ordered");
result = new byte[nRead];
for (int i = 0; i < result.length; i++)
result[i] = buf[i];
}
writerIsNotified = true;
this.notifyAll();
return result;
}
public synchronized void write(byte[] in, int len)throws ChannelClosedException {
if (len > in.length){
throw new IllegalArgumentException(
"len > in.length");
}
if (len > bufferSize) {
throw new IllegalArgumentException(
"len > bufferSize; bufferSize = " + bufferSize);
}
// wait until Buffer is Free
while (true) {
if (isClosed) {
throw new ChannelClosedException();
}
if (buffer.available() < len) {
try {
//log.debug("sleep - write");
writerIsNotified = false;
this.wait(TIMEOUT);
if (!writerIsNotified) {
// a real timeout occured, so closing channel
log.warn("channel-Timeout");
close();
}
//log.debug("wake - write");
} catch (InterruptedException e1) {
log.error(e1);
}
} else
break;
}
try {
buffer.add(in, len);
} catch (Exception e) {
// this should never be reached
log.fatal("Buffer threw exception - this should never happen!");
}
readerIsNotified = true;
this.notifyAll();
}
public synchronized void write(byte[] in) throws ChannelClosedException {
if (in.length > bufferSize) {
throw new IllegalArgumentException(
"in.length > bufferSize; bufferSize = " + bufferSize);
}
write(in, in.length);
}
}