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