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;
031
032/**
033 * An input stream that decompresses from the Pack200 format to be read
034 * as any other stream.
035 *
036 * <p>The {@link CompressorInputStream#getCount getCount} and {@link
037 * CompressorInputStream#getBytesRead getBytesRead} methods always
038 * return 0.</p>
039 *
040 * @NotThreadSafe
041 * @since 1.3
042 */
043public class Pack200CompressorInputStream extends CompressorInputStream {
044    private final InputStream originalInput;
045    private final StreamBridge streamBridge;
046
047    /**
048     * Decompresses the given stream, caching the decompressed data in
049     * memory.
050     *
051     * <p>When reading from a file the File-arg constructor may
052     * provide better performance.</p>
053     *
054     * @param in the InputStream from which this object should be created
055     * @throws IOException if reading fails
056     */
057    public Pack200CompressorInputStream(final InputStream in)
058        throws IOException {
059        this(in, Pack200Strategy.IN_MEMORY);
060    }
061
062    /**
063     * Decompresses the given stream using the given strategy to cache
064     * the results.
065     *
066     * <p>When reading from a file the File-arg constructor may
067     * provide better performance.</p>
068     *
069     * @param in the InputStream from which this object should be created
070     * @param mode the strategy to use
071     * @throws IOException if reading fails
072     */
073    public Pack200CompressorInputStream(final InputStream in,
074                                        final Pack200Strategy mode)
075        throws IOException {
076        this(in, null, mode, null);
077    }
078
079    /**
080     * Decompresses the given stream, caching the decompressed data in
081     * memory and using the given properties.
082     *
083     * <p>When reading from a file the File-arg constructor may
084     * provide better performance.</p>
085     *
086     * @param in the InputStream from which this object should be created
087     * @param props Pack200 properties to use
088     * @throws IOException if reading fails
089     */
090    public Pack200CompressorInputStream(final InputStream in,
091                                        final Map<String, String> props)
092        throws IOException {
093        this(in, Pack200Strategy.IN_MEMORY, props);
094    }
095
096    /**
097     * Decompresses the given stream using the given strategy to cache
098     * the results and the given properties.
099     *
100     * <p>When reading from a file the File-arg constructor may
101     * provide better performance.</p>
102     *
103     * @param in the InputStream from which this object should be created
104     * @param mode the strategy to use
105     * @param props Pack200 properties to use
106     * @throws IOException if reading fails
107     */
108    public Pack200CompressorInputStream(final InputStream in,
109                                        final Pack200Strategy mode,
110                                        final Map<String, String> props)
111        throws IOException {
112        this(in, null, mode, props);
113    }
114
115    /**
116     * Decompresses the given file, caching the decompressed data in
117     * memory.
118     *
119     * @param f the file to decompress
120     * @throws IOException if reading fails
121     */
122    public Pack200CompressorInputStream(final File f) throws IOException {
123        this(f, Pack200Strategy.IN_MEMORY);
124    }
125
126    /**
127     * Decompresses the given file using the given strategy to cache
128     * the results.
129     *
130     * @param f the file to decompress
131     * @param mode the strategy to use
132     * @throws IOException if reading fails
133     */
134    public Pack200CompressorInputStream(final File f, final Pack200Strategy mode)
135        throws IOException {
136        this(null, f, mode, null);
137    }
138
139    /**
140     * Decompresses the given file, caching the decompressed data in
141     * memory and using the given properties.
142     *
143     * @param f the file to decompress
144     * @param props Pack200 properties to use
145     * @throws IOException if reading fails
146     */
147    public Pack200CompressorInputStream(final File f,
148                                        final Map<String, String> props)
149        throws IOException {
150        this(f, Pack200Strategy.IN_MEMORY, props);
151    }
152
153    /**
154     * Decompresses the given file using the given strategy to cache
155     * the results and the given properties.
156     *
157     * @param f the file to decompress
158     * @param mode the strategy to use
159     * @param props Pack200 properties to use
160     * @throws IOException if reading fails
161     */
162    public Pack200CompressorInputStream(final File f, final Pack200Strategy mode,
163                                        final Map<String, String> props)
164        throws IOException {
165        this(null, f, mode, props);
166    }
167
168    private Pack200CompressorInputStream(final InputStream in, final File f,
169                                         final Pack200Strategy mode,
170                                         final Map<String, String> props)
171            throws IOException {
172        originalInput = in;
173        streamBridge = mode.newStreamBridge();
174        try (final JarOutputStream jarOut = new JarOutputStream(streamBridge)) {
175            final Pack200.Unpacker u = Pack200.newUnpacker();
176            if (props != null) {
177                u.properties().putAll(props);
178            }
179            if (f == null) {
180                u.unpack(new FilterInputStream(in) {
181                    @Override
182                    public void close() {
183                        // unpack would close this stream but we
184                        // want to give the user code more control
185                    }
186                }, jarOut);
187            } else {
188                u.unpack(f, jarOut);
189            }
190        }
191    }
192
193    @Override
194    public int read() throws IOException {
195        return streamBridge.getInput().read();
196    }
197
198    @Override
199    public int read(final byte[] b) throws IOException {
200        return streamBridge.getInput().read(b);
201    }
202
203    @Override
204    public int read(final byte[] b, final int off, final int count) throws IOException {
205        return streamBridge.getInput().read(b, off, count);
206    }
207
208    @Override
209    public int available() throws IOException {
210        return streamBridge.getInput().available();
211    }
212
213    @Override
214    public boolean markSupported() {
215        try {
216            return streamBridge.getInput().markSupported();
217        } catch (final IOException ex) {
218            return false;
219        }
220    }
221
222    @Override
223    public void mark(final int limit) {
224        try {
225            streamBridge.getInput().mark(limit);
226        } catch (final IOException ex) {
227            throw new RuntimeException(ex); //NOSONAR
228        }
229    }
230
231    @Override
232    public void reset() throws IOException {
233        streamBridge.getInput().reset();
234    }
235
236    @Override
237    public long skip(final long count) throws IOException {
238        return streamBridge.getInput().skip(count);
239    }
240
241    @Override
242    public void close() throws IOException {
243        try {
244            streamBridge.stop();
245        } finally {
246            if (originalInput != null) {
247                originalInput.close();
248            }
249        }
250    }
251
252    private static final byte[] CAFE_DOOD = new byte[] {
253        (byte) 0xCA, (byte) 0xFE, (byte) 0xD0, (byte) 0x0D
254    };
255    private static final int SIG_LENGTH = CAFE_DOOD.length;
256
257    /**
258     * Checks if the signature matches what is expected for a pack200
259     * file (0xCAFED00D).
260     *
261     * @param signature
262     *            the bytes to check
263     * @param length
264     *            the number of bytes to check
265     * @return true, if this stream is a pack200 compressed stream,
266     * false otherwise
267     */
268    public static boolean matches(final byte[] signature, final int length) {
269        if (length < SIG_LENGTH) {
270            return false;
271        }
272
273        for (int i = 0; i < SIG_LENGTH; i++) {
274            if (signature[i] != CAFE_DOOD[i]) {
275                return false;
276            }
277        }
278
279        return true;
280    }
281}