package com.limegroup.gnutella.io; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.SelectableChannel; import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.Log; /** * An OutputStream that attempts to write from a Buffer. * * The stream must be notified when data is available in the buffer * to be read. */ class BufferOutputStream extends OutputStream implements Shutdownable { private static final Log LOG = LogFactory.getLog(BufferOutputStream.class); /** the lock that reading waits on. */ private final Object LOCK = new Object(); /** the handler to get for shutdown on close */ private final NIOSocket handler; /** the buffer that has data for writing */ private final ByteBuffer buffer; /** the SelectableChannel that the buffer is written from. */ private final SelectableChannel channel; /** whether or not this stream has been shutdown. */ private boolean shutdown = false; /** * Constructs a new BufferOutputStream that writes data to the given buffer. */ BufferOutputStream(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; } /** Writes a single byte to the buffer. */ public void write(int x) throws IOException { synchronized(LOCK) { waitImpl(); buffer.put((byte)(x & 0xFF)); // there's data in the buffer now, the channel can write it. NIODispatcher.instance().interestWrite(channel, true); } } /** Writes a chunk of data to the buffer */ public void write(byte[] buf, int off, int len) throws IOException { synchronized(LOCK) { while(len > 0) { waitImpl(); int available = Math.min(buffer.remaining(), len); buffer.put(buf, off, available); off += available; len -= available; // now that there's data in the buffer, write with the channel NIODispatcher.instance().interestWrite(channel, true); } } } /** Forces all data currently in the buffer to be written to the channel. */ public void flush() throws IOException { synchronized(LOCK) { // Since that adds no data to the buffer, we do not need to interest a write. // This simply waits until the existing buffer is emptied into the TCP stack, // via whatever mechanism normally clears the buffer (via writes). while(buffer.position() > 0) { if(shutdown) throw new IOException("socket closed"); try { LOCK.wait(); } catch(InterruptedException ix) { throw new InterruptedIOException(ix); } } } } /** Waits until there is space in the buffer to write to. */ private void waitImpl() throws IOException { while(!buffer.hasRemaining()) { if(shutdown) throw new IOException("socket closed"); try { LOCK.wait(); } catch(InterruptedException ix) { throw new InterruptedIOException(ix); } } 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(); } } }