package com.limegroup.gnutella.io; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.SelectableChannel; /** * 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 { /** 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 final SelectableChannel channel; /** whether or not this stream has been shutdown. */ private boolean shutdown = false; /** * Constructs a new BufferInputStream that reads from the given buffer, * using the given socket to retrieve the soTimeouts. */ BufferInputStream(ByteBuffer buffer, NIOSocket handler, SelectableChannel channel) { this.handler = handler; this.buffer = buffer; this.channel = channel; } /** Returns the lock object upon which writing into the buffer should lock */ Object getBufferLock() { return LOCK; } /** Reads a single byte from the buffer. */ public int read() throws IOException { synchronized(LOCK) { waitImpl(); buffer.flip(); byte read = buffer.get(); buffer.compact(); // there's room in the buffer now, the channel needs some data. NIODispatcher.instance().interestRead(channel, 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 { synchronized(LOCK) { waitImpl(); buffer.flip(); int available = Math.min(buffer.remaining(), len); buffer.get(buf, off, available); buffer.compact(); // now that there's room in the buffer, fill up the channel NIODispatcher.instance().interestRead(channel, 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) { 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 { handler.shutdown(); } /** Shuts down this socket */ void shutdown() { synchronized(LOCK) { shutdown = true; LOCK.notify(); } } }