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