com.ning.compress.lzf.util.LZFFileInputStream Maven / Gradle / Ivy
Show all versions of compress-lzf Show documentation
package com.ning.compress.lzf.util;
import java.io.*;
import com.ning.compress.BufferRecycler;
import com.ning.compress.lzf.*;
/**
* Helper class that allows use of LZF compression even if a library requires
* use of {@link FileInputStream}.
*
* Note that use of this class is not recommended unless you absolutely must
* use a {@link FileInputStream} instance; otherwise basic {@link LZFInputStream}
* (which uses aggregation for underlying streams) is more appropriate
*
*
* Implementation note: much of the code is just copied from {@link LZFInputStream},
* so care must be taken to keep implementations in sync if there are fixes.
*/
public class LZFFileInputStream
extends FileInputStream
{
/**
* Underlying decoder in use.
*/
protected final ChunkDecoder _decompressor;
/**
* Object that handles details of buffer recycling
*/
protected final BufferRecycler _recycler;
/**
* Flag that indicates if we have already called 'inputStream.close()'
* (to avoid calling it multiple times)
*/
protected boolean _inputStreamClosed;
/**
* Flag that indicates whether we force full reads (reading of as many
* bytes as requested), or 'optimal' reads (up to as many as available,
* but at least one). Default is false, meaning that 'optimal' read
* is used.
*/
protected boolean _cfgFullReads = false;
/**
* the current buffer of compressed bytes (from which to decode)
* */
protected byte[] _inputBuffer;
/**
* the buffer of uncompressed bytes from which content is read
* */
protected byte[] _decodedBytes;
/**
* The current position (next char to output) in the uncompressed bytes buffer.
* */
protected int _bufferPosition = 0;
/**
* Length of the current uncompressed bytes buffer
* */
protected int _bufferLength = 0;
/**
* Wrapper object we use to allow decoder to read directly from the
* stream, without ending in infinite loop...
*/
protected final Wrapper _wrapper;
/*
///////////////////////////////////////////////////////////////////////
// Construction, configuration
///////////////////////////////////////////////////////////////////////
*/
public LZFFileInputStream(File file) throws FileNotFoundException {
this(file, ChunkDecoderFactory.optimalInstance(), BufferRecycler.instance());
}
public LZFFileInputStream(FileDescriptor fdObj) {
this(fdObj, ChunkDecoderFactory.optimalInstance(), BufferRecycler.instance());
}
public LZFFileInputStream(String name) throws FileNotFoundException {
this(name, ChunkDecoderFactory.optimalInstance(), BufferRecycler.instance());
}
public LZFFileInputStream(File file, ChunkDecoder decompressor) throws FileNotFoundException
{
this(file, decompressor, BufferRecycler.instance());
}
public LZFFileInputStream(FileDescriptor fdObj, ChunkDecoder decompressor)
{
this(fdObj, decompressor, BufferRecycler.instance());
}
public LZFFileInputStream(String name, ChunkDecoder decompressor) throws FileNotFoundException
{
this(name, decompressor, BufferRecycler.instance());
}
public LZFFileInputStream(File file, ChunkDecoder decompressor, BufferRecycler bufferRecycler) throws FileNotFoundException
{
super(file);
_decompressor = decompressor;
_recycler = bufferRecycler;
_inputStreamClosed = false;
_inputBuffer = bufferRecycler.allocInputBuffer(LZFChunk.MAX_CHUNK_LEN);
_decodedBytes = bufferRecycler.allocDecodeBuffer(LZFChunk.MAX_CHUNK_LEN);
_wrapper = new Wrapper();
}
public LZFFileInputStream(FileDescriptor fdObj, ChunkDecoder decompressor, BufferRecycler bufferRecycler)
{
super(fdObj);
_decompressor = decompressor;
_recycler = bufferRecycler;
_inputStreamClosed = false;
_inputBuffer = bufferRecycler.allocInputBuffer(LZFChunk.MAX_CHUNK_LEN);
_decodedBytes = bufferRecycler.allocDecodeBuffer(LZFChunk.MAX_CHUNK_LEN);
_wrapper = new Wrapper();
}
public LZFFileInputStream(String name, ChunkDecoder decompressor, BufferRecycler bufferRecycler) throws FileNotFoundException
{
super(name);
_decompressor = decompressor;
_recycler = bufferRecycler;
_inputStreamClosed = false;
_inputBuffer = bufferRecycler.allocInputBuffer(LZFChunk.MAX_CHUNK_LEN);
_decodedBytes = bufferRecycler.allocDecodeBuffer(LZFChunk.MAX_CHUNK_LEN);
_wrapper = new Wrapper();
}
/**
* Method that can be used define whether reads should be "full" or
* "optimal": former means that full compressed blocks are read right
* away as needed, optimal that only smaller chunks are read at a time,
* more being read as needed.
*/
public void setUseFullReads(boolean b) {
_cfgFullReads = b;
}
/*
///////////////////////////////////////////////////////////////////////
// FileInputStream overrides
///////////////////////////////////////////////////////////////////////
*/
@Override
public int available()
{
if (_inputStreamClosed) { // javadocs suggest 0 for closed as well (not -1)
return 0;
}
int left = (_bufferLength - _bufferPosition);
return (left <= 0) ? 0 : left;
}
@Override
public void close() throws IOException
{
_bufferPosition = _bufferLength = 0;
byte[] buf = _inputBuffer;
if (buf != null) {
_inputBuffer = null;
_recycler.releaseInputBuffer(buf);
}
buf = _decodedBytes;
if (buf != null) {
_decodedBytes = null;
_recycler.releaseDecodeBuffer(buf);
}
if (!_inputStreamClosed) {
_inputStreamClosed = true;
super.close();
}
}
// fine as is: don't override
// public FileChannel getChannel();
// final, can't override:
//public FileDescriptor getFD();
@Override
public int read() throws IOException
{
if (!readyBuffer()) {
return -1;
}
return _decodedBytes[_bufferPosition++] & 255;
}
@Override
public int read(byte[] b) throws IOException
{
return read(b, 0, b.length);
}
@Override
public int read(byte[] buffer, int offset, int length) throws IOException
{
if (!readyBuffer()) {
return -1;
}
if (length < 1) {
return 0;
}
// First let's read however much data we happen to have...
int chunkLength = Math.min(_bufferLength - _bufferPosition, length);
System.arraycopy(_decodedBytes, _bufferPosition, buffer, offset, chunkLength);
_bufferPosition += chunkLength;
if (chunkLength == length || !_cfgFullReads) {
return chunkLength;
}
// Need more data, then
int totalRead = chunkLength;
do {
offset += chunkLength;
if (!readyBuffer()) {
break;
}
chunkLength = Math.min(_bufferLength - _bufferPosition, (length - totalRead));
System.arraycopy(_decodedBytes, _bufferPosition, buffer, offset, chunkLength);
_bufferPosition += chunkLength;
totalRead += chunkLength;
} while (totalRead < length);
return totalRead;
}
/**
* Overridden to just skip at most a single chunk at a time
*/
/*
@Override
public long skip(long n) throws IOException
{
if (!readyBuffer()) {
return -1L;
}
int left = (_bufferLength - _bufferPosition);
// either way, just skip whatever we have decoded
if (left > n) {
left = (int) n;
}
_bufferPosition += left;
return left;
}
*/
/**
* Overridden to implement efficient skipping by skipping full chunks whenever
* possible.
*/
@Override
public long skip(long n) throws IOException
{
if (_inputStreamClosed) {
return -1;
}
if (n <= 0L) {
return n;
}
long skipped;
// if any left to skip, just return that for simplicity
if (_bufferPosition < _bufferLength) {
int left = (_bufferLength - _bufferPosition);
if (n <= left) { // small skip, fulfilled from what we already got
_bufferPosition += (int) n;
return n;
}
_bufferPosition = _bufferLength;
skipped = left;
n -= left;
} else {
skipped = 0L;
}
// and then full-chunk skipping, if possible
while (true) {
int amount = _decompressor.skipOrDecodeChunk(_wrapper, _inputBuffer, _decodedBytes, n);
if (amount >= 0) { // successful skipping of the chunk
skipped += amount;
n -= amount;
if (n <= 0L) {
return skipped;
}
continue;
}
if (amount == -1) { // EOF
close();
return skipped;
}
// decoded buffer-full, more than max skip
_bufferLength = -(amount+1);
skipped += n;
_bufferPosition = (int) n;
return skipped;
}
}
/*
///////////////////////////////////////////////////////////////////////
// Extended public API
///////////////////////////////////////////////////////////////////////
*/
/**
* Convenience method that will read and uncompress all data available,
* and write it using given {@link OutputStream}. This avoids having to
* make an intermediate copy of uncompressed data which would be needed
* when doing the same manually.
*
* @param out OutputStream to use for writing content
*
* @return Number of bytes written (uncompressed)
*/
public int readAndWrite(OutputStream out) throws IOException
{
int total = 0;
while (readyBuffer()) {
int avail = _bufferLength - _bufferPosition;
out.write(_decodedBytes, _bufferPosition, avail);
_bufferPosition += avail; // to ensure it looks like we consumed it all
total += avail;
}
return total;
}
/*
///////////////////////////////////////////////////////////////////////
// Internal methods
///////////////////////////////////////////////////////////////////////
*/
/**
* Fill the uncompressed bytes buffer by reading the underlying inputStream.
* @throws IOException
*/
protected boolean readyBuffer() throws IOException
{
if (_inputStreamClosed) {
throw new IOException("Input stream closed");
}
if (_bufferPosition < _bufferLength) {
return true;
}
_bufferLength = _decompressor.decodeChunk(_wrapper, _inputBuffer, _decodedBytes);
if (_bufferLength < 0) {
close();
return false;
}
_bufferPosition = 0;
return (_bufferPosition < _bufferLength);
}
protected final int readRaw(byte[] buffer, int offset, int length) throws IOException {
return super.read(buffer, offset, length);
}
protected final long skipRaw(long amount) throws IOException {
return super.skip(amount);
}
/*
///////////////////////////////////////////////////////////////////////
// Helper class(es)
///////////////////////////////////////////////////////////////////////
*/
/**
* This simple wrapper is needed to re-route read calls so that they will
* use "raw" reads
*/
private final class Wrapper extends InputStream
{
@Override
public void close() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public int read() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public int read(byte[] buffer, int offset, int length) throws IOException {
return readRaw(buffer, offset, length);
}
@Override
public int read(byte[] buffer) throws IOException {
return readRaw(buffer, 0, buffer.length);
}
@Override
public long skip(long n) throws IOException {
return skipRaw(n);
}
}
}