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