org.xbib.io.compress.xz.SingleXZInputStream Maven / Gradle / Ivy
The newest version!
package org.xbib.io.compress.xz;
import org.xbib.io.compress.xz.check.Check;
import org.xbib.io.compress.xz.common.DecoderUtil;
import org.xbib.io.compress.xz.common.StreamFlags;
import org.xbib.io.compress.xz.index.IndexHash;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Decompresses exactly one XZ Stream in streamed mode (no seeking).
* The decompression stops after the first XZ Stream has been decompressed,
* and the read position in the input stream is left at the first byte
* after the end of the XZ Stream. This can be useful when XZ data has
* been stored inside some other file format or protocol.
* Unless you know what you are doing, don't use this class to decompress
* standalone .xz files. For that purpose, use XZInputStream
.
* When uncompressed size is known beforehand
* If you are decompressing complete XZ streams and your application knows
* exactly how much uncompressed data there should be, it is good to try
* reading one more byte by calling read()
and checking
* that it returns -1
. This way the decompressor will parse the
* file footers and verify the integrity checks, giving the caller more
* confidence that the uncompressed data is valid.
*
* @see XZInputStream
*/
public class SingleXZInputStream extends InputStream {
private InputStream in;
private int memoryLimit;
private StreamFlags streamHeaderFlags;
private Check check;
private BlockInputStream blockDecoder = null;
private final IndexHash indexHash = new IndexHash();
private boolean endReached = false;
private IOException exception = null;
/**
* Creates a new XZ decompressor that decompresses exactly one
* XZ Stream from in
without a memory usage limit.
* This constructor reads and parses the XZ Stream Header (12 bytes)
* from in
. The header of the first Block is not read
* until read
is called.
*
* @param in input stream from which XZ-compressed
* data is read
* @throws XZFormatException input is not in the XZ format
* @throws CorruptedInputException XZ header CRC32 doesn't match
* @throws UnsupportedOptionsException XZ header is valid but specifies options
* not supported by this implementation
* @throws java.io.EOFException less than 12 bytes of input was available
* from in
* @throws java.io.IOException may be thrown by in
*/
public SingleXZInputStream(InputStream in) throws IOException {
initialize(in, -1);
}
/**
* Creates a new XZ decompressor that decompresses exactly one
* XZ Stream from in
with an optional memory usage limit.
* This is identical to SingleXZInputStream(InputStream)
* except that this takes also the memoryLimit
argument.
*
* @param in input stream from which XZ-compressed
* data is read
* @param memoryLimit memory usage limit in kibibytes (KiB)
* or -1
to impose no
* memory usage limit
* @throws XZFormatException input is not in the XZ format
* @throws CorruptedInputException XZ header CRC32 doesn't match
* @throws UnsupportedOptionsException XZ header is valid but specifies options
* not supported by this implementation
* @throws java.io.EOFException less than 12 bytes of input was available
* from in
* @throws java.io.IOException may be thrown by in
*/
public SingleXZInputStream(InputStream in, int memoryLimit)
throws IOException {
initialize(in, memoryLimit);
}
SingleXZInputStream(InputStream in, int memoryLimit,
byte[] streamHeader) throws IOException {
initialize(in, memoryLimit, streamHeader);
}
private void initialize(InputStream in, int memoryLimit)
throws IOException {
byte[] streamHeader = new byte[DecoderUtil.STREAM_HEADER_SIZE];
new DataInputStream(in).readFully(streamHeader);
initialize(in, memoryLimit, streamHeader);
}
private void initialize(InputStream in, int memoryLimit,
byte[] streamHeader) throws IOException {
this.in = in;
this.memoryLimit = memoryLimit;
streamHeaderFlags = DecoderUtil.decodeStreamHeader(streamHeader);
check = Check.getInstance(streamHeaderFlags.checkType);
}
/**
* Gets the ID of the integrity check used in this XZ Stream.
*
* @return the Check ID specified in the XZ Stream Header
*/
public int getCheckType() {
return streamHeaderFlags.checkType;
}
/**
* Gets the name of the integrity check used in this XZ Stream.
*
* @return the name of the check specified in the XZ Stream Header
*/
public String getCheckName() {
return check.getName();
}
/**
* Decompresses the next byte from this input stream.
* Reading lots of data with read()
from this input stream
* may be inefficient. Wrap it in {@link java.io.BufferedInputStream}
* if you need to read lots of data one byte at a time.
*
* @return the next decompressed byte, or -1
* to indicate the end of the compressed stream
* @throws CorruptedInputException
* @throws UnsupportedOptionsException
* @throws MemoryLimitException
* @throws XZIOException if the stream has been closed
* @throws java.io.EOFException compressed input is truncated or corrupt
* @throws java.io.IOException may be thrown by in
*/
public int read() throws IOException {
byte[] buf = new byte[1];
return read(buf, 0, 1) == -1 ? -1 : (buf[0] & 0xFF);
}
/**
* Decompresses into an array of bytes.
* If len
is zero, no bytes are read and 0
* is returned. Otherwise this will try to decompress len
* bytes of uncompressed data. Less than len
bytes may
* be read only in the following situations:
*
* - The end of the compressed data was reached successfully.
* - An error is detected after at least one but less
len
* bytes have already been successfully decompressed.
* The next call with non-zero len
will immediately
* throw the pending exception.
* - An exception is thrown.
*
*
* @param buf target buffer for uncompressed data
* @param off start offset in buf
* @param len maximum number of uncompressed bytes to read
* @return number of bytes read, or -1
to indicate
* the end of the compressed stream
* @throws CorruptedInputException
* @throws UnsupportedOptionsException
* @throws MemoryLimitException
* @throws XZIOException if the stream has been closed
* @throws java.io.EOFException compressed input is truncated or corrupt
* @throws java.io.IOException may be thrown by in
*/
public int read(byte[] buf, int off, int len) throws IOException {
if (off < 0 || len < 0 || off + len < 0 || off + len > buf.length) {
throw new IndexOutOfBoundsException();
}
if (len == 0) {
return 0;
}
if (in == null) {
throw new XZIOException("Stream closed");
}
if (exception != null) {
throw exception;
}
if (endReached) {
return -1;
}
int size = 0;
try {
while (len > 0) {
if (blockDecoder == null) {
try {
blockDecoder = new BlockInputStream(
in, check, memoryLimit, -1, -1);
} catch (IndexIndicatorException e) {
indexHash.validate(in);
validateStreamFooter();
endReached = true;
return size > 0 ? size : -1;
}
}
int ret = blockDecoder.read(buf, off, len);
if (ret > 0) {
size += ret;
off += ret;
len -= ret;
} else if (ret == -1) {
indexHash.add(blockDecoder.getUnpaddedSize(),
blockDecoder.getUncompressedSize());
blockDecoder = null;
}
}
} catch (IOException e) {
exception = e;
if (size == 0) {
throw e;
}
}
return size;
}
private void validateStreamFooter() throws IOException {
byte[] buf = new byte[DecoderUtil.STREAM_HEADER_SIZE];
new DataInputStream(in).readFully(buf);
StreamFlags streamFooterFlags = DecoderUtil.decodeStreamFooter(buf);
if (!DecoderUtil.areStreamFlagsEqual(streamHeaderFlags,
streamFooterFlags)
|| indexHash.getIndexSize() != streamFooterFlags.backwardSize) {
throw new CorruptedInputException("XZ Stream Footer does not match Stream Header");
}
}
/**
* Returns the number of uncompressed bytes that can be read
* without blocking. The value is returned with an assumption
* that the compressed input data will be valid. If the compressed
* data is corrupt, CorruptedInputException
may get
* thrown before the number of bytes claimed to be available have
* been read from this input stream.
*
* @return the number of uncompressed bytes that can be read
* without blocking
*/
public int available() throws IOException {
if (in == null) {
throw new XZIOException("Stream closed");
}
if (exception != null) {
throw exception;
}
return blockDecoder == null ? 0 : blockDecoder.available();
}
/**
* Closes the stream and calls in.close()
.
* If the stream was already closed, this does nothing.
*
* @throws java.io.IOException if thrown by in.close()
*/
public void close() throws IOException {
if (in != null) {
try {
in.close();
} finally {
in = null;
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy