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}