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.Collections; 027import java.util.Locale; 028import java.util.ServiceLoader; 029import java.util.Set; 030import java.util.SortedMap; 031import java.util.TreeMap; 032 033import org.apache.commons.compress.compressors.brotli.BrotliCompressorInputStream; 034import org.apache.commons.compress.compressors.brotli.BrotliUtils; 035import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; 036import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream; 037import org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream; 038import org.apache.commons.compress.compressors.deflate.DeflateCompressorOutputStream; 039import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream; 040import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; 041import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; 042import org.apache.commons.compress.compressors.lz4.BlockLZ4CompressorInputStream; 043import org.apache.commons.compress.compressors.lz4.BlockLZ4CompressorOutputStream; 044import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorInputStream; 045import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorOutputStream; 046import org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream; 047import org.apache.commons.compress.compressors.lzma.LZMACompressorOutputStream; 048import org.apache.commons.compress.compressors.lzma.LZMAUtils; 049import org.apache.commons.compress.compressors.pack200.Pack200CompressorInputStream; 050import org.apache.commons.compress.compressors.pack200.Pack200CompressorOutputStream; 051import org.apache.commons.compress.compressors.snappy.FramedSnappyCompressorInputStream; 052import org.apache.commons.compress.compressors.snappy.FramedSnappyCompressorOutputStream; 053import org.apache.commons.compress.compressors.snappy.SnappyCompressorInputStream; 054import org.apache.commons.compress.compressors.xz.XZCompressorInputStream; 055import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream; 056import org.apache.commons.compress.compressors.xz.XZUtils; 057import org.apache.commons.compress.compressors.z.ZCompressorInputStream; 058import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream; 059import org.apache.commons.compress.compressors.zstandard.ZstdCompressorOutputStream; 060import org.apache.commons.compress.compressors.zstandard.ZstdUtils; 061import org.apache.commons.compress.utils.IOUtils; 062import org.apache.commons.compress.utils.Sets; 063 064/** 065 * <p> 066 * Factory to create Compressor[In|Out]putStreams from names. To add other 067 * implementations you should extend CompressorStreamFactory and override the 068 * appropriate methods (and call their implementation from super of course). 069 * </p> 070 * 071 * Example (Compressing a file): 072 * 073 * <pre> 074 * final OutputStream out = Files.newOutputStream(output.toPath()); 075 * CompressorOutputStream cos = new CompressorStreamFactory() 076 * .createCompressorOutputStream(CompressorStreamFactory.BZIP2, out); 077 * IOUtils.copy(Files.newInputStream(input.toPath()), cos); 078 * cos.close(); 079 * </pre> 080 * 081 * Example (Decompressing a file): 082 * 083 * <pre> 084 * final InputStream is = Files.newInputStream(input.toPath()); 085 * CompressorInputStream in = new CompressorStreamFactory().createCompressorInputStream(CompressorStreamFactory.BZIP2, 086 * is); 087 * IOUtils.copy(in, Files.newOutputStream(output.toPath())); 088 * in.close(); 089 * </pre> 090 * 091 * @Immutable provided that the deprecated method setDecompressConcatenated is 092 * not used. 093 * @ThreadSafe even if the deprecated method setDecompressConcatenated is used 094 */ 095public class CompressorStreamFactory implements CompressorStreamProvider { 096 097 private static final CompressorStreamFactory SINGLETON = new CompressorStreamFactory(); 098 099 /** 100 * Constant (value {@value}) used to identify the BROTLI compression 101 * algorithm. 102 * 103 * @since 1.14 104 */ 105 public static final String BROTLI = "br"; 106 107 /** 108 * Constant (value {@value}) used to identify the BZIP2 compression 109 * algorithm. 110 * 111 * @since 1.1 112 */ 113 public static final String BZIP2 = "bzip2"; 114 115 /** 116 * Constant (value {@value}) used to identify the GZIP compression 117 * algorithm. 118 * 119 * @since 1.1 120 */ 121 public static final String GZIP = "gz"; 122 123 /** 124 * Constant (value {@value}) used to identify the PACK200 compression 125 * algorithm. 126 * 127 * @since 1.3 128 */ 129 public static final String PACK200 = "pack200"; 130 131 /** 132 * Constant (value {@value}) used to identify the XZ compression method. 133 * 134 * @since 1.4 135 */ 136 public static final String XZ = "xz"; 137 138 /** 139 * Constant (value {@value}) used to identify the LZMA compression method. 140 * 141 * @since 1.6 142 */ 143 public static final String LZMA = "lzma"; 144 145 /** 146 * Constant (value {@value}) used to identify the "framed" Snappy 147 * compression method. 148 * 149 * @since 1.7 150 */ 151 public static final String SNAPPY_FRAMED = "snappy-framed"; 152 153 /** 154 * Constant (value {@value}) used to identify the "raw" Snappy compression 155 * method. Not supported as an output stream type. 156 * 157 * @since 1.7 158 */ 159 public static final String SNAPPY_RAW = "snappy-raw"; 160 161 /** 162 * Constant (value {@value}) used to identify the traditional Unix compress 163 * method. Not supported as an output stream type. 164 * 165 * @since 1.7 166 */ 167 public static final String Z = "z"; 168 169 /** 170 * Constant (value {@value}) used to identify the Deflate compress method. 171 * 172 * @since 1.9 173 */ 174 public static final String DEFLATE = "deflate"; 175 176 /** 177 * Constant (value {@value}) used to identify the Deflate64 compress method. 178 * 179 * @since 1.16 180 */ 181 public static final String DEFLATE64 = "deflate64"; 182 183 /** 184 * Constant (value {@value}) used to identify the block LZ4 185 * compression method. 186 * 187 * @since 1.14 188 */ 189 public static final String LZ4_BLOCK = "lz4-block"; 190 191 /** 192 * Constant (value {@value}) used to identify the frame LZ4 193 * compression method. 194 * 195 * @since 1.14 196 */ 197 public static final String LZ4_FRAMED = "lz4-framed"; 198 199 /** 200 * Constant (value {@value}) used to identify the Zstandard compression 201 * algorithm. Not supported as an output stream type. 202 * 203 * @since 1.16 204 */ 205 public static final String ZSTANDARD = "zstd"; 206 207 private static final String YOU_NEED_BROTLI_DEC = youNeed("Google Brotli Dec", "https://github.com/google/brotli/"); 208 private static final String YOU_NEED_XZ_JAVA = youNeed("XZ for Java", "https://tukaani.org/xz/java.html"); 209 private static final String YOU_NEED_ZSTD_JNI = youNeed("Zstd JNI", "https://github.com/luben/zstd-jni"); 210 211 private static final Set<String> ALL_NAMES = Sets.newHashSet(BZIP2, GZIP, PACK200, SNAPPY_FRAMED, Z, DEFLATE, XZ, LZMA, LZ4_FRAMED, ZSTANDARD); 212 213 private static Iterable<CompressorStreamProvider> archiveStreamProviderIterable() { 214 return ServiceLoader.load(CompressorStreamProvider.class, ClassLoader.getSystemClassLoader()); 215 } 216 217 /** 218 * Detects the type of compressor stream. 219 * 220 * @param inputStream input stream 221 * @return type of compressor stream detected 222 * @throws CompressorException if no compressor stream type was detected 223 * or if something else went wrong 224 * @throws IllegalArgumentException if stream is null or does not support mark 225 * 226 * @since 1.14 227 */ 228 public static String detect(final InputStream inputStream) throws CompressorException { 229 return detect(inputStream, ALL_NAMES); 230 } 231 232 /** 233 * Detects the type of compressor stream while limiting the type to the provided set of compressor names. 234 * 235 * @param inputStream input stream 236 * @param compressorNames compressor names to limit autodetection 237 * @return type of compressor stream detected 238 * @throws CompressorException if no compressor stream type was detected 239 * or if something else went wrong 240 * @throws IllegalArgumentException if stream is null or does not support mark 241 */ 242 static String detect(final InputStream inputStream, final Set<String> compressorNames) throws CompressorException { 243 if (inputStream == null) { 244 throw new IllegalArgumentException("Stream must not be null."); 245 } 246 247 if (compressorNames == null || compressorNames.isEmpty()) { 248 throw new IllegalArgumentException("Compressor names cannot be null or empty"); 249 } 250 251 if (!inputStream.markSupported()) { 252 throw new IllegalArgumentException("Mark is not supported."); 253 } 254 255 final byte[] signature = new byte[12]; 256 inputStream.mark(signature.length); 257 int signatureLength = -1; 258 try { 259 signatureLength = IOUtils.readFully(inputStream, signature); 260 inputStream.reset(); 261 } catch (final IOException e) { 262 throw new CompressorException("IOException while reading signature.", e); 263 } 264 265 if (compressorNames.contains(BZIP2) && BZip2CompressorInputStream.matches(signature, signatureLength)) { 266 return BZIP2; 267 } 268 269 if (compressorNames.contains(GZIP) && GzipCompressorInputStream.matches(signature, signatureLength)) { 270 return GZIP; 271 } 272 273 if (compressorNames.contains(PACK200) && Pack200CompressorInputStream.matches(signature, signatureLength)) { 274 return PACK200; 275 } 276 277 if (compressorNames.contains(SNAPPY_FRAMED) && 278 FramedSnappyCompressorInputStream.matches(signature, signatureLength)) { 279 return SNAPPY_FRAMED; 280 } 281 282 if (compressorNames.contains(Z) && ZCompressorInputStream.matches(signature, signatureLength)) { 283 return Z; 284 } 285 286 if (compressorNames.contains(DEFLATE) && DeflateCompressorInputStream.matches(signature, signatureLength)) { 287 return DEFLATE; 288 } 289 290 if (compressorNames.contains(XZ) && XZUtils.matches(signature, signatureLength)) { 291 return XZ; 292 } 293 294 if (compressorNames.contains(LZMA) && LZMAUtils.matches(signature, signatureLength)) { 295 return LZMA; 296 } 297 298 if (compressorNames.contains(LZ4_FRAMED) && FramedLZ4CompressorInputStream.matches(signature, signatureLength)) { 299 return LZ4_FRAMED; 300 } 301 302 if (compressorNames.contains(ZSTANDARD) && ZstdUtils.matches(signature, signatureLength)) { 303 return ZSTANDARD; 304 } 305 306 throw new CompressorException("No Compressor found for the stream signature."); 307 } 308 309 /** 310 * Constructs a new sorted map from input stream provider names to provider 311 * objects. 312 * 313 * <p> 314 * The map returned by this method will have one entry for each provider for 315 * which support is available in the current Java virtual machine. If two or 316 * more supported provider have the same name then the resulting map will 317 * contain just one of them; which one it will contain is not specified. 318 * </p> 319 * 320 * <p> 321 * The invocation of this method, and the subsequent use of the resulting 322 * map, may cause time-consuming disk or network I/O operations to occur. 323 * This method is provided for applications that need to enumerate all of 324 * the available providers, for example to allow user provider selection. 325 * </p> 326 * 327 * <p> 328 * This method may return different results at different times if new 329 * providers are dynamically made available to the current Java virtual 330 * machine. 331 * </p> 332 * 333 * @return An immutable, map from names to provider objects 334 * @since 1.13 335 */ 336 public static SortedMap<String, CompressorStreamProvider> findAvailableCompressorInputStreamProviders() { 337 return AccessController.doPrivileged((PrivilegedAction<SortedMap<String, CompressorStreamProvider>>) () -> { 338 final TreeMap<String, CompressorStreamProvider> map = new TreeMap<>(); 339 putAll(SINGLETON.getInputStreamCompressorNames(), SINGLETON, map); 340 archiveStreamProviderIterable().forEach(provider -> putAll(provider.getInputStreamCompressorNames(), provider, map)); 341 return map; 342 }); 343 } 344 345 /** 346 * Constructs a new sorted map from output stream provider names to provider 347 * objects. 348 * 349 * <p> 350 * The map returned by this method will have one entry for each provider for 351 * which support is available in the current Java virtual machine. If two or 352 * more supported provider have the same name then the resulting map will 353 * contain just one of them; which one it will contain is not specified. 354 * </p> 355 * 356 * <p> 357 * The invocation of this method, and the subsequent use of the resulting 358 * map, may cause time-consuming disk or network I/O operations to occur. 359 * This method is provided for applications that need to enumerate all of 360 * the available providers, for example to allow user provider selection. 361 * </p> 362 * 363 * <p> 364 * This method may return different results at different times if new 365 * providers are dynamically made available to the current Java virtual 366 * machine. 367 * </p> 368 * 369 * @return An immutable, map from names to provider objects 370 * @since 1.13 371 */ 372 public static SortedMap<String, CompressorStreamProvider> findAvailableCompressorOutputStreamProviders() { 373 return AccessController.doPrivileged((PrivilegedAction<SortedMap<String, CompressorStreamProvider>>) () -> { 374 final TreeMap<String, CompressorStreamProvider> map = new TreeMap<>(); 375 putAll(SINGLETON.getOutputStreamCompressorNames(), SINGLETON, map); 376 archiveStreamProviderIterable().forEach(provider -> putAll(provider.getOutputStreamCompressorNames(), provider, map)); 377 return map; 378 }); 379 } 380 381 public static String getBrotli() { 382 return BROTLI; 383 } 384 385 public static String getBzip2() { 386 return BZIP2; 387 } 388 389 public static String getDeflate() { 390 return DEFLATE; 391 } 392 393 /** 394 * @since 1.16 395 * @return the constant {@link #DEFLATE64} 396 */ 397 public static String getDeflate64() { 398 return DEFLATE64; 399 } 400 401 public static String getGzip() { 402 return GZIP; 403 } 404 405 public static String getLZ4Block() { 406 return LZ4_BLOCK; 407 } 408 409 public static String getLZ4Framed() { 410 return LZ4_FRAMED; 411 } 412 413 public static String getLzma() { 414 return LZMA; 415 } 416 417 public static String getPack200() { 418 return PACK200; 419 } 420 421 public static CompressorStreamFactory getSingleton() { 422 return SINGLETON; 423 } 424 425 public static String getSnappyFramed() { 426 return SNAPPY_FRAMED; 427 } 428 429 public static String getSnappyRaw() { 430 return SNAPPY_RAW; 431 } 432 433 public static String getXz() { 434 return XZ; 435 } 436 437 public static String getZ() { 438 return Z; 439 } 440 441 public static String getZstandard() { 442 return ZSTANDARD; 443 } 444 445 static void putAll(final Set<String> names, final CompressorStreamProvider provider, final TreeMap<String, CompressorStreamProvider> map) { 446 names.forEach(name -> map.put(toKey(name), provider)); 447 } 448 449 private static String toKey(final String name) { 450 return name.toUpperCase(Locale.ROOT); 451 } 452 453 private static String youNeed(final String name, final String url) { 454 return " In addition to Apache Commons Compress you need the " + name + " library - see " + url; 455 } 456 457 /** 458 * If true, decompress until the end of the input. If false, stop after the 459 * first stream and leave the input position to point to the next byte after 460 * the stream 461 */ 462 private final Boolean decompressUntilEOF; 463 // This is Boolean so setDecompressConcatenated can determine whether it has 464 // been set by the ctor 465 // once the setDecompressConcatenated method has been removed, it can revert 466 // to boolean 467 468 private SortedMap<String, CompressorStreamProvider> compressorInputStreamProviders; 469 470 private SortedMap<String, CompressorStreamProvider> compressorOutputStreamProviders; 471 472 /** 473 * If true, decompress until the end of the input. If false, stop after the 474 * first stream and leave the input position to point to the next byte after 475 * the stream 476 */ 477 private volatile boolean decompressConcatenated; 478 479 private final int memoryLimitInKb; 480 481 /** 482 * Constructs an instance with the decompress Concatenated option set to false. 483 */ 484 public CompressorStreamFactory() { 485 this.decompressUntilEOF = null; 486 this.memoryLimitInKb = -1; 487 } 488 489 /** 490 * Constructs an instance with the provided decompress Concatenated option. 491 * 492 * @param decompressUntilEOF 493 * if true, decompress until the end of the input; if false, stop 494 * after the first stream and leave the input position to point 495 * to the next byte after the stream. This setting applies to the 496 * gzip, bzip2 and XZ formats only. 497 * @since 1.10 498 */ 499 public CompressorStreamFactory(final boolean decompressUntilEOF) { 500 this(decompressUntilEOF, -1); 501 } 502 503 /** 504 * Constructs an instance with the provided decompress Concatenated option. 505 * 506 * @param decompressUntilEOF 507 * if true, decompress until the end of the input; if false, stop 508 * after the first stream and leave the input position to point 509 * to the next byte after the stream. This setting applies to the 510 * gzip, bzip2 and XZ formats only. 511 * @param memoryLimitInKb 512 * Some streams require allocation of potentially significant 513 * byte arrays/tables, and they can offer checks to prevent OOMs 514 * on corrupt files. Set the maximum allowed memory allocation in KBs. 515 * 516 * @since 1.14 517 */ 518 public CompressorStreamFactory(final boolean decompressUntilEOF, final int memoryLimitInKb) { 519 this.decompressUntilEOF = decompressUntilEOF; 520 // Also copy to existing variable so can continue to use that as the 521 // current value 522 this.decompressConcatenated = decompressUntilEOF; 523 this.memoryLimitInKb = memoryLimitInKb; 524 } 525 526 /** 527 * Creates a compressor input stream from an input stream, auto-detecting the 528 * compressor type from the first few bytes of the stream. The InputStream 529 * must support marks, like BufferedInputStream. 530 * 531 * @param in 532 * the input stream 533 * @return the compressor input stream 534 * @throws CompressorException 535 * if the compressor name is not known 536 * @throws IllegalArgumentException 537 * if the stream is null or does not support mark 538 * @since 1.1 539 */ 540 public CompressorInputStream createCompressorInputStream(final InputStream in) throws CompressorException { 541 return createCompressorInputStream(detect(in), in); 542 } 543 544 /** 545 * Creates a compressor input stream from an input stream, auto-detecting the 546 * compressor type from the first few bytes of the stream while limiting the detected type 547 * to the provided set of compressor names. The InputStream must support marks, like BufferedInputStream. 548 * 549 * @param in 550 * the input stream 551 * @param compressorNames 552 * compressor names to limit autodetection 553 * @return the compressor input stream 554 * @throws CompressorException 555 * if the autodetected compressor is not in the provided set of compressor names 556 * @throws IllegalArgumentException 557 * if the stream is null or does not support mark 558 * @since 1.25.0 559 */ 560 public CompressorInputStream createCompressorInputStream(final InputStream in, final Set<String> compressorNames) 561 throws CompressorException { 562 return createCompressorInputStream(detect(in, compressorNames), in); 563 } 564 565 /** 566 * Creates a compressor input stream from a compressor name and an input 567 * stream. 568 * 569 * @param name 570 * of the compressor, i.e. {@value #GZIP}, {@value #BZIP2}, 571 * {@value #XZ}, {@value #LZMA}, {@value #PACK200}, 572 * {@value #SNAPPY_RAW}, {@value #SNAPPY_FRAMED}, {@value #Z}, 573 * {@value #LZ4_BLOCK}, {@value #LZ4_FRAMED}, {@value #ZSTANDARD}, 574 * {@value #DEFLATE64} 575 * or {@value #DEFLATE} 576 * @param in 577 * the input stream 578 * @return compressor input stream 579 * @throws CompressorException 580 * if the compressor name is not known or not available, 581 * or if there's an IOException or MemoryLimitException thrown 582 * during initialization 583 * @throws IllegalArgumentException 584 * if the name or input stream is null 585 */ 586 public CompressorInputStream createCompressorInputStream(final String name, final InputStream in) 587 throws CompressorException { 588 return createCompressorInputStream(name, in, decompressConcatenated); 589 } 590 591 @Override 592 public CompressorInputStream createCompressorInputStream(final String name, final InputStream in, 593 final boolean actualDecompressConcatenated) throws CompressorException { 594 if (name == null || in == null) { 595 throw new IllegalArgumentException("Compressor name and stream must not be null."); 596 } 597 598 try { 599 600 if (GZIP.equalsIgnoreCase(name)) { 601 return new GzipCompressorInputStream(in, actualDecompressConcatenated); 602 } 603 604 if (BZIP2.equalsIgnoreCase(name)) { 605 return new BZip2CompressorInputStream(in, actualDecompressConcatenated); 606 } 607 608 if (BROTLI.equalsIgnoreCase(name)) { 609 if (!BrotliUtils.isBrotliCompressionAvailable()) { 610 throw new CompressorException("Brotli compression is not available." + YOU_NEED_BROTLI_DEC); 611 } 612 return new BrotliCompressorInputStream(in); 613 } 614 615 if (XZ.equalsIgnoreCase(name)) { 616 if (!XZUtils.isXZCompressionAvailable()) { 617 throw new CompressorException("XZ compression is not available." + YOU_NEED_XZ_JAVA); 618 } 619 return new XZCompressorInputStream(in, actualDecompressConcatenated, memoryLimitInKb); 620 } 621 622 if (ZSTANDARD.equalsIgnoreCase(name)) { 623 if (!ZstdUtils.isZstdCompressionAvailable()) { 624 throw new CompressorException("Zstandard compression is not available." + YOU_NEED_ZSTD_JNI); 625 } 626 return new ZstdCompressorInputStream(in); 627 } 628 629 if (LZMA.equalsIgnoreCase(name)) { 630 if (!LZMAUtils.isLZMACompressionAvailable()) { 631 throw new CompressorException("LZMA compression is not available" + YOU_NEED_XZ_JAVA); 632 } 633 return new LZMACompressorInputStream(in, memoryLimitInKb); 634 } 635 636 if (PACK200.equalsIgnoreCase(name)) { 637 return new Pack200CompressorInputStream(in); 638 } 639 640 if (SNAPPY_RAW.equalsIgnoreCase(name)) { 641 return new SnappyCompressorInputStream(in); 642 } 643 644 if (SNAPPY_FRAMED.equalsIgnoreCase(name)) { 645 return new FramedSnappyCompressorInputStream(in); 646 } 647 648 if (Z.equalsIgnoreCase(name)) { 649 return new ZCompressorInputStream(in, memoryLimitInKb); 650 } 651 652 if (DEFLATE.equalsIgnoreCase(name)) { 653 return new DeflateCompressorInputStream(in); 654 } 655 656 if (DEFLATE64.equalsIgnoreCase(name)) { 657 return new Deflate64CompressorInputStream(in); 658 } 659 660 if (LZ4_BLOCK.equalsIgnoreCase(name)) { 661 return new BlockLZ4CompressorInputStream(in); 662 } 663 664 if (LZ4_FRAMED.equalsIgnoreCase(name)) { 665 return new FramedLZ4CompressorInputStream(in, actualDecompressConcatenated); 666 } 667 668 } catch (final IOException e) { 669 throw new CompressorException("Could not create CompressorInputStream.", e); 670 } 671 final CompressorStreamProvider compressorStreamProvider = getCompressorInputStreamProviders().get(toKey(name)); 672 if (compressorStreamProvider != null) { 673 return compressorStreamProvider.createCompressorInputStream(name, in, actualDecompressConcatenated); 674 } 675 676 throw new CompressorException("Compressor: " + name + " not found."); 677 } 678 679 /** 680 * Creates a compressor output stream from a compressor name and an output 681 * stream. 682 * 683 * @param name 684 * the compressor name, i.e. {@value #GZIP}, {@value #BZIP2}, 685 * {@value #XZ}, {@value #PACK200}, {@value #SNAPPY_FRAMED}, 686 * {@value #LZ4_BLOCK}, {@value #LZ4_FRAMED}, {@value #ZSTANDARD} 687 * or {@value #DEFLATE} 688 * @param out 689 * the output stream 690 * @return the compressor output stream 691 * @throws CompressorException 692 * if the archiver name is not known 693 * @throws IllegalArgumentException 694 * if the archiver name or stream is null 695 */ 696 @Override 697 public CompressorOutputStream createCompressorOutputStream(final String name, final OutputStream out) 698 throws CompressorException { 699 if (name == null || out == null) { 700 throw new IllegalArgumentException("Compressor name and stream must not be null."); 701 } 702 703 try { 704 705 if (GZIP.equalsIgnoreCase(name)) { 706 return new GzipCompressorOutputStream(out); 707 } 708 709 if (BZIP2.equalsIgnoreCase(name)) { 710 return new BZip2CompressorOutputStream(out); 711 } 712 713 if (XZ.equalsIgnoreCase(name)) { 714 return new XZCompressorOutputStream(out); 715 } 716 717 if (PACK200.equalsIgnoreCase(name)) { 718 return new Pack200CompressorOutputStream(out); 719 } 720 721 if (LZMA.equalsIgnoreCase(name)) { 722 return new LZMACompressorOutputStream(out); 723 } 724 725 if (DEFLATE.equalsIgnoreCase(name)) { 726 return new DeflateCompressorOutputStream(out); 727 } 728 729 if (SNAPPY_FRAMED.equalsIgnoreCase(name)) { 730 return new FramedSnappyCompressorOutputStream(out); 731 } 732 733 if (LZ4_BLOCK.equalsIgnoreCase(name)) { 734 return new BlockLZ4CompressorOutputStream(out); 735 } 736 737 if (LZ4_FRAMED.equalsIgnoreCase(name)) { 738 return new FramedLZ4CompressorOutputStream(out); 739 } 740 741 if (ZSTANDARD.equalsIgnoreCase(name)) { 742 return new ZstdCompressorOutputStream(out); 743 } 744 } catch (final IOException e) { 745 throw new CompressorException("Could not create CompressorOutputStream", e); 746 } 747 final CompressorStreamProvider compressorStreamProvider = getCompressorOutputStreamProviders().get(toKey(name)); 748 if (compressorStreamProvider != null) { 749 return compressorStreamProvider.createCompressorOutputStream(name, out); 750 } 751 throw new CompressorException("Compressor: " + name + " not found."); 752 } 753 754 public SortedMap<String, CompressorStreamProvider> getCompressorInputStreamProviders() { 755 if (compressorInputStreamProviders == null) { 756 compressorInputStreamProviders = Collections 757 .unmodifiableSortedMap(findAvailableCompressorInputStreamProviders()); 758 } 759 return compressorInputStreamProviders; 760 } 761 762 public SortedMap<String, CompressorStreamProvider> getCompressorOutputStreamProviders() { 763 if (compressorOutputStreamProviders == null) { 764 compressorOutputStreamProviders = Collections 765 .unmodifiableSortedMap(findAvailableCompressorOutputStreamProviders()); 766 } 767 return compressorOutputStreamProviders; 768 } 769 770 /** For tests. */ 771 boolean getDecompressConcatenated() { 772 return decompressConcatenated; 773 } 774 775 public Boolean getDecompressUntilEOF() { 776 return decompressUntilEOF; 777 } 778 779 @Override 780 public Set<String> getInputStreamCompressorNames() { 781 return Sets.newHashSet(GZIP, BROTLI, BZIP2, XZ, LZMA, PACK200, DEFLATE, SNAPPY_RAW, SNAPPY_FRAMED, Z, LZ4_BLOCK, 782 LZ4_FRAMED, ZSTANDARD, DEFLATE64); 783 } 784 785 @Override 786 public Set<String> getOutputStreamCompressorNames() { 787 return Sets.newHashSet(GZIP, BZIP2, XZ, LZMA, PACK200, DEFLATE, SNAPPY_FRAMED, LZ4_BLOCK, LZ4_FRAMED, ZSTANDARD); 788 } 789 790 /** 791 * Sets whether to decompress the full input or only the first stream in formats 792 * supporting multiple concatenated input streams. 793 * 794 * <p> 795 * This setting applies to the gzip, bzip2 and XZ formats only. 796 * </p> 797 * 798 * @param decompressConcatenated 799 * if true, decompress until the end of the input; if false, stop 800 * after the first stream and leave the input position to point 801 * to the next byte after the stream 802 * @since 1.5 803 * @deprecated 1.10 use the {@link #CompressorStreamFactory(boolean)} 804 * constructor instead 805 * @throws IllegalStateException 806 * if the constructor {@link #CompressorStreamFactory(boolean)} 807 * was used to create the factory 808 */ 809 @Deprecated 810 public void setDecompressConcatenated(final boolean decompressConcatenated) { 811 if (this.decompressUntilEOF != null) { 812 throw new IllegalStateException("Cannot override the setting defined by the constructor"); 813 } 814 this.decompressConcatenated = decompressConcatenated; 815 } 816 817}