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 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    }