    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.tukaani.xz.LZMAInputStream;
    026
    027import org.apache.commons.compress.compressors.CompressorInputStream;
    028import org.apache.commons.compress.utils.CountingInputStream;
    029import org.apache.commons.compress.utils.IOUtils;
    030import org.apache.commons.compress.utils.InputStreamStatistics;
    031
    032/**
    033 * LZMA decompressor.
    034 * @since 1.6
    035 */
    036public class LZMACompressorInputStream extends CompressorInputStream
    037    implements InputStreamStatistics {
    038
    039    private final CountingInputStream countingStream;
    040    private final InputStream in;
    041
    042    /**
    043     * Creates a new input stream that decompresses LZMA-compressed data
    044     * from the specified input stream.
    045     *
    046     * @param       inputStream where to read the compressed data
    047     *
    048     * @throws      IOException if the input is not in the .lzma format,
    049     *                          the input is corrupt or truncated, the .lzma
    050     *                          headers specify sizes that are not supported
    051     *                          by this implementation, or the underlying
    052     *                          <code>inputStream</code> throws an exception
    053     */
    054    public LZMACompressorInputStream(final InputStream inputStream)
    055            throws IOException {
    056        in = new LZMAInputStream(countingStream = new CountingInputStream(inputStream), -1);
    057    }
    058
    059    /**
    060     * Creates a new input stream that decompresses LZMA-compressed data
    061     * from the specified input stream.
    062     *
    063     * @param       inputStream where to read the compressed data
    064     *
    065     * @param       memoryLimitInKb calculated memory use threshold.  Throws MemoryLimitException
    066     *                            if calculate memory use is above this threshold
    067     *
    068     * @throws      IOException if the input is not in the .lzma format,
    069     *                          the input is corrupt or truncated, the .lzma
    070     *                          headers specify sizes that are not supported
    071     *                          by this implementation, or the underlying
    072     *                          <code>inputStream</code> throws an exception
    073     *
    074     * @since 1.14
    075     */
    076    public LZMACompressorInputStream(final InputStream inputStream, final int memoryLimitInKb)
    077            throws IOException {
    078        try {
    079            in = new LZMAInputStream(countingStream = new CountingInputStream(inputStream), memoryLimitInKb);
    080        } catch (final org.tukaani.xz.MemoryLimitException e) {
    081            //convert to commons-compress exception
    082            throw new MemoryLimitException(e.getMemoryNeeded(), e.getMemoryLimit(), e);
    083        }
    084    }
    085
    086    /** {@inheritDoc} */
    087    @Override
    088    public int read() throws IOException {
    089        final int ret = in.read();
    090        count(ret == -1 ? 0 : 1);
    091        return ret;
    092    }
    093
    094    /** {@inheritDoc} */
    095    @Override
    096    public int read(final byte[] buf, final int off, final int len) throws IOException {
    097        final int ret = in.read(buf, off, len);
    098        count(ret);
    099        return ret;
    100    }
    101
    102    /** {@inheritDoc} */
    103    @Override
    104    public long skip(final long n) throws IOException {
    105        return IOUtils.skip(in, n);
    106    }
    107
    108    /** {@inheritDoc} */
    109    @Override
    110    public int available() throws IOException {
    111        return in.available();
    112    }
    113
    114    /** {@inheritDoc} */
    115    @Override
    116    public void close() throws IOException {
    117        in.close();
    118    }
    119
    120    /**
    121     * @since 1.17
    122     */
    123    @Override
    124    public long getCompressedCount() {
    125        return countingStream.getBytesRead();
    126    }
    127
    128    /**
    129     * Checks if the signature matches what is expected for an lzma file.
    130     *
    131     * @param signature
    132     *            the bytes to check
    133     * @param length
    134     *            the number of bytes to check
    135     * @return true, if this stream is an lzma  compressed stream, false otherwise
    136     *
    137     * @since 1.10
    138     */
    139    public static boolean matches(final byte[] signature, final int length) {
    140        return signature != null && length >= 3 &&
    141                signature[0] == 0x5d && signature[1] == 0 &&
    142                signature[2] == 0;
    143    }
    144}