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 Iterable<CompressorStreamProvider> archiveStreamProviderIterable() {
214        return ServiceLoader.load(CompressorStreamProvider.class, ClassLoader.getSystemClassLoader());
215    }
216
217    /**
218     * Try to detect the type of compressor stream.
219     *
220     * @param inputStream input stream
221     * @return type of compressor stream detected
222     * @throws CompressorException if no compressor stream type was detected
223     *                             or if something else went wrong
224     * @throws IllegalArgumentException if stream is null or does not support mark
225     *
226     * @since 1.14
227     */
228    public static String detect(final InputStream inputStream) throws CompressorException {
229        if (inputStream == null) {
230            throw new IllegalArgumentException("Stream must not be null.");
231        }
232
233        if (!inputStream.markSupported()) {
234            throw new IllegalArgumentException("Mark is not supported.");
235        }
236
237        final byte[] signature = new byte[12];
238        inputStream.mark(signature.length);
239        int signatureLength = -1;
240        try {
241            signatureLength = IOUtils.readFully(inputStream, signature);
242            inputStream.reset();
243        } catch (final IOException e) {
244            throw new CompressorException("IOException while reading signature.", e);
245        }
246
247        if (BZip2CompressorInputStream.matches(signature, signatureLength)) {
248            return BZIP2;
249        }
250
251        if (GzipCompressorInputStream.matches(signature, signatureLength)) {
252            return GZIP;
253        }
254
255        if (Pack200CompressorInputStream.matches(signature, signatureLength)) {
256            return PACK200;
257        }
258
259        if (FramedSnappyCompressorInputStream.matches(signature, signatureLength)) {
260            return SNAPPY_FRAMED;
261        }
262
263        if (ZCompressorInputStream.matches(signature, signatureLength)) {
264            return Z;
265        }
266
267        if (DeflateCompressorInputStream.matches(signature, signatureLength)) {
268            return DEFLATE;
269        }
270
271        if (XZUtils.matches(signature, signatureLength)) {
272            return XZ;
273        }
274
275        if (LZMAUtils.matches(signature, signatureLength)) {
276            return LZMA;
277        }
278
279        if (FramedLZ4CompressorInputStream.matches(signature, signatureLength)) {
280            return LZ4_FRAMED;
281        }
282
283        if (ZstdUtils.matches(signature, signatureLength)) {
284            return ZSTANDARD;
285        }
286
287        throw new CompressorException("No Compressor found for the stream signature.");
288    }
289
290    /**
291     * Constructs a new sorted map from input stream provider names to provider
292     * objects.
293     *
294     * <p>
295     * The map returned by this method will have one entry for each provider for
296     * which support is available in the current Java virtual machine. If two or
297     * more supported provider have the same name then the resulting map will
298     * contain just one of them; which one it will contain is not specified.
299     * </p>
300     *
301     * <p>
302     * The invocation of this method, and the subsequent use of the resulting
303     * map, may cause time-consuming disk or network I/O operations to occur.
304     * This method is provided for applications that need to enumerate all of
305     * the available providers, for example to allow user provider selection.
306     * </p>
307     *
308     * <p>
309     * This method may return different results at different times if new
310     * providers are dynamically made available to the current Java virtual
311     * machine.
312     * </p>
313     *
314     * @return An immutable, map from names to provider objects
315     * @since 1.13
316     */
317    public static SortedMap<String, CompressorStreamProvider> findAvailableCompressorInputStreamProviders() {
318        return AccessController.doPrivileged((PrivilegedAction<SortedMap<String, CompressorStreamProvider>>) () -> {
319            final TreeMap<String, CompressorStreamProvider> map = new TreeMap<>();
320            putAll(SINGLETON.getInputStreamCompressorNames(), SINGLETON, map);
321            archiveStreamProviderIterable().forEach(provider -> putAll(provider.getInputStreamCompressorNames(), provider, map));
322            return map;
323        });
324    }
325
326    /**
327     * Constructs a new sorted map from output stream provider names to provider
328     * objects.
329     *
330     * <p>
331     * The map returned by this method will have one entry for each provider for
332     * which support is available in the current Java virtual machine. If two or
333     * more supported provider have the same name then the resulting map will
334     * contain just one of them; which one it will contain is not specified.
335     * </p>
336     *
337     * <p>
338     * The invocation of this method, and the subsequent use of the resulting
339     * map, may cause time-consuming disk or network I/O operations to occur.
340     * This method is provided for applications that need to enumerate all of
341     * the available providers, for example to allow user provider selection.
342     * </p>
343     *
344     * <p>
345     * This method may return different results at different times if new
346     * providers are dynamically made available to the current Java virtual
347     * machine.
348     * </p>
349     *
350     * @return An immutable, map from names to provider objects
351     * @since 1.13
352     */
353    public static SortedMap<String, CompressorStreamProvider> findAvailableCompressorOutputStreamProviders() {
354        return AccessController.doPrivileged((PrivilegedAction<SortedMap<String, CompressorStreamProvider>>) () -> {
355            final TreeMap<String, CompressorStreamProvider> map = new TreeMap<>();
356            putAll(SINGLETON.getOutputStreamCompressorNames(), SINGLETON, map);
357            archiveStreamProviderIterable().forEach(provider -> putAll(provider.getOutputStreamCompressorNames(), provider, map));
358            return map;
359        });
360    }
361
362    public static String getBrotli() {
363        return BROTLI;
364    }
365
366    public static String getBzip2() {
367        return BZIP2;
368    }
369
370    public static String getDeflate() {
371        return DEFLATE;
372    }
373
374    /**
375     * @since 1.16
376     * @return the constant {@link #DEFLATE64}
377     */
378    public static String getDeflate64() {
379        return DEFLATE64;
380    }
381
382    public static String getGzip() {
383        return GZIP;
384    }
385
386    public static String getLZ4Block() {
387        return LZ4_BLOCK;
388    }
389
390    public static String getLZ4Framed() {
391        return LZ4_FRAMED;
392    }
393
394    public static String getLzma() {
395        return LZMA;
396    }
397
398    public static String getPack200() {
399        return PACK200;
400    }
401
402    public static CompressorStreamFactory getSingleton() {
403        return SINGLETON;
404    }
405
406    public static String getSnappyFramed() {
407        return SNAPPY_FRAMED;
408    }
409
410    public static String getSnappyRaw() {
411        return SNAPPY_RAW;
412    }
413
414    public static String getXz() {
415        return XZ;
416    }
417
418    public static String getZ() {
419        return Z;
420    }
421
422    public static String getZstandard() {
423        return ZSTANDARD;
424    }
425
426    static void putAll(final Set<String> names, final CompressorStreamProvider provider, final TreeMap<String, CompressorStreamProvider> map) {
427        names.forEach(name -> map.put(toKey(name), provider));
428    }
429
430    private static String toKey(final String name) {
431        return name.toUpperCase(Locale.ROOT);
432    }
433
434    private static String youNeed(final String name, final String url) {
435        return " In addition to Apache Commons Compress you need the " + name + " library - see " + url;
436    }
437
438    /**
439     * If true, decompress until the end of the input. If false, stop after the
440     * first stream and leave the input position to point to the next byte after
441     * the stream
442     */
443    private final Boolean decompressUntilEOF;
444    // This is Boolean so setDecompressConcatenated can determine whether it has
445    // been set by the ctor
446    // once the setDecompressConcatenated method has been removed, it can revert
447    // to boolean
448
449    private SortedMap<String, CompressorStreamProvider> compressorInputStreamProviders;
450
451    private SortedMap<String, CompressorStreamProvider> compressorOutputStreamProviders;
452
453    /**
454     * If true, decompress until the end of the input. If false, stop after the
455     * first stream and leave the input position to point to the next byte after
456     * the stream
457     */
458    private volatile boolean decompressConcatenated;
459
460    private final int memoryLimitInKb;
461
462    /**
463     * Create an instance with the decompress Concatenated option set to false.
464     */
465    public CompressorStreamFactory() {
466        this.decompressUntilEOF = null;
467        this.memoryLimitInKb = -1;
468    }
469
470    /**
471     * Create an instance with the provided decompress Concatenated option.
472     *
473     * @param decompressUntilEOF
474     *            if true, decompress until the end of the input; if false, stop
475     *            after the first stream and leave the input position to point
476     *            to the next byte after the stream. This setting applies to the
477     *            gzip, bzip2 and xz formats only.
478     * @since 1.10
479     */
480    public CompressorStreamFactory(final boolean decompressUntilEOF) {
481        this(decompressUntilEOF, -1);
482    }
483
484    /**
485     * Create an instance with the provided decompress Concatenated option.
486     *
487     * @param decompressUntilEOF
488     *            if true, decompress until the end of the input; if false, stop
489     *            after the first stream and leave the input position to point
490     *            to the next byte after the stream. This setting applies to the
491     *            gzip, bzip2 and xz formats only.
492     *
493     * @param memoryLimitInKb
494     *            Some streams require allocation of potentially significant
495     *            byte arrays/tables, and they can offer checks to prevent OOMs
496     *            on corrupt files.  Set the maximum allowed memory allocation in KBs.
497     *
498     * @since 1.14
499     */
500    public CompressorStreamFactory(final boolean decompressUntilEOF, final int memoryLimitInKb) {
501        this.decompressUntilEOF = decompressUntilEOF;
502        // Also copy to existing variable so can continue to use that as the
503        // current value
504        this.decompressConcatenated = decompressUntilEOF;
505        this.memoryLimitInKb = memoryLimitInKb;
506    }
507    /**
508     * Create a compressor input stream from an input stream, auto-detecting 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 a compressor output stream from a 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}