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
20 package org.apache.commons.compress.compressors.pack200;
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.UncheckedIOException;
26 import java.util.Map;
27 import java.util.jar.JarOutputStream;
28
29 import org.apache.commons.compress.compressors.CompressorInputStream;
30 import org.apache.commons.compress.java.util.jar.Pack200;
31 import org.apache.commons.io.IOUtils;
32
33 /**
34 * An input stream that decompresses from the Pack200 format to be read as any other stream.
35 *
36 * <p>
37 * The {@link CompressorInputStream#getCount getCount} and {@link CompressorInputStream#getBytesRead getBytesRead} methods always return 0.
38 * </p>
39 *
40 * @NotThreadSafe
41 * @since 1.3
42 */
43 public class Pack200CompressorInputStream extends CompressorInputStream {
44
45 private static final byte[] CAFE_DOOD = { (byte) 0xCA, (byte) 0xFE, (byte) 0xD0, (byte) 0x0D };
46 private static final int SIG_LENGTH = CAFE_DOOD.length;
47
48 /**
49 * Checks if the signature matches what is expected for a pack200 file (0xCAFED00D).
50 *
51 * @param signature the bytes to check
52 * @param length the number of bytes to check
53 * @return true, if this stream is a pack200 compressed stream, false otherwise
54 */
55 public static boolean matches(final byte[] signature, final int length) {
56 if (length < SIG_LENGTH) {
57 return false;
58 }
59
60 for (int i = 0; i < SIG_LENGTH; i++) {
61 if (signature[i] != CAFE_DOOD[i]) {
62 return false;
63 }
64 }
65
66 return true;
67 }
68
69 private final InputStream originalInputStream;
70
71 private final AbstractStreamBridge abstractStreamBridge;
72
73 /**
74 * Decompresses the given file, caching the decompressed data in memory.
75 *
76 * @param file the file to decompress
77 * @throws IOException if reading fails
78 */
79 public Pack200CompressorInputStream(final File file) throws IOException {
80 this(file, Pack200Strategy.IN_MEMORY);
81 }
82
83 /**
84 * Decompresses the given file, caching the decompressed data in memory and using the given properties.
85 *
86 * @param file the file to decompress
87 * @param properties Pack200 properties to use
88 * @throws IOException if reading fails
89 */
90 public Pack200CompressorInputStream(final File file, final Map<String, String> properties) throws IOException {
91 this(file, Pack200Strategy.IN_MEMORY, properties);
92 }
93
94 /**
95 * Decompresses the given file using the given strategy to cache the results.
96 *
97 * @param file the file to decompress
98 * @param mode the strategy to use
99 * @throws IOException if reading fails
100 */
101 public Pack200CompressorInputStream(final File file, final Pack200Strategy mode) throws IOException {
102 this(null, file, mode, null);
103 }
104
105 /**
106 * Decompresses the given file using the given strategy to cache the results and the given properties.
107 *
108 * @param file the file to decompress
109 * @param mode the strategy to use
110 * @param properties Pack200 properties to use
111 * @throws IOException if reading fails
112 */
113 public Pack200CompressorInputStream(final File file, final Pack200Strategy mode, final Map<String, String> properties) throws IOException {
114 this(null, file, mode, properties);
115 }
116
117 /**
118 * Decompresses the given stream, caching the decompressed data in memory.
119 *
120 * <p>
121 * When reading from a file the File-arg constructor may provide better performance.
122 * </p>
123 *
124 * @param inputStream the InputStream from which this object should be created
125 * @throws IOException if reading fails
126 */
127 public Pack200CompressorInputStream(final InputStream inputStream) throws IOException {
128 this(inputStream, Pack200Strategy.IN_MEMORY);
129 }
130
131 private Pack200CompressorInputStream(final InputStream inputStream, final File file, final Pack200Strategy mode, final Map<String, String> properties)
132 throws IOException {
133 this.originalInputStream = inputStream;
134 this.abstractStreamBridge = mode.newStreamBridge();
135 try (JarOutputStream jarOut = new JarOutputStream(abstractStreamBridge)) {
136 final Pack200.Unpacker unpacker = Pack200.newUnpacker();
137 if (properties != null) {
138 unpacker.properties().putAll(properties);
139 }
140 if (file == null) {
141 unpacker.unpack(inputStream, jarOut);
142 } else {
143 unpacker.unpack(file, jarOut);
144 }
145 }
146 }
147
148 /**
149 * Decompresses the given stream, caching the decompressed data in memory and using the given properties.
150 *
151 * <p>
152 * When reading from a file the File-arg constructor may provide better performance.
153 * </p>
154 *
155 * @param inputStream the InputStream from which this object should be created
156 * @param properties Pack200 properties to use
157 * @throws IOException if reading fails
158 */
159 public Pack200CompressorInputStream(final InputStream inputStream, final Map<String, String> properties) throws IOException {
160 this(inputStream, Pack200Strategy.IN_MEMORY, properties);
161 }
162
163 /**
164 * Decompresses the given stream using the given strategy to cache the results.
165 *
166 * <p>
167 * When reading from a file the File-arg constructor may provide better performance.
168 * </p>
169 *
170 * @param inputStream the InputStream from which this object should be created
171 * @param mode the strategy to use
172 * @throws IOException if reading fails
173 */
174 public Pack200CompressorInputStream(final InputStream inputStream, final Pack200Strategy mode) throws IOException {
175 this(inputStream, null, mode, null);
176 }
177
178 /**
179 * Decompresses the given stream using the given strategy to cache the results and the given properties.
180 *
181 * <p>
182 * When reading from a file the File-arg constructor may provide better performance.
183 * </p>
184 *
185 * @param inputStream the InputStream from which this object should be created
186 * @param mode the strategy to use
187 * @param properties Pack200 properties to use
188 * @throws IOException if reading fails
189 */
190 public Pack200CompressorInputStream(final InputStream inputStream, final Pack200Strategy mode, final Map<String, String> properties) throws IOException {
191 this(inputStream, null, mode, properties);
192 }
193
194 @SuppressWarnings("resource") // Does not allocate
195 @Override
196 public int available() throws IOException {
197 return getInputStream().available();
198 }
199
200 @Override
201 public void close() throws IOException {
202 try {
203 abstractStreamBridge.stop();
204 } finally {
205 IOUtils.close(originalInputStream);
206 }
207 }
208
209 private InputStream getInputStream() throws IOException {
210 return abstractStreamBridge.getInputStream();
211 }
212
213 @SuppressWarnings("resource") // Does not allocate
214 @Override
215 public synchronized void mark(final int limit) {
216 try {
217 getInputStream().mark(limit);
218 } catch (final IOException ex) {
219 throw new UncheckedIOException(ex); // NOSONAR
220 }
221 }
222
223 @SuppressWarnings("resource") // Does not allocate
224 @Override
225 public boolean markSupported() {
226 try {
227 return getInputStream().markSupported();
228 } catch (final IOException ex) { // NOSONAR
229 return false;
230 }
231 }
232
233 @SuppressWarnings("resource") // Does not allocate
234 @Override
235 public int read() throws IOException {
236 return getInputStream().read();
237 }
238
239 @SuppressWarnings("resource") // Does not allocate
240 @Override
241 public int read(final byte[] b) throws IOException {
242 return getInputStream().read(b);
243 }
244
245 @SuppressWarnings("resource") // Does not allocate
246 @Override
247 public int read(final byte[] b, final int off, final int count) throws IOException {
248 return getInputStream().read(b, off, count);
249 }
250
251 @SuppressWarnings("resource") // Does not allocate
252 @Override
253 public synchronized void reset() throws IOException {
254 getInputStream().reset();
255 }
256
257 @SuppressWarnings("resource") // Does not allocate
258 @Override
259 public long skip(final long count) throws IOException {
260 return IOUtils.skip(getInputStream(), count);
261 }
262 }