001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019
020package org.apache.commons.compress.compressors.pack200;
021
022import java.io.File;
023import java.io.IOException;
024import java.io.InputStream;
025import java.util.Map;
026import java.util.jar.JarOutputStream;
027import java.util.jar.Pack200;
028
029import org.apache.commons.compress.compressors.CompressorInputStream;
030import org.apache.commons.compress.utils.CloseShieldFilterInputStream;
031import org.apache.commons.compress.utils.IOUtils;
032
033/**
034 * An input stream that decompresses from the Pack200 format to be read
035 * as any other stream.
036 *
037 * <p>The {@link CompressorInputStream#getCount getCount} and {@link
038 * CompressorInputStream#getBytesRead getBytesRead} methods always
039 * return 0.</p>
040 *
041 * @NotThreadSafe
042 * @since 1.3
043 */
044public class Pack200CompressorInputStream extends CompressorInputStream {
045    private final InputStream originalInput;
046    private final StreamBridge streamBridge;
047
048    /**
049     * Decompresses the given stream, caching the decompressed data in
050     * memory.
051     *
052     * <p>When reading from a file the File-arg constructor may
053     * provide better performance.</p>
054     *
055     * @param in the InputStream from which this object should be created
056     * @throws IOException if reading fails
057     */
058    public Pack200CompressorInputStream(final InputStream in)
059        throws IOException {
060        this(in, Pack200Strategy.IN_MEMORY);
061    }
062
063    /**
064     * Decompresses the given stream using the given strategy to cache
065     * the results.
066     *
067     * <p>When reading from a file the File-arg constructor may
068     * provide better performance.</p>
069     *
070     * @param in the InputStream from which this object should be created
071     * @param mode the strategy to use
072     * @throws IOException if reading fails
073     */
074    public Pack200CompressorInputStream(final InputStream in,
075                                        final Pack200Strategy mode)
076        throws IOException {
077        this(in, null, mode, null);
078    }
079
080    /**
081     * Decompresses the given stream, caching the decompressed data in
082     * memory and using the given properties.
083     *
084     * <p>When reading from a file the File-arg constructor may
085     * provide better performance.</p>
086     *
087     * @param in the InputStream from which this object should be created
088     * @param props Pack200 properties to use
089     * @throws IOException if reading fails
090     */
091    public Pack200CompressorInputStream(final InputStream in,
092                                        final Map<String, String> props)
093        throws IOException {
094        this(in, Pack200Strategy.IN_MEMORY, props);
095    }
096
097    /**
098     * Decompresses the given stream using the given strategy to cache
099     * the results and the given properties.
100     *
101     * <p>When reading from a file the File-arg constructor may
102     * provide better performance.</p>
103     *
104     * @param in the InputStream from which this object should be created
105     * @param mode the strategy to use
106     * @param props Pack200 properties to use
107     * @throws IOException if reading fails
108     */
109    public Pack200CompressorInputStream(final InputStream in,
110                                        final Pack200Strategy mode,
111                                        final Map<String, String> props)
112        throws IOException {
113        this(in, null, mode, props);
114    }
115
116    /**
117     * Decompresses the given file, caching the decompressed data in
118     * memory.
119     *
120     * @param f the file to decompress
121     * @throws IOException if reading fails
122     */
123    public Pack200CompressorInputStream(final File f) throws IOException {
124        this(f, Pack200Strategy.IN_MEMORY);
125    }
126
127    /**
128     * Decompresses the given file using the given strategy to cache
129     * the results.
130     *
131     * @param f the file to decompress
132     * @param mode the strategy to use
133     * @throws IOException if reading fails
134     */
135    public Pack200CompressorInputStream(final File f, final Pack200Strategy mode)
136        throws IOException {
137        this(null, f, mode, null);
138    }
139
140    /**
141     * Decompresses the given file, caching the decompressed data in
142     * memory and using the given properties.
143     *
144     * @param f the file to decompress
145     * @param props Pack200 properties to use
146     * @throws IOException if reading fails
147     */
148    public Pack200CompressorInputStream(final File f,
149                                        final Map<String, String> props)
150        throws IOException {
151        this(f, Pack200Strategy.IN_MEMORY, props);
152    }
153
154    /**
155     * Decompresses the given file using the given strategy to cache
156     * the results and the given properties.
157     *
158     * @param f the file to decompress
159     * @param mode the strategy to use
160     * @param props Pack200 properties to use
161     * @throws IOException if reading fails
162     */
163    public Pack200CompressorInputStream(final File f, final Pack200Strategy mode,
164                                        final Map<String, String> props)
165        throws IOException {
166        this(null, f, mode, props);
167    }
168
169    private Pack200CompressorInputStream(final InputStream in, final File f,
170                                         final Pack200Strategy mode,
171                                         final Map<String, String> props)
172            throws IOException {
173        originalInput = in;
174        streamBridge = mode.newStreamBridge();
175        try (final JarOutputStream jarOut = new JarOutputStream(streamBridge)) {
176            final Pack200.Unpacker u = Pack200.newUnpacker();
177            if (props != null) {
178                u.properties().putAll(props);
179            }
180            if (f == null) {
181                // unpack would close this stream but we
182                // want to give the user code more control
183                u.unpack(new CloseShieldFilterInputStream(in), jarOut);
184            } else {
185                u.unpack(f, jarOut);
186            }
187        }
188    }
189
190    @Override
191    public int read() throws IOException {
192        return streamBridge.getInput().read();
193    }
194
195    @Override
196    public int read(final byte[] b) throws IOException {
197        return streamBridge.getInput().read(b);
198    }
199
200    @Override
201    public int read(final byte[] b, final int off, final int count) throws IOException {
202        return streamBridge.getInput().read(b, off, count);
203    }
204
205    @Override
206    public int available() throws IOException {
207        return streamBridge.getInput().available();
208    }
209
210    @Override
211    public boolean markSupported() {
212        try {
213            return streamBridge.getInput().markSupported();
214        } catch (final IOException ex) { // NOSONAR
215            return false;
216        }
217    }
218
219    @Override
220    public synchronized void mark(final int limit) {
221        try {
222            streamBridge.getInput().mark(limit);
223        } catch (final IOException ex) {
224            throw new RuntimeException(ex); //NOSONAR
225        }
226    }
227
228    @Override
229    public synchronized void reset() throws IOException {
230        streamBridge.getInput().reset();
231    }
232
233    @Override
234    public long skip(final long count) throws IOException {
235        return IOUtils.skip(streamBridge.getInput(), count);
236    }
237
238    @Override
239    public void close() throws IOException {
240        try {
241            streamBridge.stop();
242        } finally {
243            if (originalInput != null) {
244                originalInput.close();
245            }
246        }
247    }
248
249    private static final byte[] CAFE_DOOD = new byte[] {
250        (byte) 0xCA, (byte) 0xFE, (byte) 0xD0, (byte) 0x0D
251    };
252    private static final int SIG_LENGTH = CAFE_DOOD.length;
253
254    /**
255     * Checks if the signature matches what is expected for a pack200
256     * file (0xCAFED00D).
257     *
258     * @param signature
259     *            the bytes to check
260     * @param length
261     *            the number of bytes to check
262     * @return true, if this stream is a pack200 compressed stream,
263     * false otherwise
264     */
265    public static boolean matches(final byte[] signature, final int length) {
266        if (length < SIG_LENGTH) {
267            return false;
268        }
269
270        for (int i = 0; i < SIG_LENGTH; i++) {
271            if (signature[i] != CAFE_DOOD[i]) {
272                return false;
273            }
274        }
275
276        return true;
277    }
278}