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 */ 019package org.apache.commons.compress.compressors; 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.OutputStream; 024 025import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; 026import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream; 027import org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream; 028import org.apache.commons.compress.compressors.deflate.DeflateCompressorOutputStream; 029import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; 030import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; 031import org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream; 032import org.apache.commons.compress.compressors.lzma.LZMAUtils; 033import org.apache.commons.compress.compressors.xz.XZCompressorInputStream; 034import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream; 035import org.apache.commons.compress.compressors.xz.XZUtils; 036import org.apache.commons.compress.compressors.pack200.Pack200CompressorInputStream; 037import org.apache.commons.compress.compressors.pack200.Pack200CompressorOutputStream; 038import org.apache.commons.compress.compressors.snappy.FramedSnappyCompressorInputStream; 039import org.apache.commons.compress.compressors.snappy.SnappyCompressorInputStream; 040import org.apache.commons.compress.compressors.z.ZCompressorInputStream; 041import org.apache.commons.compress.utils.IOUtils; 042 043/** 044 * <p>Factory to create Compressor[In|Out]putStreams from names. To add other 045 * implementations you should extend CompressorStreamFactory and override the 046 * appropriate methods (and call their implementation from super of course).</p> 047 * 048 * Example (Compressing a file): 049 * 050 * <pre> 051 * final OutputStream out = new FileOutputStream(output); 052 * CompressorOutputStream cos = 053 * new CompressorStreamFactory().createCompressorOutputStream(CompressorStreamFactory.BZIP2, out); 054 * IOUtils.copy(new FileInputStream(input), cos); 055 * cos.close(); 056 * </pre> 057 * 058 * Example (Decompressing a file): 059 * <pre> 060 * final InputStream is = new FileInputStream(input); 061 * CompressorInputStream in = 062 * new CompressorStreamFactory().createCompressorInputStream(CompressorStreamFactory.BZIP2, is); 063 * IOUtils.copy(in, new FileOutputStream(output)); 064 * in.close(); 065 * </pre> 066 * @Immutable provided that the deprecated method setDecompressConcatenated is not used. 067 * @ThreadSafe even if the deprecated method setDecompressConcatenated is used 068 */ 069public class CompressorStreamFactory { 070 071 /** 072 * Constant (value {@value}) used to identify the BZIP2 compression algorithm. 073 * @since 1.1 074 */ 075 public static final String BZIP2 = "bzip2"; 076 077 /** 078 * Constant (value {@value}) used to identify the GZIP compression algorithm. 079 * Not supported as an output stream type. 080 * @since 1.1 081 */ 082 public static final String GZIP = "gz"; 083 084 /** 085 * Constant (value {@value}) used to identify the PACK200 compression algorithm. 086 * @since 1.3 087 */ 088 public static final String PACK200 = "pack200"; 089 090 /** 091 * Constant (value {@value}) used to identify the XZ compression method. 092 * @since 1.4 093 */ 094 public static final String XZ = "xz"; 095 096 /** 097 * Constant (value {@value}) used to identify the LZMA compression method. 098 * Not supported as an output stream type. 099 * @since 1.6 100 */ 101 public static final String LZMA = "lzma"; 102 103 /** 104 * Constant (value {@value}) used to identify the "framed" Snappy compression method. 105 * Not supported as an output stream type. 106 * @since 1.7 107 */ 108 public static final String SNAPPY_FRAMED = "snappy-framed"; 109 110 /** 111 * Constant (value {@value}) used to identify the "raw" Snappy compression method. 112 * Not supported as an output stream type. 113 * @since 1.7 114 */ 115 public static final String SNAPPY_RAW = "snappy-raw"; 116 117 /** 118 * Constant (value {@value}) used to identify the traditional Unix compress method. 119 * Not supported as an output stream type. 120 * @since 1.7 121 */ 122 public static final String Z = "z"; 123 124 /** 125 * Constant (value {@value}) used to identify the Deflate compress method. 126 * @since 1.9 127 */ 128 public static final String DEFLATE = "deflate"; 129 130 /** 131 * If true, decompress until the end of the input. 132 * If false, stop after the first stream and leave the 133 * input position to point to the next byte after the stream 134 */ 135 private final Boolean decompressUntilEOF; 136 // This is Boolean so setDecompressConcatenated can determine whether it has been set by the ctor 137 // once the setDecompressConcatenated method has been removed, it can revert to boolean 138 139 /** 140 * If true, decompress until the end of the input. 141 * If false, stop after the first stream and leave the 142 * input position to point to the next byte after the stream 143 */ 144 private volatile boolean decompressConcatenated = false; 145 146 /** 147 * Create an instance with the decompress Concatenated option set to false. 148 */ 149 public CompressorStreamFactory() { 150 this.decompressUntilEOF = null; 151 } 152 153 /** 154 * Create an instance with the provided decompress Concatenated option. 155 * @param decompressUntilEOF 156 * if true, decompress until the end of the 157 * input; if false, stop after the first 158 * stream and leave the input position to point 159 * to the next byte after the stream. 160 * This setting applies to the gzip, bzip2 and xz formats only. 161 * @since 1.10 162 */ 163 public CompressorStreamFactory(final boolean decompressUntilEOF) { 164 this.decompressUntilEOF = Boolean.valueOf(decompressUntilEOF); 165 // Also copy to existing variable so can continue to use that as the current value 166 this.decompressConcatenated = decompressUntilEOF; 167 } 168 169 /** 170 * Whether to decompress the full input or only the first stream 171 * in formats supporting multiple concatenated input streams. 172 * 173 * <p>This setting applies to the gzip, bzip2 and xz formats only.</p> 174 * 175 * @param decompressConcatenated 176 * if true, decompress until the end of the 177 * input; if false, stop after the first 178 * stream and leave the input position to point 179 * to the next byte after the stream 180 * @since 1.5 181 * @deprecated 1.10 use the {@link #CompressorStreamFactory(boolean)} constructor instead 182 * @throws IllegalStateException if the constructor {@link #CompressorStreamFactory(boolean)} 183 * was used to create the factory 184 */ 185 @Deprecated 186 public void setDecompressConcatenated(final boolean decompressConcatenated) { 187 if (this.decompressUntilEOF != null) { 188 throw new IllegalStateException("Cannot override the setting defined by the constructor"); 189 } 190 this.decompressConcatenated = decompressConcatenated; 191 } 192 193 /** 194 * Create an compressor input stream from an input stream, autodetecting 195 * the compressor type from the first few bytes of the stream. The InputStream 196 * must support marks, like BufferedInputStream. 197 * 198 * @param in the input stream 199 * @return the compressor input stream 200 * @throws CompressorException if the compressor name is not known 201 * @throws IllegalArgumentException if the stream is null or does not support mark 202 * @since 1.1 203 */ 204 public CompressorInputStream createCompressorInputStream(final InputStream in) 205 throws CompressorException { 206 if (in == null) { 207 throw new IllegalArgumentException("Stream must not be null."); 208 } 209 210 if (!in.markSupported()) { 211 throw new IllegalArgumentException("Mark is not supported."); 212 } 213 214 final byte[] signature = new byte[12]; 215 in.mark(signature.length); 216 try { 217 final int signatureLength = IOUtils.readFully(in, signature); 218 in.reset(); 219 220 if (BZip2CompressorInputStream.matches(signature, signatureLength)) { 221 return new BZip2CompressorInputStream(in, decompressConcatenated); 222 } 223 224 if (GzipCompressorInputStream.matches(signature, signatureLength)) { 225 return new GzipCompressorInputStream(in, decompressConcatenated); 226 } 227 228 if (Pack200CompressorInputStream.matches(signature, signatureLength)) { 229 return new Pack200CompressorInputStream(in); 230 } 231 232 if (FramedSnappyCompressorInputStream.matches(signature, signatureLength)) { 233 return new FramedSnappyCompressorInputStream(in); 234 } 235 236 if (ZCompressorInputStream.matches(signature, signatureLength)) { 237 return new ZCompressorInputStream(in); 238 } 239 240 if (DeflateCompressorInputStream.matches(signature, signatureLength)) { 241 return new DeflateCompressorInputStream(in); 242 } 243 244 if (XZUtils.matches(signature, signatureLength) && 245 XZUtils.isXZCompressionAvailable()) { 246 return new XZCompressorInputStream(in, decompressConcatenated); 247 } 248 249 if (LZMAUtils.matches(signature, signatureLength) && 250 LZMAUtils.isLZMACompressionAvailable()) { 251 return new LZMACompressorInputStream(in); 252 } 253 254 } catch (final IOException e) { 255 throw new CompressorException("Failed to detect Compressor from InputStream.", e); 256 } 257 258 throw new CompressorException("No Compressor found for the stream signature."); 259 } 260 261 /** 262 * Create a compressor input stream from a compressor name and an input stream. 263 * 264 * @param name of the compressor, 265 * i.e. {@value #GZIP}, {@value #BZIP2}, {@value #XZ}, {@value #LZMA}, 266 * {@value #PACK200}, {@value #SNAPPY_RAW}, {@value #SNAPPY_FRAMED}, 267 * {@value #Z} or {@value #DEFLATE} 268 * @param in the input stream 269 * @return compressor input stream 270 * @throws CompressorException if the compressor name is not known 271 * @throws IllegalArgumentException if the name or input stream is null 272 */ 273 public CompressorInputStream createCompressorInputStream(final String name, 274 final InputStream in) throws CompressorException { 275 if (name == null || in == null) { 276 throw new IllegalArgumentException( 277 "Compressor name and stream must not be null."); 278 } 279 280 try { 281 282 if (GZIP.equalsIgnoreCase(name)) { 283 return new GzipCompressorInputStream(in, decompressConcatenated); 284 } 285 286 if (BZIP2.equalsIgnoreCase(name)) { 287 return new BZip2CompressorInputStream(in, decompressConcatenated); 288 } 289 290 if (XZ.equalsIgnoreCase(name)) { 291 return new XZCompressorInputStream(in, decompressConcatenated); 292 } 293 294 if (LZMA.equalsIgnoreCase(name)) { 295 return new LZMACompressorInputStream(in); 296 } 297 298 if (PACK200.equalsIgnoreCase(name)) { 299 return new Pack200CompressorInputStream(in); 300 } 301 302 if (SNAPPY_RAW.equalsIgnoreCase(name)) { 303 return new SnappyCompressorInputStream(in); 304 } 305 306 if (SNAPPY_FRAMED.equalsIgnoreCase(name)) { 307 return new FramedSnappyCompressorInputStream(in); 308 } 309 310 if (Z.equalsIgnoreCase(name)) { 311 return new ZCompressorInputStream(in); 312 } 313 314 if (DEFLATE.equalsIgnoreCase(name)) { 315 return new DeflateCompressorInputStream(in); 316 } 317 318 } catch (final IOException e) { 319 throw new CompressorException( 320 "Could not create CompressorInputStream.", e); 321 } 322 throw new CompressorException("Compressor: " + name + " not found."); 323 } 324 325 /** 326 * Create an compressor output stream from an compressor name and an output stream. 327 * 328 * @param name the compressor name, 329 * i.e. {@value #GZIP}, {@value #BZIP2}, {@value #XZ}, 330 * {@value #PACK200} or {@value #DEFLATE} 331 * @param out the output stream 332 * @return the compressor output stream 333 * @throws CompressorException if the archiver name is not known 334 * @throws IllegalArgumentException if the archiver name or stream is null 335 */ 336 public CompressorOutputStream createCompressorOutputStream( 337 final String name, final OutputStream out) 338 throws CompressorException { 339 if (name == null || out == null) { 340 throw new IllegalArgumentException( 341 "Compressor name and stream must not be null."); 342 } 343 344 try { 345 346 if (GZIP.equalsIgnoreCase(name)) { 347 return new GzipCompressorOutputStream(out); 348 } 349 350 if (BZIP2.equalsIgnoreCase(name)) { 351 return new BZip2CompressorOutputStream(out); 352 } 353 354 if (XZ.equalsIgnoreCase(name)) { 355 return new XZCompressorOutputStream(out); 356 } 357 358 if (PACK200.equalsIgnoreCase(name)) { 359 return new Pack200CompressorOutputStream(out); 360 } 361 362 if (DEFLATE.equalsIgnoreCase(name)) { 363 return new DeflateCompressorOutputStream(out); 364 } 365 366 } catch (final IOException e) { 367 throw new CompressorException( 368 "Could not create CompressorOutputStream", e); 369 } 370 throw new CompressorException("Compressor: " + name + " not found."); 371 } 372 373 // For Unit tests 374 boolean getDecompressConcatenated() { 375 return decompressConcatenated; 376 } 377}