    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        try (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                }, jarOut);
    187            } else {
    188                u.unpack(f, jarOut);
    189            }
    190        }
    191    }
    192
    193    @Override
    194    public int read() throws IOException {
    195        return streamBridge.getInput().read();
    196    }
    197
    198    @Override
    199    public int read(final byte[] b) throws IOException {
    200        return streamBridge.getInput().read(b);
    201    }
    202
    203    @Override
    204    public int read(final byte[] b, final int off, final int count) throws IOException {
    205        return streamBridge.getInput().read(b, off, count);
    206    }
    207
    208    @Override
    209    public int available() throws IOException {
    210        return streamBridge.getInput().available();
    211    }
    212
    213    @Override
    214    public boolean markSupported() {
    215        try {
    216            return streamBridge.getInput().markSupported();
    217        } catch (final IOException ex) {
    218            return false;
    219        }
    220    }
    221
    222    @Override
    223    public void mark(final int limit) {
    224        try {
    225            streamBridge.getInput().mark(limit);
    226        } catch (final IOException ex) {
    227            throw new RuntimeException(ex); //NOSONAR
    228        }
    229    }
    230
    231    @Override
    232    public void reset() throws IOException {
    233        streamBridge.getInput().reset();
    234    }
    235
    236    @Override
    237    public long skip(final long count) throws IOException {
    238        return streamBridge.getInput().skip(count);
    239    }
    240
    241    @Override
    242    public void close() throws IOException {
    243        try {
    244            streamBridge.stop();
    245        } finally {
    246            if (originalInput != null) {
    247                originalInput.close();
    248            }
    249        }
    250    }
    251
    252    private static final byte[] CAFE_DOOD = new byte[] {
    253        (byte) 0xCA, (byte) 0xFE, (byte) 0xD0, (byte) 0x0D
    254    };
    255    private static final int SIG_LENGTH = CAFE_DOOD.length;
    256
    257    /**
    258     * Checks if the signature matches what is expected for a pack200
    259     * file (0xCAFED00D).
    260     *
    261     * @param signature
    262     *            the bytes to check
    263     * @param length
    264     *            the number of bytes to check
    265     * @return true, if this stream is a pack200 compressed stream,
    266     * false otherwise
    267     */
    268    public static boolean matches(final byte[] signature, final int length) {
    269        if (length < SIG_LENGTH) {
    270            return false;
    271        }
    272
    273        for (int i = 0; i < SIG_LENGTH; i++) {
    274            if (signature[i] != CAFE_DOOD[i]) {
    275                return false;
    276            }
    277        }
    278
    279        return true;
    280    }
    281}