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 * https://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.IOUtils; 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 unpacker.unpack(inputStream, jarOut); 142 } else { 143 unpacker.unpack(file, jarOut); 144 } 145 } 146 } 147 148 /** 149 * Decompresses the given stream, caching the decompressed data in memory and using the given properties. 150 * 151 * <p> 152 * When reading from a file the File-arg constructor may provide better performance. 153 * </p> 154 * 155 * @param inputStream the InputStream from which this object should be created 156 * @param properties Pack200 properties to use 157 * @throws IOException if reading fails 158 */ 159 public Pack200CompressorInputStream(final InputStream inputStream, final Map<String, String> properties) throws IOException { 160 this(inputStream, Pack200Strategy.IN_MEMORY, properties); 161 } 162 163 /** 164 * Decompresses the given stream using the given strategy to cache the results. 165 * 166 * <p> 167 * When reading from a file the File-arg constructor may provide better performance. 168 * </p> 169 * 170 * @param inputStream the InputStream from which this object should be created 171 * @param mode the strategy to use 172 * @throws IOException if reading fails 173 */ 174 public Pack200CompressorInputStream(final InputStream inputStream, final Pack200Strategy mode) throws IOException { 175 this(inputStream, null, mode, null); 176 } 177 178 /** 179 * Decompresses the given stream using the given strategy to cache the results and the given properties. 180 * 181 * <p> 182 * When reading from a file the File-arg constructor may provide better performance. 183 * </p> 184 * 185 * @param inputStream the InputStream from which this object should be created 186 * @param mode the strategy to use 187 * @param properties Pack200 properties to use 188 * @throws IOException if reading fails 189 */ 190 public Pack200CompressorInputStream(final InputStream inputStream, final Pack200Strategy mode, final Map<String, String> properties) throws IOException { 191 this(inputStream, null, mode, properties); 192 } 193 194 @SuppressWarnings("resource") // Does not allocate 195 @Override 196 public int available() throws IOException { 197 return getInputStream().available(); 198 } 199 200 @Override 201 public void close() throws IOException { 202 try { 203 abstractStreamBridge.stop(); 204 } finally { 205 IOUtils.close(originalInputStream); 206 } 207 } 208 209 private InputStream getInputStream() throws IOException { 210 return abstractStreamBridge.getInputStream(); 211 } 212 213 @SuppressWarnings("resource") // Does not allocate 214 @Override 215 public synchronized void mark(final int limit) { 216 try { 217 getInputStream().mark(limit); 218 } catch (final IOException ex) { 219 throw new UncheckedIOException(ex); // NOSONAR 220 } 221 } 222 223 @SuppressWarnings("resource") // Does not allocate 224 @Override 225 public boolean markSupported() { 226 try { 227 return getInputStream().markSupported(); 228 } catch (final IOException ex) { // NOSONAR 229 return false; 230 } 231 } 232 233 @SuppressWarnings("resource") // Does not allocate 234 @Override 235 public int read() throws IOException { 236 return getInputStream().read(); 237 } 238 239 @SuppressWarnings("resource") // Does not allocate 240 @Override 241 public int read(final byte[] b) throws IOException { 242 return getInputStream().read(b); 243 } 244 245 @SuppressWarnings("resource") // Does not allocate 246 @Override 247 public int read(final byte[] b, final int off, final int count) throws IOException { 248 return getInputStream().read(b, off, count); 249 } 250 251 @SuppressWarnings("resource") // Does not allocate 252 @Override 253 public synchronized void reset() throws IOException { 254 getInputStream().reset(); 255 } 256 257 @SuppressWarnings("resource") // Does not allocate 258 @Override 259 public long skip(final long count) throws IOException { 260 return IOUtils.skip(getInputStream(), count); 261 } 262}