package com.limegroup.gnutella.connection;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.zip.Inflater;
import java.util.zip.DataFormatException;
import com.limegroup.gnutella.io.ChannelReader;
import com.limegroup.gnutella.io.InterestReadChannel;
/**
* Reads data from a source channel and offers the inflated version for reading.
*
* Each invocation of read(ByteBuffer) will attempt to return any inflated data.
* If no data is available for inflation, data will be read from the source channel
* and inflation will be attempted. The ByteBuffer will be filled as much as possible
* without blocking.
*
* The source channel may not be entirely emptied out in a single call to read(ByteBuffer),
* because the supplied ByteBuffer 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
* read(ByteBuffer).
*
* The source channel does not need to be set for construction. However, before read(ByteBuffer)
* is called, setReadChannel(ReadableByteChannel) must be called with a valid channel.
*/
public class InflaterReader implements ChannelReader, InterestReadChannel {
/** the inflater that will do the decompressing for us */
private Inflater inflater;
/** the channel this reads from */
private InterestReadChannel 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.
* Prior to read(ByteBuffer) being called, setReadChannel(ReadableByteChannel)
* MUST be called.
*/
public InflaterReader(Inflater inflater) {
this(null, inflater);
}
/**
* Constructs a new InflaterReader with the given source channel & inflater.
*/
public InflaterReader(InterestReadChannel 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(InterestReadChannel channel) {
if(channel == null)
throw new NullPointerException("cannot set null channel!");
this.channel = channel;
}
/** Gets the read channel */
public InterestReadChannel getReadChannel() {
return channel;
}
public void interest(boolean status) {
channel.interest(status);
}
/**
* Reads from this' inflater into the given ByteBuffer.
*/
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 this reader is open.
*/
public boolean isOpen() {
return channel.isOpen();
}
/**
* Closes this channel.
*/
public void close() throws IOException {
channel.close();
}
}