View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.compress.compressors.lzma;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  
24  import org.apache.commons.compress.MemoryLimitException;
25  import org.apache.commons.compress.compressors.CompressorInputStream;
26  import org.apache.commons.compress.utils.InputStreamStatistics;
27  import org.apache.commons.io.build.AbstractStreamBuilder;
28  import org.apache.commons.io.input.BoundedInputStream;
29  import org.tukaani.xz.LZMA2Options;
30  import org.tukaani.xz.LZMAInputStream;
31  
32  /**
33   * LZMA decompressor.
34   *
35   * @since 1.6
36   */
37  public class LZMACompressorInputStream extends CompressorInputStream implements InputStreamStatistics {
38  
39      // @formatter:off
40      /**
41       * Builds a new {@link LZMACompressorInputStream}.
42       *
43       * <p>
44       * For example:
45       * </p>
46       * <pre>{@code
47       * LZMACompressorOutputStream s = LZMACompressorInputStream.builder()
48       *   .setPath(path)
49       *   .get();
50       * }
51       * </pre>
52       *
53       * @see #get()
54       * @see LZMA2Options
55       * @since 1.28.0
56       */
57      // @formatter:on
58      public static class Builder extends AbstractStreamBuilder<LZMACompressorInputStream, Builder> {
59  
60          private int memoryLimitKiB = -1;
61  
62          @Override
63          public LZMACompressorInputStream get() throws IOException {
64              return new LZMACompressorInputStream(this);
65          }
66  
67          /**
68           * Sets a working memory threshold in kibibytes (KiB).
69           *
70           * @param memoryLimitKiB Sets a working memory threshold in kibibytes (KiB). Processing throws MemoryLimitException if memory use is above this
71           *                       threshold.
72           * @return this instance.
73           */
74          public Builder setMemoryLimitKiB(final int memoryLimitKiB) {
75              this.memoryLimitKiB = memoryLimitKiB;
76              return this;
77          }
78      }
79  
80      /**
81       * Constructs a new builder of {@link LZMACompressorOutputStream}.
82       *
83       * @return a new builder of {@link LZMACompressorOutputStream}.
84       * @since 1.28.0
85       */
86      public static Builder builder() {
87          return new Builder();
88      }
89  
90      /**
91       * Checks if the signature matches what is expected for an LZMA file.
92       *
93       * @param signature the bytes to check
94       * @param length    the number of bytes to check
95       * @return true, if this stream is an LZMA compressed stream, false otherwise
96       * @since 1.10
97       */
98      public static boolean matches(final byte[] signature, final int length) {
99          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 }