    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one
    003 * or more contributor license agreements.  See the NOTICE file
    004 * distributed with this work for additional information
    005 * regarding copyright ownership.  The ASF licenses this file
    006 * to you under the Apache License, Version 2.0 (the
    007 * "License"); you may not use this file except in compliance
    008 * with the License.  You may obtain a copy of the License at
    009 *
    010 * http://www.apache.org/licenses/LICENSE-2.0
    011 *
    012 * Unless required by applicable law or agreed to in writing,
    013 * software distributed under the License is distributed on an
    014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015 * KIND, either express or implied.  See the License for the
    016 * specific language governing permissions and limitations
    017 * under the License.
    018 */
    019package org.apache.commons.compress.compressors.lzma;
    020
    021import java.io.IOException;
    022import java.io.InputStream;
    023
    024import org.apache.commons.compress.MemoryLimitException;
    025import org.apache.commons.compress.compressors.CompressorInputStream;
    026import org.apache.commons.compress.utils.InputStreamStatistics;
    027import org.apache.commons.io.input.BoundedInputStream;
    028import org.tukaani.xz.LZMAInputStream;
    029
    030/**
    031 * LZMA decompressor.
    032 *
    033 * @since 1.6
    034 */
    035public class LZMACompressorInputStream extends CompressorInputStream implements InputStreamStatistics {
    036
    037    /**
    038     * Checks if the signature matches what is expected for an LZMA file.
    039     *
    040     * @param signature the bytes to check
    041     * @param length    the number of bytes to check
    042     * @return true, if this stream is an LZMA compressed stream, false otherwise
    043     *
    044     * @since 1.10
    045     */
    046    public static boolean matches(final byte[] signature, final int length) {
    047        return signature != null && length >= 3 && signature[0] == 0x5d && signature[1] == 0 && signature[2] == 0;
    048    }
    049
    050    private final BoundedInputStream countingStream;
    051
    052    private final InputStream in;
    053
    054    /**
    055     * Creates a new input stream that decompresses LZMA-compressed data from the specified input stream.
    056     *
    057     * @param inputStream where to read the compressed data
    058     *
    059     * @throws IOException if the input is not in the .lzma format, the input is corrupt or truncated, the .lzma headers specify sizes that are not supported by
    060     *                     this implementation, or the underlying {@code inputStream} throws an exception
    061     */
    062    public LZMACompressorInputStream(final InputStream inputStream) throws IOException {
    063        in = new LZMAInputStream(countingStream = BoundedInputStream.builder().setInputStream(inputStream).get(), -1);
    064    }
    065
    066    /**
    067     * Creates a new input stream that decompresses LZMA-compressed data from the specified input stream.
    068     *
    069     * @param inputStream     where to read the compressed data
    070     *
    071     * @param memoryLimitInKb calculated memory use threshold. Throws MemoryLimitException if calculate memory use is above this threshold
    072     *
    073     * @throws IOException if the input is not in the .lzma format, the input is corrupt or truncated, the .lzma headers specify sizes that are not supported by
    074     *                     this implementation, or the underlying {@code inputStream} throws an exception
    075     *
    076     * @since 1.14
    077     */
    078    public LZMACompressorInputStream(final InputStream inputStream, final int memoryLimitInKb) throws IOException {
    079        try {
    080            in = new LZMAInputStream(countingStream = BoundedInputStream.builder().setInputStream(inputStream).get(), memoryLimitInKb);
    081        } catch (final org.tukaani.xz.MemoryLimitException e) {
    082            // convert to commons-compress exception
    083            throw new MemoryLimitException(e.getMemoryNeeded(), e.getMemoryLimit(), e);
    084        }
    085    }
    086
    087    /** {@inheritDoc} */
    088    @Override
    089    public int available() throws IOException {
    090        return in.available();
    091    }
    092
    093    /** {@inheritDoc} */
    094    @Override
    095    public void close() throws IOException {
    096        in.close();
    097    }
    098
    099    /**
    100     * @since 1.17
    101     */
    102    @Override
    103    public long getCompressedCount() {
    104        return countingStream.getCount();
    105    }
    106
    107    /** {@inheritDoc} */
    108    @Override
    109    public int read() throws IOException {
    110        final int ret = in.read();
    111        count(ret == -1 ? 0 : 1);
    112        return ret;
    113    }
    114
    115    /** {@inheritDoc} */
    116    @Override
    117    public int read(final byte[] buf, final int off, final int len) throws IOException {
    118        final int ret = in.read(buf, off, len);
    119        count(ret);
    120        return ret;
    121    }
    122
    123    /** {@inheritDoc} */
    124    @Override
    125    public long skip(final long n) throws IOException {
    126        return org.apache.commons.io.IOUtils.skip(in, n);
    127    }
    128}