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