    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}