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