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