package com.limegroup.gnutella.io;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
/**
* Manages reading data from the network & piping it to a blocking input stream.
*
* This uses a BufferInputStream that waits on a lock when no data is available.
* The stream exposes a BufferLock that should be notified when data is available
* to be read.
*/
class NIOInputStream {
private final NIOSocket handler;
private final SocketChannel channel;
private BufferInputStream source;
private Object bufferLock;
private ByteBuffer buffer;
private boolean shutdown;
/**
* Constructs a new pipe to allow SocketChannel's reading to funnel
* to a blocking InputStream.
*/
NIOInputStream(NIOSocket handler, SocketChannel channel) throws IOException {
this.handler = handler;
this.channel = channel;
}
/**
* Creates the pipes, buffer, and registers channels for interest.
*/
synchronized void init() throws IOException {
if(buffer != null)
throw new IllegalStateException("already init'd!");
if(shutdown)
throw new IOException("Already closed!");
buffer = ByteBuffer.allocate(8192); // TODO: use a ByteBufferPool
source = new BufferInputStream(buffer, handler, channel);
bufferLock = source.getBufferLock();
NIODispatcher.instance().interestRead(channel, true);
}
/**
* Retrieves the InputStream to read from.
*/
synchronized InputStream getInputStream() throws IOException {
if(buffer == null)
init();
return source;
}
/**
* Notification that a read can happen on the SocketChannel.
*/
void readChannel() throws IOException {
synchronized(bufferLock) {
int read = 0;
// read everything we can.
while(buffer.hasRemaining() && (read = channel.read(buffer)) > 0);
if(read == -1)
throw new IOException("channel closed.");
// If there's data in the buffer, we're interested in writing.
if(buffer.position() > 0)
bufferLock.notify();
// if there's room in the buffer, we're interested in more reading ...
// if not, we're not interested in more reading.
if(!buffer.hasRemaining())
NIODispatcher.instance().interestRead(channel, false);
}
}
/**
* Notification that an IOException has occurred on one of these channels.
*/
public void handleIOException(IOException iox) {
// Inform the NIOSocket that things are dead. That will shut us down.
handler.shutdown();
}
/**
* Shuts down all internal channels.
* The SocketChannel should be shut by NIOSocket.
*/
synchronized void shutdown() {
if(shutdown)
return;
if(source != null)
source.shutdown();
shutdown = true;
}
}