package org.limewire.nio; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import org.limewire.nio.channel.InterestWritableByteChannel; import org.limewire.nio.observer.Shutdownable; import org.limewire.nio.observer.WriteObserver; /** * Manages writing data to the network from a piped blocking OutputStream. * * This uses a BufferOutputStream 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 written. */ class NIOOutputStream implements WriteObserver { private final Shutdownable handler; private final InterestWritableByteChannel channel; private BufferOutputStream sink; private volatile Object bufferLock; private ByteBuffer buffer; private boolean shutdown; /** * Constructs a new pipe to allow SocketChannel's reading to funnel * to a blocking InputStream. */ NIOOutputStream(Shutdownable handler, InterestWritableByteChannel channel) { this.handler = handler; this.channel = channel; } /** * Creates the pipes, buffer & registers channels for interest. */ private synchronized NIOOutputStream init() throws IOException { if(buffer != null) throw new IllegalStateException("already init'd!"); if(shutdown) throw new IOException("already closed!"); this.buffer = NIODispatcher.instance().getBufferCache().getHeap(); sink = new BufferOutputStream(buffer, handler, this, channel); bufferLock = sink.getBufferLock(); return this; } /** * Retrieves the OutputStream to write to. */ synchronized OutputStream getOutputStream() throws IOException { if(buffer == null) init(); return sink; } /** * Notification that a write can happen on the SocketChannel. */ public boolean handleWrite() throws IOException {// write everything we can. if(buffer == null) return false; synchronized(bufferLock) { buffer.flip(); while(buffer.hasRemaining() && channel.write(buffer) > 0); if (buffer.position() > 0) { // > 0 if it could write something if (buffer.hasRemaining()) buffer.compact(); else buffer.clear(); } else // == 0 if it couldn't write anything buffer.position(buffer.limit()).limit(buffer.capacity()); // If there's room in the buffer, we're interested in writing. if(buffer.hasRemaining()) bufferLock.notify(); // if we were able to write everything, we're not interested in more writing. // otherwise, we are interested. if(buffer.position() == 0) { channel.interestWrite(this, false); return false; } else { return true; } } } /** * Shuts down all internal channels. * The SocketChannel should be shut by NIOSocket. */ public synchronized void shutdown() { if(shutdown) return; shutdown = true; if(sink != null) sink.shutdown(); if(buffer != null) { NIODispatcher.instance().getScheduledExecutorService().execute(new Runnable() { public void run() { NIODispatcher.instance().getBufferCache().release(buffer); } }); } } /** Unused */ public void handleIOException(IOException iox) { throw new RuntimeException("unsupported operation", iox); } }