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