    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 *   https://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.IOUtils;
    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                unpacker.unpack(inputStream, jarOut);
    142            } else {
    143                unpacker.unpack(file, jarOut);
    144            }
    145        }
    146    }
    147
    148    /**
    149     * Decompresses the given stream, caching the decompressed data in memory and using the given properties.
    150     *
    151     * <p>
    152     * When reading from a file the File-arg constructor may provide better performance.
    153     * </p>
    154     *
    155     * @param inputStream the InputStream from which this object should be created
    156     * @param properties  Pack200 properties to use
    157     * @throws IOException if reading fails
    158     */
    159    public Pack200CompressorInputStream(final InputStream inputStream, final Map<String, String> properties) throws IOException {
    160        this(inputStream, Pack200Strategy.IN_MEMORY, properties);
    161    }
    162
    163    /**
    164     * Decompresses the given stream using the given strategy to cache the results.
    165     *
    166     * <p>
    167     * When reading from a file the File-arg constructor may provide better performance.
    168     * </p>
    169     *
    170     * @param inputStream the InputStream from which this object should be created
    171     * @param mode        the strategy to use
    172     * @throws IOException if reading fails
    173     */
    174    public Pack200CompressorInputStream(final InputStream inputStream, final Pack200Strategy mode) throws IOException {
    175        this(inputStream, null, mode, null);
    176    }
    177
    178    /**
    179     * Decompresses the given stream using the given strategy to cache the results and the given properties.
    180     *
    181     * <p>
    182     * When reading from a file the File-arg constructor may provide better performance.
    183     * </p>
    184     *
    185     * @param inputStream the InputStream from which this object should be created
    186     * @param mode        the strategy to use
    187     * @param properties  Pack200 properties to use
    188     * @throws IOException if reading fails
    189     */
    190    public Pack200CompressorInputStream(final InputStream inputStream, final Pack200Strategy mode, final Map<String, String> properties) throws IOException {
    191        this(inputStream, null, mode, properties);
    192    }
    193
    194    @SuppressWarnings("resource") // Does not allocate
    195    @Override
    196    public int available() throws IOException {
    197        return getInputStream().available();
    198    }
    199
    200    @Override
    201    public void close() throws IOException {
    202        try {
    203            abstractStreamBridge.stop();
    204        } finally {
    205            IOUtils.close(originalInputStream);
    206        }
    207    }
    208
    209    private InputStream getInputStream() throws IOException {
    210        return abstractStreamBridge.getInputStream();
    211    }
    212
    213    @SuppressWarnings("resource") // Does not allocate
    214    @Override
    215    public synchronized void mark(final int limit) {
    216        try {
    217            getInputStream().mark(limit);
    218        } catch (final IOException ex) {
    219            throw new UncheckedIOException(ex); // NOSONAR
    220        }
    221    }
    222
    223    @SuppressWarnings("resource") // Does not allocate
    224    @Override
    225    public boolean markSupported() {
    226        try {
    227            return getInputStream().markSupported();
    228        } catch (final IOException ex) { // NOSONAR
    229            return false;
    230        }
    231    }
    232
    233    @SuppressWarnings("resource") // Does not allocate
    234    @Override
    235    public int read() throws IOException {
    236        return getInputStream().read();
    237    }
    238
    239    @SuppressWarnings("resource") // Does not allocate
    240    @Override
    241    public int read(final byte[] b) throws IOException {
    242        return getInputStream().read(b);
    243    }
    244
    245    @SuppressWarnings("resource") // Does not allocate
    246    @Override
    247    public int read(final byte[] b, final int off, final int count) throws IOException {
    248        return getInputStream().read(b, off, count);
    249    }
    250
    251    @SuppressWarnings("resource") // Does not allocate
    252    @Override
    253    public synchronized void reset() throws IOException {
    254        getInputStream().reset();
    255    }
    256
    257    @SuppressWarnings("resource") // Does not allocate
    258    @Override
    259    public long skip(final long count) throws IOException {
    260        return IOUtils.skip(getInputStream(), count);
    261    }
    262}