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 }