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   * http://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.Locale;
28  import java.util.ServiceLoader;
29  import java.util.Set;
30  import java.util.SortedMap;
31  import java.util.TreeMap;
32  
33  import org.apache.commons.compress.compressors.brotli.BrotliCompressorInputStream;
34  import org.apache.commons.compress.compressors.brotli.BrotliUtils;
35  import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
36  import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
37  import org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream;
38  import org.apache.commons.compress.compressors.deflate.DeflateCompressorOutputStream;
39  import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream;
40  import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
41  import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
42  import org.apache.commons.compress.compressors.lz4.BlockLZ4CompressorInputStream;
43  import org.apache.commons.compress.compressors.lz4.BlockLZ4CompressorOutputStream;
44  import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorInputStream;
45  import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorOutputStream;
46  import org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream;
47  import org.apache.commons.compress.compressors.lzma.LZMACompressorOutputStream;
48  import org.apache.commons.compress.compressors.lzma.LZMAUtils;
49  import org.apache.commons.compress.compressors.pack200.Pack200CompressorInputStream;
50  import org.apache.commons.compress.compressors.pack200.Pack200CompressorOutputStream;
51  import org.apache.commons.compress.compressors.snappy.FramedSnappyCompressorInputStream;
52  import org.apache.commons.compress.compressors.snappy.FramedSnappyCompressorOutputStream;
53  import org.apache.commons.compress.compressors.snappy.SnappyCompressorInputStream;
54  import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
55  import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
56  import org.apache.commons.compress.compressors.xz.XZUtils;
57  import org.apache.commons.compress.compressors.z.ZCompressorInputStream;
58  import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream;
59  import org.apache.commons.compress.compressors.zstandard.ZstdCompressorOutputStream;
60  import org.apache.commons.compress.compressors.zstandard.ZstdUtils;
61  import org.apache.commons.compress.utils.IOUtils;
62  import org.apache.commons.compress.utils.Sets;
63  
64  /**
65   * <p>
66   * Factory to create 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      *
211      * @since 1.14
212      */
213     public static String detect(final InputStream inputStream) throws CompressorException {
214         return detect(inputStream, ALL_NAMES);
215     }
216 
217     /**
218      * Detects the type of compressor stream while limiting the type to the provided set of compressor names.
219      *
220      * @param inputStream     input stream
221      * @param compressorNames compressor names to limit autodetection
222      * @return type of compressor stream detected
223      * @throws CompressorException      if no compressor stream type was detected or if something else went wrong
224      * @throws IllegalArgumentException if stream is null or does not support mark
225      */
226     static String detect(final InputStream inputStream, final Set<String> compressorNames) throws CompressorException {
227         if (inputStream == null) {
228             throw new IllegalArgumentException("Stream must not be null.");
229         }
230 
231         if (compressorNames == null || compressorNames.isEmpty()) {
232             throw new IllegalArgumentException("Compressor names cannot be null or empty");
233         }
234 
235         if (!inputStream.markSupported()) {
236             throw new IllegalArgumentException("Mark is not supported.");
237         }
238 
239         final byte[] signature = new byte[12];
240         inputStream.mark(signature.length);
241         int signatureLength = -1;
242         try {
243             signatureLength = IOUtils.readFully(inputStream, signature);
244             inputStream.reset();
245         } catch (final IOException e) {
246             throw new CompressorException("IOException while reading signature.", e);
247         }
248 
249         if (compressorNames.contains(BZIP2) && BZip2CompressorInputStream.matches(signature, signatureLength)) {
250             return BZIP2;
251         }
252 
253         if (compressorNames.contains(GZIP) && GzipCompressorInputStream.matches(signature, signatureLength)) {
254             return GZIP;
255         }
256 
257         if (compressorNames.contains(PACK200) && Pack200CompressorInputStream.matches(signature, signatureLength)) {
258             return PACK200;
259         }
260 
261         if (compressorNames.contains(SNAPPY_FRAMED) && FramedSnappyCompressorInputStream.matches(signature, signatureLength)) {
262             return SNAPPY_FRAMED;
263         }
264 
265         if (compressorNames.contains(Z) && ZCompressorInputStream.matches(signature, signatureLength)) {
266             return Z;
267         }
268 
269         if (compressorNames.contains(DEFLATE) && DeflateCompressorInputStream.matches(signature, signatureLength)) {
270             return DEFLATE;
271         }
272 
273         if (compressorNames.contains(XZ) && XZUtils.matches(signature, signatureLength)) {
274             return XZ;
275         }
276 
277         if (compressorNames.contains(LZMA) && LZMAUtils.matches(signature, signatureLength)) {
278             return LZMA;
279         }
280 
281         if (compressorNames.contains(LZ4_FRAMED) && FramedLZ4CompressorInputStream.matches(signature, signatureLength)) {
282             return LZ4_FRAMED;
283         }
284 
285         if (compressorNames.contains(ZSTANDARD) && ZstdUtils.matches(signature, signatureLength)) {
286             return ZSTANDARD;
287         }
288 
289         throw new CompressorException("No Compressor found for the stream signature.");
290     }
291 
292     /**
293      * Constructs a new sorted map from input stream provider names to provider objects.
294      *
295      * <p>
296      * 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
297      * supported provider have the same name then the resulting map will contain just one of them; which one it will contain is not specified.
298      * </p>
299      *
300      * <p>
301      * 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
302      * is provided for applications that need to enumerate all of the available providers, for example to allow user provider selection.
303      * </p>
304      *
305      * <p>
306      * This method may return different results at different times if new providers are dynamically made available to the current Java virtual machine.
307      * </p>
308      *
309      * @return An immutable, map from names to provider objects
310      * @since 1.13
311      */
312     public static SortedMap<String, CompressorStreamProvider> findAvailableCompressorInputStreamProviders() {
313         return AccessController.doPrivileged((PrivilegedAction<SortedMap<String, CompressorStreamProvider>>) () -> {
314             final TreeMap<String, CompressorStreamProvider> map = new TreeMap<>();
315             putAll(SINGLETON.getInputStreamCompressorNames(), SINGLETON, map);
316             archiveStreamProviderIterable().forEach(provider -> putAll(provider.getInputStreamCompressorNames(), provider, map));
317             return map;
318         });
319     }
320 
321     /**
322      * Constructs a new sorted map from output stream provider names to provider objects.
323      *
324      * <p>
325      * 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
326      * supported provider have the same name then the resulting map will contain just one of them; which one it will contain is not specified.
327      * </p>
328      *
329      * <p>
330      * 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
331      * is provided for applications that need to enumerate all of the available providers, for example to allow user provider selection.
332      * </p>
333      *
334      * <p>
335      * This method may return different results at different times if new providers are dynamically made available to the current Java virtual machine.
336      * </p>
337      *
338      * @return An immutable, map from names to provider objects
339      * @since 1.13
340      */
341     public static SortedMap<String, CompressorStreamProvider> findAvailableCompressorOutputStreamProviders() {
342         return AccessController.doPrivileged((PrivilegedAction<SortedMap<String, CompressorStreamProvider>>) () -> {
343             final TreeMap<String, CompressorStreamProvider> map = new TreeMap<>();
344             putAll(SINGLETON.getOutputStreamCompressorNames(), SINGLETON, map);
345             archiveStreamProviderIterable().forEach(provider -> putAll(provider.getOutputStreamCompressorNames(), provider, map));
346             return map;
347         });
348     }
349 
350     public static String getBrotli() {
351         return BROTLI;
352     }
353 
354     public static String getBzip2() {
355         return BZIP2;
356     }
357 
358     public static String getDeflate() {
359         return DEFLATE;
360     }
361 
362     /**
363      * @since 1.16
364      * @return the constant {@link #DEFLATE64}
365      */
366     public static String getDeflate64() {
367         return DEFLATE64;
368     }
369 
370     public static String getGzip() {
371         return GZIP;
372     }
373 
374     public static String getLZ4Block() {
375         return LZ4_BLOCK;
376     }
377 
378     public static String getLZ4Framed() {
379         return LZ4_FRAMED;
380     }
381 
382     public static String getLzma() {
383         return LZMA;
384     }
385 
386     public static String getPack200() {
387         return PACK200;
388     }
389 
390     public static CompressorStreamFactory getSingleton() {
391         return SINGLETON;
392     }
393 
394     public static String getSnappyFramed() {
395         return SNAPPY_FRAMED;
396     }
397 
398     public static String getSnappyRaw() {
399         return SNAPPY_RAW;
400     }
401 
402     public static String getXz() {
403         return XZ;
404     }
405 
406     public static String getZ() {
407         return Z;
408     }
409 
410     public static String getZstandard() {
411         return ZSTANDARD;
412     }
413 
414     static void putAll(final Set<String> names, final CompressorStreamProvider provider, final TreeMap<String, CompressorStreamProvider> map) {
415         names.forEach(name -> map.put(toKey(name), provider));
416     }
417 
418     private static String toKey(final String name) {
419         return name.toUpperCase(Locale.ROOT);
420     }
421 
422     private static String youNeed(final String name, final String url) {
423         return " In addition to Apache Commons Compress you need the " + name + " library - see " + url;
424     }
425 
426     /**
427      * 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
428      * stream
429      */
430     private final Boolean decompressUntilEOF;
431     // This is Boolean so setDecompressConcatenated can determine whether it has
432     // been set by the ctor
433     // once the setDecompressConcatenated method has been removed, it can revert
434     // to boolean
435 
436     private SortedMap<String, CompressorStreamProvider> compressorInputStreamProviders;
437 
438     private SortedMap<String, CompressorStreamProvider> compressorOutputStreamProviders;
439 
440     /**
441      * 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
442      * stream
443      */
444     private volatile boolean decompressConcatenated;
445 
446     private final int memoryLimitInKb;
447 
448     /**
449      * Constructs an instance with the decompress Concatenated option set to false.
450      */
451     public CompressorStreamFactory() {
452         this.decompressUntilEOF = null;
453         this.memoryLimitInKb = -1;
454     }
455 
456     /**
457      * Constructs an instance with the provided decompress Concatenated option.
458      *
459      * @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
460      *                           the next byte after the stream. This setting applies to the gzip, bzip2 and XZ formats only.
461      * @since 1.10
462      */
463     public CompressorStreamFactory(final boolean decompressUntilEOF) {
464         this(decompressUntilEOF, -1);
465     }
466 
467     /**
468      * Constructs an instance with the provided decompress Concatenated option.
469      *
470      * @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
471      *                           the next byte after the stream. This setting applies to the gzip, bzip2 and XZ formats only.
472      * @param memoryLimitInKb    Some streams require allocation of potentially significant byte arrays/tables, and they can offer checks to prevent OOMs on
473      *                           corrupt files. Set the maximum allowed memory allocation in KBs.
474      *
475      * @since 1.14
476      */
477     public CompressorStreamFactory(final boolean decompressUntilEOF, final int memoryLimitInKb) {
478         this.decompressUntilEOF = decompressUntilEOF;
479         // Also copy to existing variable so can continue to use that as the
480         // current value
481         this.decompressConcatenated = decompressUntilEOF;
482         this.memoryLimitInKb = memoryLimitInKb;
483     }
484 
485     /**
486      * 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
487      * support marks, like BufferedInputStream.
488      *
489      * @param in the input stream
490      * @return the compressor input stream
491      * @throws CompressorException      if the compressor name is not known
492      * @throws IllegalArgumentException if the stream is null or does not support mark
493      * @since 1.1
494      */
495     public CompressorInputStream createCompressorInputStream(final InputStream in) throws CompressorException {
496         return createCompressorInputStream(detect(in), in);
497     }
498 
499     /**
500      * 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
501      * detected type to the provided set of compressor names. The InputStream must support marks, like BufferedInputStream.
502      *
503      * @param in              the input stream
504      * @param compressorNames compressor names to limit autodetection
505      * @return the compressor input stream
506      * @throws CompressorException      if the autodetected compressor is not in the provided set of compressor names
507      * @throws IllegalArgumentException if the stream is null or does not support mark
508      * @since 1.25.0
509      */
510     public CompressorInputStream createCompressorInputStream(final InputStream in, final Set<String> compressorNames) throws CompressorException {
511         return createCompressorInputStream(detect(in, compressorNames), in);
512     }
513 
514     /**
515      * Creates a compressor input stream from a compressor name and an input stream.
516      *
517      * @param name of the compressor, i.e. {@value #GZIP}, {@value #BZIP2}, {@value #XZ}, {@value #LZMA}, {@value #PACK200}, {@value #SNAPPY_RAW},
518      *             {@value #SNAPPY_FRAMED}, {@value #Z}, {@value #LZ4_BLOCK}, {@value #LZ4_FRAMED}, {@value #ZSTANDARD}, {@value #DEFLATE64} or
519      *             {@value #DEFLATE}
520      * @param in   the input stream
521      * @return compressor input stream
522      * @throws CompressorException      if the compressor name is not known or not available, or if there's an IOException or MemoryLimitException thrown during
523      *                                  initialization
524      * @throws IllegalArgumentException if the name or input stream is null
525      */
526     public CompressorInputStream createCompressorInputStream(final String name, final InputStream in) throws CompressorException {
527         return createCompressorInputStream(name, in, decompressConcatenated);
528     }
529 
530     @Override
531     public CompressorInputStream createCompressorInputStream(final String name, final InputStream in, final boolean actualDecompressConcatenated)
532             throws CompressorException {
533         if (name == null || in == null) {
534             throw new IllegalArgumentException("Compressor name and stream must not be null.");
535         }
536 
537         try {
538 
539             if (GZIP.equalsIgnoreCase(name)) {
540                 return new GzipCompressorInputStream(in, actualDecompressConcatenated);
541             }
542 
543             if (BZIP2.equalsIgnoreCase(name)) {
544                 return new BZip2CompressorInputStream(in, actualDecompressConcatenated);
545             }
546 
547             if (BROTLI.equalsIgnoreCase(name)) {
548                 if (!BrotliUtils.isBrotliCompressionAvailable()) {
549                     throw new CompressorException("Brotli compression is not available." + YOU_NEED_BROTLI_DEC);
550                 }
551                 return new BrotliCompressorInputStream(in);
552             }
553 
554             if (XZ.equalsIgnoreCase(name)) {
555                 if (!XZUtils.isXZCompressionAvailable()) {
556                     throw new CompressorException("XZ compression is not available." + YOU_NEED_XZ_JAVA);
557                 }
558                 return new XZCompressorInputStream(in, actualDecompressConcatenated, memoryLimitInKb);
559             }
560 
561             if (ZSTANDARD.equalsIgnoreCase(name)) {
562                 if (!ZstdUtils.isZstdCompressionAvailable()) {
563                     throw new CompressorException("Zstandard compression is not available." + YOU_NEED_ZSTD_JNI);
564                 }
565                 return new ZstdCompressorInputStream(in);
566             }
567 
568             if (LZMA.equalsIgnoreCase(name)) {
569                 if (!LZMAUtils.isLZMACompressionAvailable()) {
570                     throw new CompressorException("LZMA compression is not available" + YOU_NEED_XZ_JAVA);
571                 }
572                 return new LZMACompressorInputStream(in, memoryLimitInKb);
573             }
574 
575             if (PACK200.equalsIgnoreCase(name)) {
576                 return new Pack200CompressorInputStream(in);
577             }
578 
579             if (SNAPPY_RAW.equalsIgnoreCase(name)) {
580                 return new SnappyCompressorInputStream(in);
581             }
582 
583             if (SNAPPY_FRAMED.equalsIgnoreCase(name)) {
584                 return new FramedSnappyCompressorInputStream(in);
585             }
586 
587             if (Z.equalsIgnoreCase(name)) {
588                 return new ZCompressorInputStream(in, memoryLimitInKb);
589             }
590 
591             if (DEFLATE.equalsIgnoreCase(name)) {
592                 return new DeflateCompressorInputStream(in);
593             }
594 
595             if (DEFLATE64.equalsIgnoreCase(name)) {
596                 return new Deflate64CompressorInputStream(in);
597             }
598 
599             if (LZ4_BLOCK.equalsIgnoreCase(name)) {
600                 return new BlockLZ4CompressorInputStream(in);
601             }
602 
603             if (LZ4_FRAMED.equalsIgnoreCase(name)) {
604                 return new FramedLZ4CompressorInputStream(in, actualDecompressConcatenated);
605             }
606 
607         } catch (final IOException e) {
608             throw new CompressorException("Could not create CompressorInputStream.", e);
609         }
610         final CompressorStreamProvider compressorStreamProvider = getCompressorInputStreamProviders().get(toKey(name));
611         if (compressorStreamProvider != null) {
612             return compressorStreamProvider.createCompressorInputStream(name, in, actualDecompressConcatenated);
613         }
614 
615         throw new CompressorException("Compressor: " + name + " not found.");
616     }
617 
618     /**
619      * Creates a compressor output stream from a compressor name and an output stream.
620      *
621      * @param name the compressor name, i.e. {@value #GZIP}, {@value #BZIP2}, {@value #XZ}, {@value #PACK200}, {@value #SNAPPY_FRAMED}, {@value #LZ4_BLOCK},
622      *             {@value #LZ4_FRAMED}, {@value #ZSTANDARD} or {@value #DEFLATE}
623      * @param out  the output stream
624      * @return the compressor output stream
625      * @throws CompressorException      if the archiver name is not known
626      * @throws IllegalArgumentException if the archiver name or stream is null
627      */
628     @Override
629     public CompressorOutputStream createCompressorOutputStream(final String name, final OutputStream out) throws CompressorException {
630         if (name == null || out == null) {
631             throw new IllegalArgumentException("Compressor name and stream must not be null.");
632         }
633 
634         try {
635 
636             if (GZIP.equalsIgnoreCase(name)) {
637                 return new GzipCompressorOutputStream(out);
638             }
639 
640             if (BZIP2.equalsIgnoreCase(name)) {
641                 return new BZip2CompressorOutputStream(out);
642             }
643 
644             if (XZ.equalsIgnoreCase(name)) {
645                 return new XZCompressorOutputStream(out);
646             }
647 
648             if (PACK200.equalsIgnoreCase(name)) {
649                 return new Pack200CompressorOutputStream(out);
650             }
651 
652             if (LZMA.equalsIgnoreCase(name)) {
653                 return new LZMACompressorOutputStream(out);
654             }
655 
656             if (DEFLATE.equalsIgnoreCase(name)) {
657                 return new DeflateCompressorOutputStream(out);
658             }
659 
660             if (SNAPPY_FRAMED.equalsIgnoreCase(name)) {
661                 return new FramedSnappyCompressorOutputStream(out);
662             }
663 
664             if (LZ4_BLOCK.equalsIgnoreCase(name)) {
665                 return new BlockLZ4CompressorOutputStream(out);
666             }
667 
668             if (LZ4_FRAMED.equalsIgnoreCase(name)) {
669                 return new FramedLZ4CompressorOutputStream(out);
670             }
671 
672             if (ZSTANDARD.equalsIgnoreCase(name)) {
673                 return new ZstdCompressorOutputStream(out);
674             }
675         } catch (final IOException e) {
676             throw new CompressorException("Could not create CompressorOutputStream", e);
677         }
678         final CompressorStreamProvider compressorStreamProvider = getCompressorOutputStreamProviders().get(toKey(name));
679         if (compressorStreamProvider != null) {
680             return compressorStreamProvider.createCompressorOutputStream(name, out);
681         }
682         throw new CompressorException("Compressor: " + name + " not found.");
683     }
684 
685     public SortedMap<String, CompressorStreamProvider> getCompressorInputStreamProviders() {
686         if (compressorInputStreamProviders == null) {
687             compressorInputStreamProviders = Collections.unmodifiableSortedMap(findAvailableCompressorInputStreamProviders());
688         }
689         return compressorInputStreamProviders;
690     }
691 
692     public SortedMap<String, CompressorStreamProvider> getCompressorOutputStreamProviders() {
693         if (compressorOutputStreamProviders == null) {
694             compressorOutputStreamProviders = Collections.unmodifiableSortedMap(findAvailableCompressorOutputStreamProviders());
695         }
696         return compressorOutputStreamProviders;
697     }
698 
699     /** For tests. */
700     boolean getDecompressConcatenated() {
701         return decompressConcatenated;
702     }
703 
704     public Boolean getDecompressUntilEOF() {
705         return decompressUntilEOF;
706     }
707 
708     @Override
709     public Set<String> getInputStreamCompressorNames() {
710         return Sets.newHashSet(GZIP, BROTLI, BZIP2, XZ, LZMA, PACK200, DEFLATE, SNAPPY_RAW, SNAPPY_FRAMED, Z, LZ4_BLOCK, LZ4_FRAMED, ZSTANDARD, DEFLATE64);
711     }
712 
713     @Override
714     public Set<String> getOutputStreamCompressorNames() {
715         return Sets.newHashSet(GZIP, BZIP2, XZ, LZMA, PACK200, DEFLATE, SNAPPY_FRAMED, LZ4_BLOCK, LZ4_FRAMED, ZSTANDARD);
716     }
717 
718     /**
719      * Sets whether to decompress the full input or only the first stream in formats supporting multiple concatenated input streams.
720      *
721      * <p>
722      * This setting applies to the gzip, bzip2 and XZ formats only.
723      * </p>
724      *
725      * @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
726      *                               to the next byte after the stream
727      * @since 1.5
728      * @deprecated 1.10 use the {@link #CompressorStreamFactory(boolean)} constructor instead
729      * @throws IllegalStateException if the constructor {@link #CompressorStreamFactory(boolean)} was used to create the factory
730      */
731     @Deprecated
732     public void setDecompressConcatenated(final boolean decompressConcatenated) {
733         if (this.decompressUntilEOF != null) {
734             throw new IllegalStateException("Cannot override the setting defined by the constructor");
735         }
736         this.decompressConcatenated = decompressConcatenated;
737     }
738 
739 }