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