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