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 final InputStream originalInput; 047 private final StreamBridge streamBridge; 048 049 /** 050 * Decompresses the given stream, caching the decompressed data in 051 * memory. 052 * 053 * <p>When reading from a file the File-arg constructor may 054 * provide better performance.</p> 055 * 056 * @param in the InputStream from which this object should be created 057 * @throws IOException if reading fails 058 */ 059 public Pack200CompressorInputStream(final InputStream in) 060 throws IOException { 061 this(in, Pack200Strategy.IN_MEMORY); 062 } 063 064 /** 065 * Decompresses the given stream using the given strategy to cache 066 * the results. 067 * 068 * <p>When reading from a file the File-arg constructor may 069 * provide better performance.</p> 070 * 071 * @param in the InputStream from which this object should be created 072 * @param mode the strategy to use 073 * @throws IOException if reading fails 074 */ 075 public Pack200CompressorInputStream(final InputStream in, 076 final Pack200Strategy mode) 077 throws IOException { 078 this(in, null, mode, null); 079 } 080 081 /** 082 * Decompresses the given stream, caching the decompressed data in 083 * memory and using the given properties. 084 * 085 * <p>When reading from a file the File-arg constructor may 086 * provide better performance.</p> 087 * 088 * @param in the InputStream from which this object should be created 089 * @param props Pack200 properties to use 090 * @throws IOException if reading fails 091 */ 092 public Pack200CompressorInputStream(final InputStream in, 093 final Map<String, String> props) 094 throws IOException { 095 this(in, Pack200Strategy.IN_MEMORY, props); 096 } 097 098 /** 099 * Decompresses the given stream using the given strategy to cache 100 * the results and the given properties. 101 * 102 * <p>When reading from a file the File-arg constructor may 103 * provide better performance.</p> 104 * 105 * @param in the InputStream from which this object should be created 106 * @param mode the strategy to use 107 * @param props Pack200 properties to use 108 * @throws IOException if reading fails 109 */ 110 public Pack200CompressorInputStream(final InputStream in, 111 final Pack200Strategy mode, 112 final Map<String, String> props) 113 throws IOException { 114 this(in, null, mode, props); 115 } 116 117 /** 118 * Decompresses the given file, caching the decompressed data in 119 * memory. 120 * 121 * @param f the file to decompress 122 * @throws IOException if reading fails 123 */ 124 public Pack200CompressorInputStream(final File f) throws IOException { 125 this(f, Pack200Strategy.IN_MEMORY); 126 } 127 128 /** 129 * Decompresses the given file using the given strategy to cache 130 * the results. 131 * 132 * @param f the file to decompress 133 * @param mode the strategy to use 134 * @throws IOException if reading fails 135 */ 136 public Pack200CompressorInputStream(final File f, final Pack200Strategy mode) 137 throws IOException { 138 this(null, f, mode, null); 139 } 140 141 /** 142 * Decompresses the given file, caching the decompressed data in 143 * memory and using the given properties. 144 * 145 * @param f the file to decompress 146 * @param props Pack200 properties to use 147 * @throws IOException if reading fails 148 */ 149 public Pack200CompressorInputStream(final File f, 150 final Map<String, String> props) 151 throws IOException { 152 this(f, Pack200Strategy.IN_MEMORY, props); 153 } 154 155 /** 156 * Decompresses the given file using the given strategy to cache 157 * the results and the given properties. 158 * 159 * @param f the file to decompress 160 * @param mode the strategy to use 161 * @param props Pack200 properties to use 162 * @throws IOException if reading fails 163 */ 164 public Pack200CompressorInputStream(final File f, final Pack200Strategy mode, 165 final Map<String, String> props) 166 throws IOException { 167 this(null, f, mode, props); 168 } 169 170 private Pack200CompressorInputStream(final InputStream in, final File f, 171 final Pack200Strategy mode, 172 final Map<String, String> props) 173 throws IOException { 174 originalInput = in; 175 streamBridge = mode.newStreamBridge(); 176 try (final JarOutputStream jarOut = new JarOutputStream(streamBridge)) { 177 final Pack200.Unpacker u = Pack200.newUnpacker(); 178 if (props != null) { 179 u.properties().putAll(props); 180 } 181 if (f == null) { 182 // unpack would close this stream but we want to give the call site more control 183 // TODO unpack should not close its given stream. 184 try (final CloseShieldFilterInputStream closeShield = new CloseShieldFilterInputStream(in)) { 185 u.unpack(closeShield, jarOut); 186 } 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) { // NOSONAR 218 return false; 219 } 220 } 221 222 @Override 223 public synchronized void mark(final int limit) { 224 try { 225 streamBridge.getInput().mark(limit); 226 } catch (final IOException ex) { 227 throw new UncheckedIOException(ex); //NOSONAR 228 } 229 } 230 231 @Override 232 public synchronized void reset() throws IOException { 233 streamBridge.getInput().reset(); 234 } 235 236 @Override 237 public long skip(final long count) throws IOException { 238 return IOUtils.skip(streamBridge.getInput(), 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}