package org.limewire.nio;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import org.limewire.service.ErrorService;
/**
* Allows to read and write to/from channels and other buffers
* with virtually no memory overhead.
*/
public class CircularByteBuffer {
private static final DevNull DEV_NULL = new DevNull();
private final ByteBufferCache cache;
private final int capacity;
private ByteBuffer in, out;
private ByteOrder order = ByteOrder.BIG_ENDIAN;
private boolean lastOut = true;
public CircularByteBuffer(int capacity, ByteBufferCache cache) {
this.cache = cache;
this.capacity = capacity;
}
private void initBuffers() {
if (in == null) {
assert out == null;
in = cache.getHeap(capacity);
out = in.asReadOnlyBuffer();
} else
assert out != null;
}
public final int remainingIn() {
if (in == null)
return capacity;
int i = in.position();
int o = out.position();
if (i > o)
return in.capacity() - i + o;
else if (i < o)
return o - i;
else
return lastOut ? in.capacity() : 0;
}
public final int remainingOut() {
return capacity() - remainingIn();
}
public void put(ByteBuffer src) {
if (src.remaining() > remainingIn())
throw new BufferOverflowException();
if (src.hasRemaining())
lastOut = false;
else
return;
initBuffers();
if (src.remaining() > in.remaining()) {
int oldLimit = src.limit();
src.limit(src.position() + in.remaining());
in.put(src);
in.rewind();
src.limit(oldLimit);
}
in.put(src);
}
public void put(CircularByteBuffer src) {
if (src.remainingOut() > remainingIn())
throw new BufferOverflowException();
initBuffers();
if (in.remaining() < src.remainingOut()) {
src.out.limit(in.remaining());
in.put(src.out);
in.rewind();
src.out.limit(src.out.capacity());
}
in.put(src.out);
lastOut = false;
}
public byte get() {
if (remainingOut() < 1)
throw new BufferUnderflowException();
if (!out.hasRemaining())
out.rewind();
byte ret = out.get();
releaseIfEmpty();
lastOut = true;
return ret;
}
public void get(byte [] dest) {
get(dest,0,dest.length);
}
public void get(byte [] dest, int offset, int length) {
if (remainingOut() < length)
throw new BufferUnderflowException();
if (length > 0)
lastOut = true;
else
return;
if (out.remaining() < length) {
int remaining = out.remaining();
out.get(dest, offset, remaining);
offset+=remaining;
length-=remaining;
out.rewind();
}
out.get(dest,offset,length);
releaseIfEmpty();
}
public void get(ByteBuffer dest) {
if (remainingOut() < dest.remaining())
throw new BufferUnderflowException();
if (dest.remaining() > 0)
lastOut = true;
else
return;
if (out.remaining() < dest.remaining()) {
dest.put(out);
out.rewind();
}
out.limit(out.position() + dest.remaining());
dest.put(out);
out.limit(out.capacity());
releaseIfEmpty();
}
private void releaseIfEmpty() {
if (in != null && out != null && out.position() == in.position()) {
cache.release(in);
in = null;
out = null;
}
}
public int write(WritableByteChannel sink, int len) throws IOException {
int written = 0;
int thisTime = 0;
while (remainingOut() > 0 && written < len) {
if (!out.hasRemaining())
out.rewind();
if (in.position() > out.position()) {
if (len == Integer.MAX_VALUE)
out.limit(in.position());
else
out.limit(Math.min(in.position(), len - written + out.position()));
}
try {
thisTime = sink.write(out);
} finally {
if (thisTime > 0)
lastOut = true;
}
out.limit(out.capacity());
if (thisTime == 0)
break;
written += thisTime;
}
releaseIfEmpty();
return written;
}
public int write(WritableByteChannel sink) throws IOException {
return write(sink, Integer.MAX_VALUE);
}
/**
* Reads data from the source channel.
* @return the amount of data read, >= 0
* @throws IOException if an error occurred or
* no data was read and end of stream was reached.
*/
public int read(ReadableByteChannel source) throws IOException {
int read = 0;
int thisTime = 0;
while (remainingIn() > 0) {
initBuffers();
if (!in.hasRemaining())
in.rewind();
if (out.position() > in.position())
in.limit(out.position());
try {
thisTime = source.read(in);
} finally {
if (thisTime > 0)
lastOut = false;
}
in.limit(in.capacity());
if (thisTime == 0)
break;
if (thisTime == -1) {
if (read == 0)
throw new IOException();
return read;
}
read += thisTime;
}
return read;
}
public int size() {
return remainingOut();
}
public int capacity() {
return capacity;
}
@Override
public String toString() {
return "circular buffer in:"+in+" out:"+out;
}
public void order(ByteOrder order) {
this.order = order;
}
public int getInt() throws BufferUnderflowException {
if (remainingOut() < 4)
throw new BufferUnderflowException();
if (order == ByteOrder.BIG_ENDIAN)
return getU() << 24 | getU() << 16 | getU() << 8 | getU();
else
return getU() | getU() << 8 | getU() << 16 | getU() << 24;
}
private int getU() {
return get() & 0xFF;
}
public void discard(int num) {
if (remainingOut() < num)
throw new BufferUnderflowException();
try {
write(DEV_NULL, num);
} catch (IOException impossible) {
ErrorService.error(impossible);
}
}
private static class DevNull implements WritableByteChannel {
public int write(ByteBuffer src) throws IOException {
int ret = src.remaining();
src.position(src.limit());
return ret;
}
public void close() throws IOException {
}
public boolean isOpen() {
return true;
}
}
}