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