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