package org.limewire.nio; import java.io.IOException; import java.io.InputStream; import java.net.SocketException; import java.nio.ByteBuffer; import org.limewire.nio.channel.InterestReadableByteChannel; import org.limewire.nio.observer.Shutdownable; import org.limewire.nio.timeout.ReadTimeout; /** * 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 shutdownable to shutdown. */ private final Shutdownable shutdownHandler; /** The ReadTimeout handler. */ private final ReadTimeout readTimeoutHandler; /** the buffer that has data for reading */ private final ByteBuffer buffer; /** The NIOInputStream to notify when we're about to read. */ private final NIOInputStream nioStream; /** the SelectableChannel that the buffer is read from. */ private InterestReadableByteChannel 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, ReadTimeout timeout, Shutdownable shutdown, InterestReadableByteChannel channel, NIOInputStream stream) { this.readTimeoutHandler = timeout; this.shutdownHandler = shutdown; this.buffer = buffer; this.channel = channel; this.nioStream = stream; } void setReadChannel(InterestReadableByteChannel 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. */ @Override 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.interestRead(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 */ @Override 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.interestRead(true); return available; // the amount we read. } } /** Determines how much data can be read without blocking */ @Override public int available() throws IOException { synchronized(LOCK) { return buffer.position(); } } /** Waits the soTimeout amount of time. */ private void waitImpl() throws IOException { long timeout = readTimeoutHandler.getReadTimeout(); if(timeout == -1) throw new SocketException("unable to get read timeout"); boolean looped = false; while(buffer.position() == 0 && !finished) { if(shutdown) throw new IOException("socket closed"); if(looped && timeout != 0) throw new java.net.SocketTimeoutException("read timed out (" + timeout + ")"); nioStream.readHappening(); 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 */ @Override public void close() throws IOException { shutdownHandler.shutdown(); } /** Shuts down this socket */ public void shutdown() { synchronized(LOCK) { shutdown = true; LOCK.notify(); } } }