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