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