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