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}