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; 024import java.security.AccessController; 025import java.security.PrivilegedAction; 026import java.util.ArrayList; 027import java.util.Collections; 028import java.util.Iterator; 029import java.util.Locale; 030import java.util.Set; 031import java.util.SortedMap; 032import java.util.TreeMap; 033 034import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; 035import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream; 036import org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream; 037import org.apache.commons.compress.compressors.deflate.DeflateCompressorOutputStream; 038import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; 039import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; 040import org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream; 041import org.apache.commons.compress.compressors.lzma.LZMACompressorOutputStream; 042import org.apache.commons.compress.compressors.lzma.LZMAUtils; 043import org.apache.commons.compress.compressors.pack200.Pack200CompressorInputStream; 044import org.apache.commons.compress.compressors.pack200.Pack200CompressorOutputStream; 045import org.apache.commons.compress.compressors.snappy.FramedSnappyCompressorInputStream; 046import org.apache.commons.compress.compressors.snappy.SnappyCompressorInputStream; 047import org.apache.commons.compress.compressors.xz.XZCompressorInputStream; 048import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream; 049import org.apache.commons.compress.compressors.xz.XZUtils; 050import org.apache.commons.compress.compressors.z.ZCompressorInputStream; 051import org.apache.commons.compress.utils.IOUtils; 052import org.apache.commons.compress.utils.Lists; 053import org.apache.commons.compress.utils.ServiceLoaderIterator; 054import org.apache.commons.compress.utils.Sets; 055 056/** 057 * <p> 058 * Factory to create Compressor[In|Out]putStreams from names. To add other 059 * implementations you should extend CompressorStreamFactory and override the 060 * appropriate methods (and call their implementation from super of course). 061 * </p> 062 * 063 * Example (Compressing a file): 064 * 065 * <pre> 066 * final OutputStream out = new FileOutputStream(output); 067 * CompressorOutputStream cos = new CompressorStreamFactory() 068 * .createCompressorOutputStream(CompressorStreamFactory.BZIP2, out); 069 * IOUtils.copy(new FileInputStream(input), cos); 070 * cos.close(); 071 * </pre> 072 * 073 * Example (Decompressing a file): 074 * 075 * <pre> 076 * final InputStream is = new FileInputStream(input); 077 * CompressorInputStream in = new CompressorStreamFactory().createCompressorInputStream(CompressorStreamFactory.BZIP2, 078 * is); 079 * IOUtils.copy(in, new FileOutputStream(output)); 080 * in.close(); 081 * </pre> 082 * 083 * @Immutable provided that the deprecated method setDecompressConcatenated is 084 * not used. 085 * @ThreadSafe even if the deprecated method setDecompressConcatenated is used 086 */ 087public class CompressorStreamFactory implements CompressorStreamProvider { 088 089 private static final CompressorStreamFactory SINGLETON = new CompressorStreamFactory(); 090 091 /** 092 * Constant (value {@value}) used to identify the BZIP2 compression 093 * algorithm. 094 * 095 * @since 1.1 096 */ 097 public static final String BZIP2 = "bzip2"; 098 099 /** 100 * Constant (value {@value}) used to identify the GZIP compression 101 * algorithm. Not supported as an output stream type. 102 * 103 * @since 1.1 104 */ 105 public static final String GZIP = "gz"; 106 107 /** 108 * Constant (value {@value}) used to identify the PACK200 compression 109 * algorithm. 110 * 111 * @since 1.3 112 */ 113 public static final String PACK200 = "pack200"; 114 115 /** 116 * Constant (value {@value}) used to identify the XZ compression method. 117 * 118 * @since 1.4 119 */ 120 public static final String XZ = "xz"; 121 122 /** 123 * Constant (value {@value}) used to identify the LZMA compression method. 124 * Not supported as an output stream type. 125 * 126 * @since 1.6 127 */ 128 public static final String LZMA = "lzma"; 129 130 /** 131 * Constant (value {@value}) used to identify the "framed" Snappy 132 * compression method. Not supported as an output stream type. 133 * 134 * @since 1.7 135 */ 136 public static final String SNAPPY_FRAMED = "snappy-framed"; 137 138 /** 139 * Constant (value {@value}) used to identify the "raw" Snappy compression 140 * method. Not supported as an output stream type. 141 * 142 * @since 1.7 143 */ 144 public static final String SNAPPY_RAW = "snappy-raw"; 145 146 /** 147 * Constant (value {@value}) used to identify the traditional Unix compress 148 * method. Not supported as an output stream type. 149 * 150 * @since 1.7 151 */ 152 public static final String Z = "z"; 153 154 /** 155 * Constant (value {@value}) used to identify the Deflate compress method. 156 * 157 * @since 1.9 158 */ 159 public static final String DEFLATE = "deflate"; 160 161 /** 162 * Constructs a new sorted map from input stream provider names to provider 163 * objects. 164 * 165 * <p> 166 * The map returned by this method will have one entry for each provider for 167 * which support is available in the current Java virtual machine. If two or 168 * more supported provider have the same name then the resulting map will 169 * contain just one of them; which one it will contain is not specified. 170 * </p> 171 * 172 * <p> 173 * The invocation of this method, and the subsequent use of the resulting 174 * map, may cause time-consuming disk or network I/O operations to occur. 175 * This method is provided for applications that need to enumerate all of 176 * the available providers, for example to allow user provider selection. 177 * </p> 178 * 179 * <p> 180 * This method may return different results at different times if new 181 * providers are dynamically made available to the current Java virtual 182 * machine. 183 * </p> 184 * 185 * @return An immutable, map from names to provider objects 186 * @since 1.13 187 */ 188 public static SortedMap<String, CompressorStreamProvider> findAvailableCompressorInputStreamProviders() { 189 return AccessController.doPrivileged(new PrivilegedAction<SortedMap<String, CompressorStreamProvider>>() { 190 @Override 191 public SortedMap<String, CompressorStreamProvider> run() { 192 final TreeMap<String, CompressorStreamProvider> map = new TreeMap<>(); 193 putAll(SINGLETON.getInputStreamCompressorNames(), SINGLETON, map); 194 for (final CompressorStreamProvider provider : findCompressorStreamProviders()) { 195 putAll(provider.getInputStreamCompressorNames(), provider, map); 196 } 197 return map; 198 } 199 }); 200 } 201 202 /** 203 * Constructs a new sorted map from output stream provider names to provider 204 * objects. 205 * 206 * <p> 207 * The map returned by this method will have one entry for each provider for 208 * which support is available in the current Java virtual machine. If two or 209 * more supported provider have the same name then the resulting map will 210 * contain just one of them; which one it will contain is not specified. 211 * </p> 212 * 213 * <p> 214 * The invocation of this method, and the subsequent use of the resulting 215 * map, may cause time-consuming disk or network I/O operations to occur. 216 * This method is provided for applications that need to enumerate all of 217 * the available providers, for example to allow user provider selection. 218 * </p> 219 * 220 * <p> 221 * This method may return different results at different times if new 222 * providers are dynamically made available to the current Java virtual 223 * machine. 224 * </p> 225 * 226 * @return An immutable, map from names to provider objects 227 * @since 1.13 228 */ 229 public static SortedMap<String, CompressorStreamProvider> findAvailableCompressorOutputStreamProviders() { 230 return AccessController.doPrivileged(new PrivilegedAction<SortedMap<String, CompressorStreamProvider>>() { 231 @Override 232 public SortedMap<String, CompressorStreamProvider> run() { 233 final TreeMap<String, CompressorStreamProvider> map = new TreeMap<>(); 234 putAll(SINGLETON.getOutputStreamCompressorNames(), SINGLETON, map); 235 for (final CompressorStreamProvider provider : findCompressorStreamProviders()) { 236 putAll(provider.getOutputStreamCompressorNames(), provider, map); 237 } 238 return map; 239 } 240 241 }); 242 } 243 private static ArrayList<CompressorStreamProvider> findCompressorStreamProviders() { 244 return Lists.newArrayList(serviceLoaderIterator()); 245 } 246 247 public static String getBzip2() { 248 return BZIP2; 249 } 250 251 public static String getDeflate() { 252 return DEFLATE; 253 } 254 255 public static String getGzip() { 256 return GZIP; 257 } 258 259 public static String getLzma() { 260 return LZMA; 261 } 262 263 public static String getPack200() { 264 return PACK200; 265 } 266 267 public static CompressorStreamFactory getSingleton() { 268 return SINGLETON; 269 } 270 271 public static String getSnappyFramed() { 272 return SNAPPY_FRAMED; 273 } 274 275 public static String getSnappyRaw() { 276 return SNAPPY_RAW; 277 } 278 279 public static String getXz() { 280 return XZ; 281 } 282 283 public static String getZ() { 284 return Z; 285 } 286 287 static void putAll(final Set<String> names, final CompressorStreamProvider provider, 288 final TreeMap<String, CompressorStreamProvider> map) { 289 for (final String name : names) { 290 map.put(toKey(name), provider); 291 } 292 } 293 294 private static Iterator<CompressorStreamProvider> serviceLoaderIterator() { 295 return new ServiceLoaderIterator<>(CompressorStreamProvider.class); 296 } 297 298 private static String toKey(final String name) { 299 return name.toUpperCase(Locale.ROOT); 300 } 301 302 /** 303 * If true, decompress until the end of the input. If false, stop after the 304 * first stream and leave the input position to point to the next byte after 305 * the stream 306 */ 307 private final Boolean decompressUntilEOF; 308 // This is Boolean so setDecompressConcatenated can determine whether it has 309 // been set by the ctor 310 // once the setDecompressConcatenated method has been removed, it can revert 311 // to boolean 312 313 private SortedMap<String, CompressorStreamProvider> compressorInputStreamProviders; 314 315 private SortedMap<String, CompressorStreamProvider> compressorOutputStreamProviders; 316 317 /** 318 * If true, decompress until the end of the input. If false, stop after the 319 * first stream and leave the input position to point to the next byte after 320 * the stream 321 */ 322 private volatile boolean decompressConcatenated = false; 323 324 /** 325 * Create an instance with the decompress Concatenated option set to false. 326 */ 327 public CompressorStreamFactory() { 328 this.decompressUntilEOF = null; 329 } 330 331 /** 332 * Create an instance with the provided decompress Concatenated option. 333 * 334 * @param decompressUntilEOF 335 * if true, decompress until the end of the input; if false, stop 336 * after the first stream and leave the input position to point 337 * to the next byte after the stream. This setting applies to the 338 * gzip, bzip2 and xz formats only. 339 * @since 1.10 340 */ 341 public CompressorStreamFactory(final boolean decompressUntilEOF) { 342 this.decompressUntilEOF = Boolean.valueOf(decompressUntilEOF); 343 // Also copy to existing variable so can continue to use that as the 344 // current value 345 this.decompressConcatenated = decompressUntilEOF; 346 } 347 348 /** 349 * Create an compressor input stream from an input stream, autodetecting the 350 * compressor type from the first few bytes of the stream. The InputStream 351 * must support marks, like BufferedInputStream. 352 * 353 * @param in 354 * the input stream 355 * @return the compressor input stream 356 * @throws CompressorException 357 * if the compressor name is not known 358 * @throws IllegalArgumentException 359 * if the stream is null or does not support mark 360 * @since 1.1 361 */ 362 public CompressorInputStream createCompressorInputStream(final InputStream in) throws CompressorException { 363 if (in == null) { 364 throw new IllegalArgumentException("Stream must not be null."); 365 } 366 367 if (!in.markSupported()) { 368 throw new IllegalArgumentException("Mark is not supported."); 369 } 370 371 final byte[] signature = new byte[12]; 372 in.mark(signature.length); 373 try { 374 final int signatureLength = IOUtils.readFully(in, signature); 375 in.reset(); 376 377 if (BZip2CompressorInputStream.matches(signature, signatureLength)) { 378 return new BZip2CompressorInputStream(in, decompressConcatenated); 379 } 380 381 if (GzipCompressorInputStream.matches(signature, signatureLength)) { 382 return new GzipCompressorInputStream(in, decompressConcatenated); 383 } 384 385 if (Pack200CompressorInputStream.matches(signature, signatureLength)) { 386 return new Pack200CompressorInputStream(in); 387 } 388 389 if (FramedSnappyCompressorInputStream.matches(signature, signatureLength)) { 390 return new FramedSnappyCompressorInputStream(in); 391 } 392 393 if (ZCompressorInputStream.matches(signature, signatureLength)) { 394 return new ZCompressorInputStream(in); 395 } 396 397 if (DeflateCompressorInputStream.matches(signature, signatureLength)) { 398 return new DeflateCompressorInputStream(in); 399 } 400 401 if (XZUtils.matches(signature, signatureLength) && XZUtils.isXZCompressionAvailable()) { 402 return new XZCompressorInputStream(in, decompressConcatenated); 403 } 404 405 if (LZMAUtils.matches(signature, signatureLength) && LZMAUtils.isLZMACompressionAvailable()) { 406 return new LZMACompressorInputStream(in); 407 } 408 409 } catch (final IOException e) { 410 throw new CompressorException("Failed to detect Compressor from InputStream.", e); 411 } 412 413 throw new CompressorException("No Compressor found for the stream signature."); 414 } 415 416 /** 417 * Creates a compressor input stream from a compressor name and an input 418 * stream. 419 * 420 * @param name 421 * of the compressor, i.e. {@value #GZIP}, {@value #BZIP2}, 422 * {@value #XZ}, {@value #LZMA}, {@value #PACK200}, 423 * {@value #SNAPPY_RAW}, {@value #SNAPPY_FRAMED}, {@value #Z} or 424 * {@value #DEFLATE} 425 * @param in 426 * the input stream 427 * @return compressor input stream 428 * @throws CompressorException 429 * if the compressor name is not known 430 * @throws IllegalArgumentException 431 * if the name or input stream is null 432 */ 433 public CompressorInputStream createCompressorInputStream(final String name, final InputStream in) 434 throws CompressorException { 435 return createCompressorInputStream(name, in, decompressConcatenated); 436 } 437 438 @Override 439 public CompressorInputStream createCompressorInputStream(final String name, final InputStream in, 440 final boolean actualDecompressConcatenated) throws CompressorException { 441 if (name == null || in == null) { 442 throw new IllegalArgumentException("Compressor name and stream must not be null."); 443 } 444 445 try { 446 447 if (GZIP.equalsIgnoreCase(name)) { 448 return new GzipCompressorInputStream(in, actualDecompressConcatenated); 449 } 450 451 if (BZIP2.equalsIgnoreCase(name)) { 452 return new BZip2CompressorInputStream(in, actualDecompressConcatenated); 453 } 454 455 if (XZ.equalsIgnoreCase(name)) { 456 return new XZCompressorInputStream(in, actualDecompressConcatenated); 457 } 458 459 if (LZMA.equalsIgnoreCase(name)) { 460 return new LZMACompressorInputStream(in); 461 } 462 463 if (PACK200.equalsIgnoreCase(name)) { 464 return new Pack200CompressorInputStream(in); 465 } 466 467 if (SNAPPY_RAW.equalsIgnoreCase(name)) { 468 return new SnappyCompressorInputStream(in); 469 } 470 471 if (SNAPPY_FRAMED.equalsIgnoreCase(name)) { 472 return new FramedSnappyCompressorInputStream(in); 473 } 474 475 if (Z.equalsIgnoreCase(name)) { 476 return new ZCompressorInputStream(in); 477 } 478 479 if (DEFLATE.equalsIgnoreCase(name)) { 480 return new DeflateCompressorInputStream(in); 481 } 482 483 } catch (final IOException e) { 484 throw new CompressorException("Could not create CompressorInputStream.", e); 485 } 486 final CompressorStreamProvider compressorStreamProvider = getCompressorInputStreamProviders().get(toKey(name)); 487 if (compressorStreamProvider != null) { 488 return compressorStreamProvider.createCompressorInputStream(name, in, actualDecompressConcatenated); 489 } 490 491 throw new CompressorException("Compressor: " + name + " not found."); 492 } 493 494 /** 495 * Creates an compressor output stream from an compressor name and an output 496 * stream. 497 * 498 * @param name 499 * the compressor name, i.e. {@value #GZIP}, {@value #BZIP2}, 500 * {@value #XZ}, {@value #PACK200} or {@value #DEFLATE} 501 * @param out 502 * the output stream 503 * @return the compressor output stream 504 * @throws CompressorException 505 * if the archiver name is not known 506 * @throws IllegalArgumentException 507 * if the archiver name or stream is null 508 */ 509 @Override 510 public CompressorOutputStream createCompressorOutputStream(final String name, final OutputStream out) 511 throws CompressorException { 512 if (name == null || out == null) { 513 throw new IllegalArgumentException("Compressor name and stream must not be null."); 514 } 515 516 try { 517 518 if (GZIP.equalsIgnoreCase(name)) { 519 return new GzipCompressorOutputStream(out); 520 } 521 522 if (BZIP2.equalsIgnoreCase(name)) { 523 return new BZip2CompressorOutputStream(out); 524 } 525 526 if (XZ.equalsIgnoreCase(name)) { 527 return new XZCompressorOutputStream(out); 528 } 529 530 if (PACK200.equalsIgnoreCase(name)) { 531 return new Pack200CompressorOutputStream(out); 532 } 533 534 if (LZMA.equalsIgnoreCase(name)) { 535 return new LZMACompressorOutputStream(out); 536 } 537 538 if (DEFLATE.equalsIgnoreCase(name)) { 539 return new DeflateCompressorOutputStream(out); 540 } 541 542 } catch (final IOException e) { 543 throw new CompressorException("Could not create CompressorOutputStream", e); 544 } 545 final CompressorStreamProvider compressorStreamProvider = getCompressorOutputStreamProviders().get(toKey(name)); 546 if (compressorStreamProvider != null) { 547 return compressorStreamProvider.createCompressorOutputStream(name, out); 548 } 549 throw new CompressorException("Compressor: " + name + " not found."); 550 } 551 552 public SortedMap<String, CompressorStreamProvider> getCompressorInputStreamProviders() { 553 if (compressorInputStreamProviders == null) { 554 compressorInputStreamProviders = Collections 555 .unmodifiableSortedMap(findAvailableCompressorInputStreamProviders()); 556 } 557 return compressorInputStreamProviders; 558 } 559 560 public SortedMap<String, CompressorStreamProvider> getCompressorOutputStreamProviders() { 561 if (compressorOutputStreamProviders == null) { 562 compressorOutputStreamProviders = Collections 563 .unmodifiableSortedMap(findAvailableCompressorOutputStreamProviders()); 564 } 565 return compressorOutputStreamProviders; 566 } 567 568 // For Unit tests 569 boolean getDecompressConcatenated() { 570 return decompressConcatenated; 571 } 572 573 public Boolean getDecompressUntilEOF() { 574 return decompressUntilEOF; 575 } 576 577 @Override 578 public Set<String> getInputStreamCompressorNames() { 579 return Sets.newHashSet(GZIP, BZIP2, XZ, LZMA, PACK200, SNAPPY_RAW, SNAPPY_FRAMED, Z, DEFLATE); 580 } 581 582 @Override 583 public Set<String> getOutputStreamCompressorNames() { 584 return Sets.newHashSet(GZIP, BZIP2, XZ, LZMA, PACK200, DEFLATE); 585 } 586 587 /** 588 * Whether to decompress the full input or only the first stream in formats 589 * supporting multiple concatenated input streams. 590 * 591 * <p> 592 * This setting applies to the gzip, bzip2 and xz formats only. 593 * </p> 594 * 595 * @param decompressConcatenated 596 * if true, decompress until the end of the input; if false, stop 597 * after the first stream and leave the input position to point 598 * to the next byte after the stream 599 * @since 1.5 600 * @deprecated 1.10 use the {@link #CompressorStreamFactory(boolean)} 601 * constructor instead 602 * @throws IllegalStateException 603 * if the constructor {@link #CompressorStreamFactory(boolean)} 604 * was used to create the factory 605 */ 606 @Deprecated 607 public void setDecompressConcatenated(final boolean decompressConcatenated) { 608 if (this.decompressUntilEOF != null) { 609 throw new IllegalStateException("Cannot override the setting defined by the constructor"); 610 } 611 this.decompressConcatenated = decompressConcatenated; 612 } 613 614}