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