package org.limewire.nio.channel; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Channel; import org.limewire.nio.RequiresSelectionKeyAttachment; import org.limewire.nio.Throttle; import org.limewire.nio.ThrottleListener; import org.limewire.util.BufferUtils; /** * Reads data from a channel. The data is controlled by a {@link Throttle}; to * work with the <code>Throttle</code>, <code>ThrottleReader</code> uses an * attachment. */ public class ThrottleReader implements InterestReadableByteChannel, ChannelReader, RequiresSelectionKeyAttachment { //private static final Log LOG = LogFactory.getLog(ThrottleReader.class); /** The channel to write to & interest on. */ private volatile InterestReadableByteChannel channel; /** The throttle we're using. */ private final Throttle throttle; /** The amount of data we were told we can read. */ private int available; /** The last interest state, to interact well with the Throttle. */ private volatile boolean lastInterestState; private final Listener throttleListener; /** * Constructs a <code>ThrottleReader</code> with the given * <code>Throttle</code>. * <p> * <b>NOTE</b>: You must call * {@link #setReadChannel(InterestReadableByteChannel) * setReadChannel(InterestReadableByteChannel)} * prior to using <code>ThrottleReader</code>. */ public ThrottleReader(Throttle throttle) { this(throttle, null); } /** * Constructs a new <code>ThrottleReader</code> with the given throttle and * channel. */ public ThrottleReader(Throttle throttle, InterestReadableByteChannel channel) { this.throttle = throttle; this.channel = channel; this.throttleListener = new Listener(); } /** Retrieves the channel. */ public InterestReadableByteChannel getReadChannel() { return channel; } /** Sets the channel. */ public void setReadChannel(InterestReadableByteChannel channel) { this.channel = channel; throttle.interest(throttleListener); } /** * Tells the <code>Throttle</code> that we're interested in receiving * bandwidthAvailable events at some point in time. */ public void interestRead(boolean status) { lastInterestState = status; if(channel != null) { if(status) throttle.interest(throttleListener); else channel.interestRead(false); } } /** * Read data from the chain. * <p> * Only reads up to 'available' amount of data. */ public int read(ByteBuffer buffer) throws IOException { InterestReadableByteChannel chain = channel; if(chain == null) throw new IllegalStateException("reading with no chain!"); int totalRead = 0; if (available > 0) { int priorLimit = buffer.limit(); if (buffer.remaining() > available) { // LOG.debug("Limting amount remaining to read from " + // buffer.remaining() + " to " + available); buffer.limit(buffer.position() + available); } try { totalRead = channel.read(buffer); } finally { buffer.limit(priorLimit); } if (totalRead > 0) available -= totalRead; } else { // Humor the underlying channel -- it may need // to read data internally. // Important to set totalRead because it may be -1, // which we have to propagate outwards for EOF detection. totalRead = channel.read(BufferUtils.getEmptyBuffer()); channel.interestRead(false); if(lastInterestState) throttle.interest(throttleListener); } //LOG.debug("Read: " + totalRead + ", leaving: " + available + " left."); return totalRead; } /** Closes the underlying channel. */ public void close() throws IOException { Channel source = channel; if(source != null) source.close(); } /** Determines if the underlying channel is open. */ public boolean isOpen() { Channel source = channel; return source != null ? source.isOpen() : false; } /** * The attachment must be the same as the attachment of the * <code>SelectionKey</code> associated with the socket * <code>ThrottleReader</code> uses. */ public void setAttachment(Object o) { throttleListener.setAttachment(o); } private final class Listener implements ThrottleListener { /** The object that the Throttle will recognize as the SelectionKey attachments */ private Object attachment; /** * Notification from the <code>Throttle</code> that bandwidth is available. * Returns <code>false</code> if the channel no longer is open and will not * be interested again. */ public boolean bandwidthAvailable() { if(channel.isOpen() && lastInterestState) { channel.interestRead(true); return true; } else { return false; } } /** Determines if the underlying channel is open. */ public boolean isOpen() { return ThrottleReader.this.isOpen(); } /** * Requests some bandwidth from the throttle. */ public void requestBandwidth() { available = throttle.request(); } /** * Releases available bandwidth back to the throttle. */ public void releaseBandwidth() { throttle.release(available); available = 0; if(lastInterestState) throttle.interest(this); } /** Sets the attachment that the <code>Throttle</code> will recognize for this Writer. */ public void setAttachment(Object att) { attachment = att; } /** Gets the attachment. */ public Object getAttachment() { return attachment; } } }