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