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  import org.apache.commons.io.input.CloseShieldInputStream;
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                 // unpack would close this stream but we want to give the call site more control
142                 // TODO unpack should not close its given stream.
143                 try (CloseShieldInputStream closeShield = CloseShieldInputStream.wrap(inputStream)) {
144                     unpacker.unpack(closeShield, jarOut);
145                 }
146             } else {
147                 unpacker.unpack(file, jarOut);
148             }
149         }
150     }
151 
152     /**
153      * Decompresses the given stream, caching the decompressed data in memory and using the given properties.
154      *
155      * <p>
156      * When reading from a file the File-arg constructor may provide better performance.
157      * </p>
158      *
159      * @param inputStream the InputStream from which this object should be created
160      * @param properties  Pack200 properties to use
161      * @throws IOException if reading fails
162      */
163     public Pack200CompressorInputStream(final InputStream inputStream, final Map<String, String> properties) throws IOException {
164         this(inputStream, Pack200Strategy.IN_MEMORY, properties);
165     }
166 
167     /**
168      * Decompresses the given stream using the given strategy to cache the results.
169      *
170      * <p>
171      * When reading from a file the File-arg constructor may provide better performance.
172      * </p>
173      *
174      * @param inputStream the InputStream from which this object should be created
175      * @param mode        the strategy to use
176      * @throws IOException if reading fails
177      */
178     public Pack200CompressorInputStream(final InputStream inputStream, final Pack200Strategy mode) throws IOException {
179         this(inputStream, null, mode, null);
180     }
181 
182     /**
183      * Decompresses the given stream using the given strategy to cache the results and the given properties.
184      *
185      * <p>
186      * When reading from a file the File-arg constructor may provide better performance.
187      * </p>
188      *
189      * @param inputStream the InputStream from which this object should be created
190      * @param mode        the strategy to use
191      * @param properties  Pack200 properties to use
192      * @throws IOException if reading fails
193      */
194     public Pack200CompressorInputStream(final InputStream inputStream, final Pack200Strategy mode, final Map<String, String> properties) throws IOException {
195         this(inputStream, null, mode, properties);
196     }
197 
198     @SuppressWarnings("resource") // Does not allocate
199     @Override
200     public int available() throws IOException {
201         return getInputStream().available();
202     }
203 
204     @Override
205     public void close() throws IOException {
206         try {
207             abstractStreamBridge.stop();
208         } finally {
209             if (originalInputStream != null) {
210                 originalInputStream.close();
211             }
212         }
213     }
214 
215     private InputStream getInputStream() throws IOException {
216         return abstractStreamBridge.getInputStream();
217     }
218 
219     @SuppressWarnings("resource") // Does not allocate
220     @Override
221     public synchronized void mark(final int limit) {
222         try {
223             getInputStream().mark(limit);
224         } catch (final IOException ex) {
225             throw new UncheckedIOException(ex); // NOSONAR
226         }
227     }
228 
229     @SuppressWarnings("resource") // Does not allocate
230     @Override
231     public boolean markSupported() {
232         try {
233             return getInputStream().markSupported();
234         } catch (final IOException ex) { // NOSONAR
235             return false;
236         }
237     }
238 
239     @SuppressWarnings("resource") // Does not allocate
240     @Override
241     public int read() throws IOException {
242         return getInputStream().read();
243     }
244 
245     @SuppressWarnings("resource") // Does not allocate
246     @Override
247     public int read(final byte[] b) throws IOException {
248         return getInputStream().read(b);
249     }
250 
251     @SuppressWarnings("resource") // Does not allocate
252     @Override
253     public int read(final byte[] b, final int off, final int count) throws IOException {
254         return getInputStream().read(b, off, count);
255     }
256 
257     @SuppressWarnings("resource") // Does not allocate
258     @Override
259     public synchronized void reset() throws IOException {
260         getInputStream().reset();
261     }
262 
263     @SuppressWarnings("resource") // Does not allocate
264     @Override
265     public long skip(final long count) throws IOException {
266         return org.apache.commons.io.IOUtils.skip(getInputStream(), count);
267     }
268 }