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