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