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