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.FilterInputStream;
024import java.io.IOException;
025import java.io.InputStream;
026import java.util.Map;
027import java.util.jar.JarOutputStream;
028import java.util.jar.Pack200;
029
030import org.apache.commons.compress.compressors.CompressorInputStream;
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                u.unpack(new FilterInputStream(in) {
182                    @Override
183                    public void close() {
184                        // unpack would close this stream but we
185                        // want to give the user code more control
186                    }
187                }, jarOut);
188            } else {
189                u.unpack(f, jarOut);
190            }
191        }
192    }
193
194    @Override
195    public int read() throws IOException {
196        return streamBridge.getInput().read();
197    }
198
199    @Override
200    public int read(final byte[] b) throws IOException {
201        return streamBridge.getInput().read(b);
202    }
203
204    @Override
205    public int read(final byte[] b, final int off, final int count) throws IOException {
206        return streamBridge.getInput().read(b, off, count);
207    }
208
209    @Override
210    public int available() throws IOException {
211        return streamBridge.getInput().available();
212    }
213
214    @Override
215    public boolean markSupported() {
216        try {
217            return streamBridge.getInput().markSupported();
218        } catch (final IOException ex) { // NOSONAR
219            return false;
220        }
221    }
222
223    @Override
224    public void mark(final int limit) {
225        try {
226            streamBridge.getInput().mark(limit);
227        } catch (final IOException ex) {
228            throw new RuntimeException(ex); //NOSONAR
229        }
230    }
231
232    @Override
233    public void reset() throws IOException {
234        streamBridge.getInput().reset();
235    }
236
237    @Override
238    public long skip(final long count) throws IOException {
239        return IOUtils.skip(streamBridge.getInput(), count);
240    }
241
242    @Override
243    public void close() throws IOException {
244        try {
245            streamBridge.stop();
246        } finally {
247            if (originalInput != null) {
248                originalInput.close();
249            }
250        }
251    }
252
253    private static final byte[] CAFE_DOOD = new byte[] {
254        (byte) 0xCA, (byte) 0xFE, (byte) 0xD0, (byte) 0x0D
255    };
256    private static final int SIG_LENGTH = CAFE_DOOD.length;
257
258    /**
259     * Checks if the signature matches what is expected for a pack200
260     * file (0xCAFED00D).
261     *
262     * @param signature
263     *            the bytes to check
264     * @param length
265     *            the number of bytes to check
266     * @return true, if this stream is a pack200 compressed stream,
267     * false otherwise
268     */
269    public static boolean matches(final byte[] signature, final int length) {
270        if (length < SIG_LENGTH) {
271            return false;
272        }
273
274        for (int i = 0; i < SIG_LENGTH; i++) {
275            if (signature[i] != CAFE_DOOD[i]) {
276                return false;
277            }
278        }
279
280        return true;
281    }
282}