View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.compress.compressors;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.security.AccessController;
25  import java.security.PrivilegedAction;
26  import java.util.Collections;
27  import java.util.ServiceLoader;
28  import java.util.Set;
29  import java.util.SortedMap;
30  import java.util.TreeMap;
31  
32  import org.apache.commons.compress.compressors.brotli.BrotliCompressorInputStream;
33  import org.apache.commons.compress.compressors.brotli.BrotliUtils;
34  import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
35  import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
36  import org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream;
37  import org.apache.commons.compress.compressors.deflate.DeflateCompressorOutputStream;
38  import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream;
39  import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
40  import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
41  import org.apache.commons.compress.compressors.lz4.BlockLZ4CompressorInputStream;
42  import org.apache.commons.compress.compressors.lz4.BlockLZ4CompressorOutputStream;
43  import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorInputStream;
44  import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorOutputStream;
45  import org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream;
46  import org.apache.commons.compress.compressors.lzma.LZMACompressorOutputStream;
47  import org.apache.commons.compress.compressors.lzma.LZMAUtils;
48  import org.apache.commons.compress.compressors.pack200.Pack200CompressorInputStream;
49  import org.apache.commons.compress.compressors.pack200.Pack200CompressorOutputStream;
50  import org.apache.commons.compress.compressors.snappy.FramedSnappyCompressorInputStream;
51  import org.apache.commons.compress.compressors.snappy.FramedSnappyCompressorOutputStream;
52  import org.apache.commons.compress.compressors.snappy.SnappyCompressorInputStream;
53  import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
54  import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
55  import org.apache.commons.compress.compressors.xz.XZUtils;
56  import org.apache.commons.compress.compressors.z.ZCompressorInputStream;
57  import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream;
58  import org.apache.commons.compress.compressors.zstandard.ZstdCompressorOutputStream;
59  import org.apache.commons.compress.compressors.zstandard.ZstdUtils;
60  import org.apache.commons.compress.utils.IOUtils;
61  import org.apache.commons.compress.utils.Sets;
62  import org.apache.commons.lang3.StringUtils;
63  
64  /**
65   * <p>
66   * Creates a Compressor[In|Out]putStreams from names. To add other implementations you should extend CompressorStreamFactory and override the
67   * appropriate methods (and call their implementation from super of course).
68   * </p>
69   *
70   * Example (Compressing a file):
71   *
72   * <pre>
73   * final OutputStream out = Files.newOutputStream(output.toPath());
74   * CompressorOutputStream cos = new CompressorStreamFactory().createCompressorOutputStream(CompressorStreamFactory.BZIP2, out);
75   * IOUtils.copy(Files.newInputStream(input.toPath()), cos);
76   * cos.close();
77   * </pre>
78   *
79   * Example (Decompressing a file):
80   *
81   * <pre>
82   * final InputStream is = Files.newInputStream(input.toPath());
83   * CompressorInputStream in = new CompressorStreamFactory().createCompressorInputStream(CompressorStreamFactory.BZIP2, is);
84   * IOUtils.copy(in, Files.newOutputStream(output.toPath()));
85   * in.close();
86   * </pre>
87   *
88   * @Immutable provided that the deprecated method setDecompressConcatenated is not used.
89   * @ThreadSafe even if the deprecated method setDecompressConcatenated is used
90   */
91  public class CompressorStreamFactory implements CompressorStreamProvider {
92  
93      private static final CompressorStreamFactory SINGLETON = new CompressorStreamFactory();
94  
95      /**
96       * Constant (value {@value}) used to identify the BROTLI compression algorithm.
97       *
98       * @since 1.14
99       */
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 }