package com.limegroup.gnutella.io;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;
/**
* An InputStream that attempts to read from a Buffer.
*
* The stream must be notified when data is available in the buffer
* to be read.
*/
class BufferInputStream extends InputStream implements Shutdownable {
private static final Log LOG = LogFactory.getLog(BufferInputStream.class);
/** the lock that reading waits on. */
private final Object LOCK = new Object();
/** the socket to get soTimeouts for waiting & shutdown on close */
private final NIOSocket handler;
/** the buffer that has data for reading */
private final ByteBuffer buffer;
/** the SelectableChannel that the buffer is read from. */
private InterestReadChannel channel;
/** whether or not this stream has been shutdown. */
private boolean shutdown = false;
/** whether or not there's no data left to read on this stream. */
private boolean finished = false;
/**
* Constructs a new BufferInputStream that reads from the given buffer,
* using the given socket to retrieve the soTimeouts.
*/
BufferInputStream(ByteBuffer buffer, NIOSocket handler, InterestReadChannel channel) {
this.handler = handler;
this.buffer = buffer;
this.channel = channel;
}
void setReadChannel(InterestReadChannel newChannel) {
synchronized(LOCK) {
this.channel = newChannel;
}
}
/** Returns the lock object upon which writing into the buffer should lock */
Object getBufferLock() {
return LOCK;
}
/** Marks this stream as finished -- having no data left to read. */
void finished() {
finished = true;
}
/** Reads a single byte from the buffer. */
public int read() throws IOException {
synchronized(LOCK) {
waitImpl();
if(finished && buffer.position() == 0)
return -1;
buffer.flip();
byte read = buffer.get();
buffer.compact();
// there's room in the buffer now, the channel needs some data.
channel.interest(true);
// must &, otherwise implicit cast can change value.
// (for example, reading the byte -1 is very different than
// reading the int -1, which means EOF.)
return read & 0xFF;
}
}
/** Reads a chunk of data from the buffer */
public int read(byte[] buf, int off, int len) throws IOException {
if (len == 0)
return 0;
synchronized(LOCK) {
waitImpl();
if(finished && buffer.position() == 0)
return -1;
buffer.flip();
int available = Math.min(buffer.remaining(), len);
buffer.get(buf, off, available);
if (buffer.hasRemaining())
buffer.compact();
else
buffer.clear();
// now that there's room in the buffer, fill up the channel
channel.interest(true);
return available; // the amount we read.
}
}
/** Determines how much data can be read without blocking */
public int available() throws IOException {
synchronized(LOCK) {
return buffer.position();
}
}
/** Waits the soTimeout amount of time. */
private void waitImpl() throws IOException {
int timeout = handler.getSoTimeout();
boolean looped = false;
while(buffer.position() == 0 && !finished) {
if(shutdown)
throw new IOException("socket closed");
if(looped && timeout != 0)
throw new java.io.InterruptedIOException("read timed out (" + timeout + ")");
try {
LOCK.wait(timeout);
} catch(InterruptedException ix) {
throw new InterruptedIOException(ix);
}
looped = true;
}
if(shutdown)
throw new IOException("socket closed");
}
/** Closes this InputStream & the Socket that it's associated with */
public void close() throws IOException {
NIODispatcher.instance().shutdown(handler);
}
/** Shuts down this socket */
public void shutdown() {
synchronized(LOCK) {
shutdown = true;
LOCK.notify();
}
}
}