All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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