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        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                },
187                jarOut);
188        } else {
189            u.unpack(f, jarOut);
190        }
191        jarOut.close();
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) {
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);
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 streamBridge.getInput().skip(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}