package org.limewire.nio.channel;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
/**
* Reads data from a source channel and offers the inflated version for reading.
* <p>
* Each invocation of {@link #read(ByteBuffer)} attempts to return as much
* inflated data as possible.
*/
public class InflaterReader implements ChannelReader, InterestReadableByteChannel {
/** the inflater that will do the decompressing for us */
private Inflater inflater;
/** the channel this reads from */
private InterestReadableByteChannel channel;
/** the temporary buffer that data from the channel goes to prior to inflating */
private ByteBuffer data;
/**
* Constructs a new InflaterReader without an underlying source.
* <p>
* Prior to <code>read(ByteBuffer)</code> being called,
* <code>setReadChannel(ReadableByteChannel)</code> MUST be called.
*/
public InflaterReader(Inflater inflater) {
this(null, inflater);
}
/**
* Constructs a new <code>InflaterReader</code> with the given source
* channel and inflater.
*/
public InflaterReader(InterestReadableByteChannel channel, Inflater inflater ) {
if(inflater == null)
throw new NullPointerException("null inflater!");
this.channel = channel;
this.inflater = inflater;
this.data = ByteBuffer.allocate(512);
}
/**
* Sets the new channel.
*/
public void setReadChannel(InterestReadableByteChannel channel) {
if(channel == null)
throw new NullPointerException("cannot set null channel!");
this.channel = channel;
}
/** Gets the read channel. */
public InterestReadableByteChannel getReadChannel() {
return channel;
}
public void interestRead(boolean status) {
channel.interestRead(status);
}
/**
* Reads from this inflater into the given <code>ByteBuffer</code>.
* <p>
* If data isn't available for inflation, data will be read from the source
* channel and inflation will be attempted. The {@link ByteBuffer} will be
* filled as much as possible without blocking.
* <p>
* The source channel may not be entirely emptied out in a single call to
* <code>read(ByteBuffer)</code>, because the supplied
* <code>ByteBuffer</code> may not be large enough to accept all inflated
* data. If this is the case, the data will remain in the source channel
* until further calls to <code>read(ByteBuffer)</code>.
* <p>
* The source channel does not need to be set for construction. However,
* before <code>read(ByteBuffer)</code> is called,
* {@link #setReadChannel(InterestReadableByteChannel)} must be called with
* a valid channel.
*/
public int read(ByteBuffer buffer) throws IOException {
int written = 0;
int read = 0;
// inflate loop... inflate -> read -> lather -> rinse -> repeat as necessary.
// only break out of this loop if
// a) output buffer gets full
// b) inflater finishes or needs a dictionary
// c) no data can be inflated & no data can be read off the channel
while(buffer.hasRemaining()) { // (case a above)
// first try to inflate any prior input from the inflater.
int inflated = inflate(buffer);
written += inflated;
// if we couldn't inflate anything...
if(inflated == 0) {
// if this inflater is done or needs a dictionary, we're screwed. (case b above)
if (inflater.finished() || inflater.needsDictionary()) {
read = -1;
break;
}
// if the buffer needs input, add it.
if(inflater.needsInput()) {
// First gobble up any data from the channel we're dependent on.
while(data.hasRemaining() && (read = channel.read(data)) > 0);
// if we couldn't read any data, we suck. (case c above)
if(data.position() == 0)
break;
// Then put that data into the inflater.
inflater.setInput(data.array(), 0, data.position());
data.clear();
}
}
// if we're here, we either:
// a) inflated some data
// b) didn't inflate, but read some data that we input'd to the inflater
// if a), we'll continue trying to inflate so long as the output buffer
// has space left.
// if b), we try to inflate and ultimately end up at a).
}
if(written > 0)
return written;
else if(read == -1)
return -1;
else
return 0;
}
/** Inflates data to this buffer. */
private int inflate(ByteBuffer buffer) throws IOException {
int written = 0;
int position = buffer.position();
try {
written = inflater.inflate(buffer.array(), position, buffer.remaining());
} catch(DataFormatException dfe) {
IOException x = new IOException();
x.initCause(dfe);
throw x;
} catch(NullPointerException npe) {
// possible if the inflater was closed on a separate thread.
IOException x = new IOException();
x.initCause(npe);
throw x;
}
buffer.position(position + written);
return written;
}
/**
* Determines if the underlying channel is open. <code>setReadChannel</code>
* must be called prior to calling <code>isOpen</code>.
*/
public boolean isOpen() {
return channel.isOpen();
}
/**
* Closes the underlying channel.
*/
public void close() throws IOException {
channel.close();
}
}