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