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