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.io.UncheckedIOException;
026import java.util.Map;
027import java.util.jar.JarOutputStream;
028
029import org.apache.commons.compress.compressors.CompressorInputStream;
030import org.apache.commons.compress.java.util.jar.Pack200;
031import org.apache.commons.compress.utils.CloseShieldFilterInputStream;
032import org.apache.commons.compress.utils.IOUtils;
033
034/**
035 * An input stream that decompresses from the Pack200 format to be read
036 * as any other stream.
037 *
038 * <p>The {@link CompressorInputStream#getCount getCount} and {@link
039 * CompressorInputStream#getBytesRead getBytesRead} methods always
040 * return 0.</p>
041 *
042 * @NotThreadSafe
043 * @since 1.3
044 */
045public class Pack200CompressorInputStream extends CompressorInputStream {
046    private final InputStream originalInput;
047    private final StreamBridge streamBridge;
048
049    /**
050     * Decompresses the given stream, caching the decompressed data in
051     * memory.
052     *
053     * <p>When reading from a file the File-arg constructor may
054     * provide better performance.</p>
055     *
056     * @param in the InputStream from which this object should be created
057     * @throws IOException if reading fails
058     */
059    public Pack200CompressorInputStream(final InputStream in)
060        throws IOException {
061        this(in, Pack200Strategy.IN_MEMORY);
062    }
063
064    /**
065     * Decompresses the given stream using the given strategy to cache
066     * the results.
067     *
068     * <p>When reading from a file the File-arg constructor may
069     * provide better performance.</p>
070     *
071     * @param in the InputStream from which this object should be created
072     * @param mode the strategy to use
073     * @throws IOException if reading fails
074     */
075    public Pack200CompressorInputStream(final InputStream in,
076                                        final Pack200Strategy mode)
077        throws IOException {
078        this(in, null, mode, null);
079    }
080
081    /**
082     * Decompresses the given stream, caching the decompressed data in
083     * memory and using the given properties.
084     *
085     * <p>When reading from a file the File-arg constructor may
086     * provide better performance.</p>
087     *
088     * @param in the InputStream from which this object should be created
089     * @param props Pack200 properties to use
090     * @throws IOException if reading fails
091     */
092    public Pack200CompressorInputStream(final InputStream in,
093                                        final Map<String, String> props)
094        throws IOException {
095        this(in, Pack200Strategy.IN_MEMORY, props);
096    }
097
098    /**
099     * Decompresses the given stream using the given strategy to cache
100     * the results and the given properties.
101     *
102     * <p>When reading from a file the File-arg constructor may
103     * provide better performance.</p>
104     *
105     * @param in the InputStream from which this object should be created
106     * @param mode the strategy to use
107     * @param props Pack200 properties to use
108     * @throws IOException if reading fails
109     */
110    public Pack200CompressorInputStream(final InputStream in,
111                                        final Pack200Strategy mode,
112                                        final Map<String, String> props)
113        throws IOException {
114        this(in, null, mode, props);
115    }
116
117    /**
118     * Decompresses the given file, caching the decompressed data in
119     * memory.
120     *
121     * @param f the file to decompress
122     * @throws IOException if reading fails
123     */
124    public Pack200CompressorInputStream(final File f) throws IOException {
125        this(f, Pack200Strategy.IN_MEMORY);
126    }
127
128    /**
129     * Decompresses the given file using the given strategy to cache
130     * the results.
131     *
132     * @param f the file to decompress
133     * @param mode the strategy to use
134     * @throws IOException if reading fails
135     */
136    public Pack200CompressorInputStream(final File f, final Pack200Strategy mode)
137        throws IOException {
138        this(null, f, mode, null);
139    }
140
141    /**
142     * Decompresses the given file, caching the decompressed data in
143     * memory and using the given properties.
144     *
145     * @param f the file to decompress
146     * @param props Pack200 properties to use
147     * @throws IOException if reading fails
148     */
149    public Pack200CompressorInputStream(final File f,
150                                        final Map<String, String> props)
151        throws IOException {
152        this(f, Pack200Strategy.IN_MEMORY, props);
153    }
154
155    /**
156     * Decompresses the given file using the given strategy to cache
157     * the results and the given properties.
158     *
159     * @param f the file to decompress
160     * @param mode the strategy to use
161     * @param props Pack200 properties to use
162     * @throws IOException if reading fails
163     */
164    public Pack200CompressorInputStream(final File f, final Pack200Strategy mode,
165                                        final Map<String, String> props)
166        throws IOException {
167        this(null, f, mode, props);
168    }
169
170    private Pack200CompressorInputStream(final InputStream in, final File f,
171                                         final Pack200Strategy mode,
172                                         final Map<String, String> props)
173            throws IOException {
174        originalInput = in;
175        streamBridge = mode.newStreamBridge();
176        try (final JarOutputStream jarOut = new JarOutputStream(streamBridge)) {
177            final Pack200.Unpacker u = Pack200.newUnpacker();
178            if (props != null) {
179                u.properties().putAll(props);
180            }
181            if (f == null) {
182                // unpack would close this stream but we want to give the call site more control
183                // TODO unpack should not close its given stream.
184                try (final CloseShieldFilterInputStream closeShield = new CloseShieldFilterInputStream(in)) {
185                    u.unpack(closeShield, jarOut);
186                }
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) { // NOSONAR
218            return false;
219        }
220    }
221
222    @Override
223    public synchronized void mark(final int limit) {
224        try {
225            streamBridge.getInput().mark(limit);
226        } catch (final IOException ex) {
227            throw new UncheckedIOException(ex); //NOSONAR
228        }
229    }
230
231    @Override
232    public synchronized void reset() throws IOException {
233        streamBridge.getInput().reset();
234    }
235
236    @Override
237    public long skip(final long count) throws IOException {
238        return IOUtils.skip(streamBridge.getInput(), 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}