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