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