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