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 }