    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 *   https://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.build.AbstractStreamBuilder;
    028import org.apache.commons.io.input.BoundedInputStream;
    029import org.tukaani.xz.LZMA2Options;
    030import org.tukaani.xz.LZMAInputStream;
    031
    032/**
    033 * LZMA decompressor.
    034 *
    035 * @since 1.6
    036 */
    037public class LZMACompressorInputStream extends CompressorInputStream implements InputStreamStatistics {
    038
    039    // @formatter:off
    040    /**
    041     * Builds a new {@link LZMACompressorInputStream}.
    042     *
    043     * <p>
    044     * For example:
    045     * </p>
    046     * <pre>{@code
    047     * LZMACompressorOutputStream s = LZMACompressorInputStream.builder()
    048     *   .setPath(path)
    049     *   .get();
    050     * }
    051     * </pre>
    052     *
    053     * @see #get()
    054     * @see LZMA2Options
    055     * @since 1.28.0
    056     */
    057    // @formatter:on
    058    public static class Builder extends AbstractStreamBuilder<LZMACompressorInputStream, Builder> {
    059
    060        private int memoryLimitKiB = -1;
    061
    062        @Override
    063        public LZMACompressorInputStream get() throws IOException {
    064            return new LZMACompressorInputStream(this);
    065        }
    066
    067        /**
    068         * Sets a working memory threshold in kibibytes (KiB).
    069         *
    070         * @param memoryLimitKiB Sets a working memory threshold in kibibytes (KiB). Processing throws MemoryLimitException if memory use is above this
    071         *                       threshold.
    072         * @return this instance.
    073         */
    074        public Builder setMemoryLimitKiB(final int memoryLimitKiB) {
    075            this.memoryLimitKiB = memoryLimitKiB;
    076            return this;
    077        }
    078    }
    079
    080    /**
    081     * Constructs a new builder of {@link LZMACompressorOutputStream}.
    082     *
    083     * @return a new builder of {@link LZMACompressorOutputStream}.
    084     * @since 1.28.0
    085     */
    086    public static Builder builder() {
    087        return new Builder();
    088    }
    089
    090    /**
    091     * Checks if the signature matches what is expected for an LZMA file.
    092     *
    093     * @param signature the bytes to check
    094     * @param length    the number of bytes to check
    095     * @return true, if this stream is an LZMA compressed stream, false otherwise
    096     * @since 1.10
    097     */
    098    public static boolean matches(final byte[] signature, final int length) {
    099        return signature != null && length >= 3 && signature[0] == 0x5d && signature[1] == 0 && signature[2] == 0;
    100    }
    101
    102    private final BoundedInputStream countingStream;
    103
    104    private final InputStream in;
    105
    106    @SuppressWarnings("resource") // Caller closes
    107    private LZMACompressorInputStream(final Builder builder) throws IOException {
    108        try {
    109            in = new LZMAInputStream(countingStream = BoundedInputStream.builder().setInputStream(builder.getInputStream()).get(), builder.memoryLimitKiB);
    110        } catch (final org.tukaani.xz.MemoryLimitException e) {
    111            // convert to Commons Compress exception
    112            throw new MemoryLimitException(e.getMemoryNeeded(), e.getMemoryLimit(), (Throwable) e);
    113        }
    114    }
    115
    116    /**
    117     * Creates a new input stream that decompresses LZMA-compressed data from the specified input stream.
    118     *
    119     * @param inputStream where to read the compressed data
    120     * @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
    121     *                     this implementation, or the underlying {@code inputStream} throws an exception
    122     */
    123    public LZMACompressorInputStream(final InputStream inputStream) throws IOException {
    124        this(builder().setInputStream(inputStream));
    125    }
    126
    127    /**
    128     * Creates a new input stream that decompresses LZMA-compressed data from the specified input stream.
    129     *
    130     * @param inputStream     where to read the compressed data
    131     * @param memoryLimitKiB Sets a working memory threshold in kibibytes (KiB). Processing throws MemoryLimitException if memory use is above this threshold.
    132     * @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
    133     *                     this implementation, or the underlying {@code inputStream} throws an exception
    134     *
    135     * @since 1.14
    136     * @deprecated Use {@link #builder()}.
    137     */
    138    @Deprecated
    139    public LZMACompressorInputStream(final InputStream inputStream, final int memoryLimitKiB) throws IOException {
    140        this(builder().setInputStream(inputStream).setMemoryLimitKiB(memoryLimitKiB));
    141    }
    142
    143    /** {@inheritDoc} */
    144    @Override
    145    public int available() throws IOException {
    146        return in.available();
    147    }
    148
    149    /** {@inheritDoc} */
    150    @Override
    151    public void close() throws IOException {
    152        in.close();
    153    }
    154
    155    /**
    156     * @since 1.17
    157     */
    158    @Override
    159    public long getCompressedCount() {
    160        return countingStream.getCount();
    161    }
    162
    163    /** {@inheritDoc} */
    164    @Override
    165    public int read() throws IOException {
    166        final int ret = in.read();
    167        count(ret == -1 ? 0 : 1);
    168        return ret;
    169    }
    170
    171    /** {@inheritDoc} */
    172    @Override
    173    public int read(final byte[] buf, final int off, final int len) throws IOException {
    174        final int ret = in.read(buf, off, len);
    175        count(ret);
    176        return ret;
    177    }
    178
    179    /** {@inheritDoc} */
    180    @Override
    181    public long skip(final long n) throws IOException {
    182        return org.apache.commons.io.IOUtils.skip(in, n);
    183    }
    184}