001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.commons.compress.compressors;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.OutputStream;
024import java.security.AccessController;
025import java.security.PrivilegedAction;
026import java.util.Collections;
027import java.util.Locale;
028import java.util.ServiceLoader;
029import java.util.Set;
030import java.util.SortedMap;
031import java.util.TreeMap;
032
033import org.apache.commons.compress.compressors.brotli.BrotliCompressorInputStream;
034import org.apache.commons.compress.compressors.brotli.BrotliUtils;
035import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
036import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
037import org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream;
038import org.apache.commons.compress.compressors.deflate.DeflateCompressorOutputStream;
039import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream;
040import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
041import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
042import org.apache.commons.compress.compressors.lz4.BlockLZ4CompressorInputStream;
043import org.apache.commons.compress.compressors.lz4.BlockLZ4CompressorOutputStream;
044import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorInputStream;
045import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorOutputStream;
046import org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream;
047import org.apache.commons.compress.compressors.lzma.LZMACompressorOutputStream;
048import org.apache.commons.compress.compressors.lzma.LZMAUtils;
049import org.apache.commons.compress.compressors.snappy.FramedSnappyCompressorInputStream;
050import org.apache.commons.compress.compressors.snappy.FramedSnappyCompressorOutputStream;
051import org.apache.commons.compress.compressors.snappy.SnappyCompressorInputStream;
052import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
053import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
054import org.apache.commons.compress.compressors.xz.XZUtils;
055import org.apache.commons.compress.compressors.z.ZCompressorInputStream;
056import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream;
057import org.apache.commons.compress.compressors.zstandard.ZstdCompressorOutputStream;
058import org.apache.commons.compress.compressors.zstandard.ZstdUtils;
059import org.apache.commons.compress.utils.IOUtils;
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            archiveStreamProviderIterable().forEach(provider -> putAll(provider.getInputStreamCompressorNames(), provider, map));
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            archiveStreamProviderIterable().forEach(provider -> putAll(provider.getOutputStreamCompressorNames(), provider, map));
283            return map;
284        });
285    }
286
287    public static String getBrotli() {
288        return BROTLI;
289    }
290
291    public static String getBzip2() {
292        return BZIP2;
293    }
294
295    public static String getDeflate() {
296        return DEFLATE;
297    }
298
299    /**
300     * @since 1.16
301     * @return the constant {@link #DEFLATE64}
302     */
303    public static String getDeflate64() {
304        return DEFLATE64;
305    }
306
307    public static String getGzip() {
308        return GZIP;
309    }
310
311    public static String getLzma() {
312        return LZMA;
313    }
314
315    public static String getPack200() {
316        return PACK200;
317    }
318
319    public static CompressorStreamFactory getSingleton() {
320        return SINGLETON;
321    }
322
323    public static String getSnappyFramed() {
324        return SNAPPY_FRAMED;
325    }
326
327    public static String getSnappyRaw() {
328        return SNAPPY_RAW;
329    }
330
331    public static String getXz() {
332        return XZ;
333    }
334
335    public static String getZ() {
336        return Z;
337    }
338
339    public static String getLZ4Framed() {
340        return LZ4_FRAMED;
341    }
342
343    public static String getLZ4Block() {
344        return LZ4_BLOCK;
345    }
346
347    public static String getZstandard() {
348        return ZSTANDARD;
349    }
350
351    static void putAll(final Set<String> names, final CompressorStreamProvider provider, final TreeMap<String, CompressorStreamProvider> map) {
352        names.forEach(name -> map.put(toKey(name), provider));
353    }
354
355    private static Iterable<CompressorStreamProvider> archiveStreamProviderIterable() {
356        return ServiceLoader.load(CompressorStreamProvider.class, ClassLoader.getSystemClassLoader());
357    }
358
359    private static String toKey(final String name) {
360        return name.toUpperCase(Locale.ROOT);
361    }
362
363    /**
364     * If true, decompress until the end of the input. If false, stop after the
365     * first stream and leave the input position to point to the next byte after
366     * the stream
367     */
368    private final Boolean decompressUntilEOF;
369    // This is Boolean so setDecompressConcatenated can determine whether it has
370    // been set by the ctor
371    // once the setDecompressConcatenated method has been removed, it can revert
372    // to boolean
373
374    private SortedMap<String, CompressorStreamProvider> compressorInputStreamProviders;
375
376    private SortedMap<String, CompressorStreamProvider> compressorOutputStreamProviders;
377
378    /**
379     * If true, decompress until the end of the input. If false, stop after the
380     * first stream and leave the input position to point to the next byte after
381     * the stream
382     */
383    private volatile boolean decompressConcatenated;
384
385    private final int memoryLimitInKb;
386
387    /**
388     * Create an instance with the decompress Concatenated option set to false.
389     */
390    public CompressorStreamFactory() {
391        this.decompressUntilEOF = null;
392        this.memoryLimitInKb = -1;
393    }
394
395    /**
396     * Create an instance with the provided decompress Concatenated option.
397     *
398     * @param decompressUntilEOF
399     *            if true, decompress until the end of the input; if false, stop
400     *            after the first stream and leave the input position to point
401     *            to the next byte after the stream. This setting applies to the
402     *            gzip, bzip2 and xz formats only.
403     *
404     * @param memoryLimitInKb
405     *            Some streams require allocation of potentially significant
406     *            byte arrays/tables, and they can offer checks to prevent OOMs
407     *            on corrupt files.  Set the maximum allowed memory allocation in KBs.
408     *
409     * @since 1.14
410     */
411    public CompressorStreamFactory(final boolean decompressUntilEOF, final int memoryLimitInKb) {
412        this.decompressUntilEOF = decompressUntilEOF;
413        // Also copy to existing variable so can continue to use that as the
414        // current value
415        this.decompressConcatenated = decompressUntilEOF;
416        this.memoryLimitInKb = memoryLimitInKb;
417    }
418
419    /**
420     * Create an instance with the provided decompress Concatenated option.
421     *
422     * @param decompressUntilEOF
423     *            if true, decompress until the end of the input; if false, stop
424     *            after the first stream and leave the input position to point
425     *            to the next byte after the stream. This setting applies to the
426     *            gzip, bzip2 and xz formats only.
427     * @since 1.10
428     */
429    public CompressorStreamFactory(final boolean decompressUntilEOF) {
430        this(decompressUntilEOF, -1);
431    }
432
433    /**
434     * Try to detect the type of compressor stream.
435     *
436     * @param inputStream input stream
437     * @return type of compressor stream detected
438     * @throws CompressorException if no compressor stream type was detected
439     *                             or if something else went wrong
440     * @throws IllegalArgumentException if stream is null or does not support mark
441     *
442     * @since 1.14
443     */
444    public static String detect(final InputStream inputStream) throws CompressorException {
445        if (inputStream == null) {
446            throw new IllegalArgumentException("Stream must not be null.");
447        }
448
449        if (!inputStream.markSupported()) {
450            throw new IllegalArgumentException("Mark is not supported.");
451        }
452
453        final byte[] signature = new byte[12];
454        inputStream.mark(signature.length);
455        int signatureLength = -1;
456        try {
457            signatureLength = IOUtils.readFully(inputStream, signature);
458            inputStream.reset();
459        } catch (final IOException e) {
460            throw new CompressorException("IOException while reading signature.", e);
461        }
462
463        if (BZip2CompressorInputStream.matches(signature, signatureLength)) {
464            return BZIP2;
465        }
466
467        if (GzipCompressorInputStream.matches(signature, signatureLength)) {
468            return GZIP;
469        }
470
471        if (FramedSnappyCompressorInputStream.matches(signature, signatureLength)) {
472            return SNAPPY_FRAMED;
473        }
474
475        if (ZCompressorInputStream.matches(signature, signatureLength)) {
476            return Z;
477        }
478
479        if (DeflateCompressorInputStream.matches(signature, signatureLength)) {
480            return DEFLATE;
481        }
482
483        if (XZUtils.matches(signature, signatureLength)) {
484            return XZ;
485        }
486
487        if (LZMAUtils.matches(signature, signatureLength)) {
488            return LZMA;
489        }
490
491        if (FramedLZ4CompressorInputStream.matches(signature, signatureLength)) {
492            return LZ4_FRAMED;
493        }
494
495        if (ZstdUtils.matches(signature, signatureLength)) {
496            return ZSTANDARD;
497        }
498
499        throw new CompressorException("No Compressor found for the stream signature.");
500    }
501    /**
502     * Create an compressor input stream from an input stream, autodetecting the
503     * compressor type from the first few bytes of the stream. The InputStream
504     * must support marks, like BufferedInputStream.
505     *
506     * @param in
507     *            the input stream
508     * @return the compressor input stream
509     * @throws CompressorException
510     *             if the compressor name is not known
511     * @throws IllegalArgumentException
512     *             if the stream is null or does not support mark
513     * @since 1.1
514     */
515    public CompressorInputStream createCompressorInputStream(final InputStream in) throws CompressorException {
516        return createCompressorInputStream(detect(in), in);
517    }
518
519    /**
520     * Creates a compressor input stream from a compressor name and an input
521     * stream.
522     *
523     * @param name
524     *            of the compressor, i.e. {@value #GZIP}, {@value #BZIP2},
525     *            {@value #XZ}, {@value #LZMA}, {@value #PACK200},
526     *            {@value #SNAPPY_RAW}, {@value #SNAPPY_FRAMED}, {@value #Z},
527     *            {@value #LZ4_BLOCK}, {@value #LZ4_FRAMED}, {@value #ZSTANDARD},
528     *            {@value #DEFLATE64}
529     *            or {@value #DEFLATE}
530     * @param in
531     *            the input stream
532     * @return compressor input stream
533     * @throws CompressorException
534     *             if the compressor name is not known or not available,
535     *             or if there's an IOException or MemoryLimitException thrown
536     *             during initialization
537     * @throws IllegalArgumentException
538     *             if the name or input stream is null
539     */
540    public CompressorInputStream createCompressorInputStream(final String name, final InputStream in)
541            throws CompressorException {
542        return createCompressorInputStream(name, in, decompressConcatenated);
543    }
544
545    @Override
546    public CompressorInputStream createCompressorInputStream(final String name, final InputStream in,
547            final boolean actualDecompressConcatenated) throws CompressorException {
548        if (name == null || in == null) {
549            throw new IllegalArgumentException("Compressor name and stream must not be null.");
550        }
551
552        try {
553
554            if (GZIP.equalsIgnoreCase(name)) {
555                return new GzipCompressorInputStream(in, actualDecompressConcatenated);
556            }
557
558            if (BZIP2.equalsIgnoreCase(name)) {
559                return new BZip2CompressorInputStream(in, actualDecompressConcatenated);
560            }
561
562            if (BROTLI.equalsIgnoreCase(name)) {
563                if (!BrotliUtils.isBrotliCompressionAvailable()) {
564                    throw new CompressorException("Brotli compression is not available." + YOU_NEED_BROTLI_DEC);
565                }
566                return new BrotliCompressorInputStream(in);
567            }
568
569            if (XZ.equalsIgnoreCase(name)) {
570                if (!XZUtils.isXZCompressionAvailable()) {
571                    throw new CompressorException("XZ compression is not available." + YOU_NEED_XZ_JAVA);
572                }
573                return new XZCompressorInputStream(in, actualDecompressConcatenated, memoryLimitInKb);
574            }
575
576            if (ZSTANDARD.equalsIgnoreCase(name)) {
577                if (!ZstdUtils.isZstdCompressionAvailable()) {
578                    throw new CompressorException("Zstandard compression is not available." + YOU_NEED_ZSTD_JNI);
579                }
580                return new ZstdCompressorInputStream(in);
581            }
582
583            if (LZMA.equalsIgnoreCase(name)) {
584                if (!LZMAUtils.isLZMACompressionAvailable()) {
585                    throw new CompressorException("LZMA compression is not available" + YOU_NEED_XZ_JAVA);
586                }
587                return new LZMACompressorInputStream(in, memoryLimitInKb);
588            }
589
590            if (PACK200.equalsIgnoreCase(name)) {
591                throw new CompressorException("Pack200 compression is not available in this build.");
592            }
593
594            if (SNAPPY_RAW.equalsIgnoreCase(name)) {
595                return new SnappyCompressorInputStream(in);
596            }
597
598            if (SNAPPY_FRAMED.equalsIgnoreCase(name)) {
599                return new FramedSnappyCompressorInputStream(in);
600            }
601
602            if (Z.equalsIgnoreCase(name)) {
603                return new ZCompressorInputStream(in, memoryLimitInKb);
604            }
605
606            if (DEFLATE.equalsIgnoreCase(name)) {
607                return new DeflateCompressorInputStream(in);
608            }
609
610            if (DEFLATE64.equalsIgnoreCase(name)) {
611                return new Deflate64CompressorInputStream(in);
612            }
613
614            if (LZ4_BLOCK.equalsIgnoreCase(name)) {
615                return new BlockLZ4CompressorInputStream(in);
616            }
617
618            if (LZ4_FRAMED.equalsIgnoreCase(name)) {
619                return new FramedLZ4CompressorInputStream(in, actualDecompressConcatenated);
620            }
621
622        } catch (final IOException e) {
623            throw new CompressorException("Could not create CompressorInputStream.", e);
624        }
625        final CompressorStreamProvider compressorStreamProvider = getCompressorInputStreamProviders().get(toKey(name));
626        if (compressorStreamProvider != null) {
627            return compressorStreamProvider.createCompressorInputStream(name, in, actualDecompressConcatenated);
628        }
629
630        throw new CompressorException("Compressor: " + name + " not found.");
631    }
632
633    /**
634     * Creates an compressor output stream from an compressor name and an output
635     * stream.
636     *
637     * @param name
638     *            the compressor name, i.e. {@value #GZIP}, {@value #BZIP2},
639     *            {@value #XZ}, {@value #PACK200}, {@value #SNAPPY_FRAMED},
640     *            {@value #LZ4_BLOCK}, {@value #LZ4_FRAMED}, {@value #ZSTANDARD}
641     *            or {@value #DEFLATE}
642     * @param out
643     *            the output stream
644     * @return the compressor output stream
645     * @throws CompressorException
646     *             if the archiver name is not known
647     * @throws IllegalArgumentException
648     *             if the archiver name or stream is null
649     */
650    @Override
651    public CompressorOutputStream createCompressorOutputStream(final String name, final OutputStream out)
652            throws CompressorException {
653        if (name == null || out == null) {
654            throw new IllegalArgumentException("Compressor name and stream must not be null.");
655        }
656
657        try {
658
659            if (GZIP.equalsIgnoreCase(name)) {
660                return new GzipCompressorOutputStream(out);
661            }
662
663            if (BZIP2.equalsIgnoreCase(name)) {
664                return new BZip2CompressorOutputStream(out);
665            }
666
667            if (XZ.equalsIgnoreCase(name)) {
668                return new XZCompressorOutputStream(out);
669            }
670
671            if (PACK200.equalsIgnoreCase(name)) {
672                throw new CompressorException("Pack200 compression is not available in this build.");
673            }
674
675            if (LZMA.equalsIgnoreCase(name)) {
676                return new LZMACompressorOutputStream(out);
677            }
678
679            if (DEFLATE.equalsIgnoreCase(name)) {
680                return new DeflateCompressorOutputStream(out);
681            }
682
683            if (SNAPPY_FRAMED.equalsIgnoreCase(name)) {
684                return new FramedSnappyCompressorOutputStream(out);
685            }
686
687            if (LZ4_BLOCK.equalsIgnoreCase(name)) {
688                return new BlockLZ4CompressorOutputStream(out);
689            }
690
691            if (LZ4_FRAMED.equalsIgnoreCase(name)) {
692                return new FramedLZ4CompressorOutputStream(out);
693            }
694
695            if (ZSTANDARD.equalsIgnoreCase(name)) {
696                return new ZstdCompressorOutputStream(out);
697            }
698        } catch (final IOException e) {
699            throw new CompressorException("Could not create CompressorOutputStream", e);
700        }
701        final CompressorStreamProvider compressorStreamProvider = getCompressorOutputStreamProviders().get(toKey(name));
702        if (compressorStreamProvider != null) {
703            return compressorStreamProvider.createCompressorOutputStream(name, out);
704        }
705        throw new CompressorException("Compressor: " + name + " not found.");
706    }
707
708    public SortedMap<String, CompressorStreamProvider> getCompressorInputStreamProviders() {
709        if (compressorInputStreamProviders == null) {
710            compressorInputStreamProviders = Collections
711                    .unmodifiableSortedMap(findAvailableCompressorInputStreamProviders());
712        }
713        return compressorInputStreamProviders;
714    }
715
716    public SortedMap<String, CompressorStreamProvider> getCompressorOutputStreamProviders() {
717        if (compressorOutputStreamProviders == null) {
718            compressorOutputStreamProviders = Collections
719                    .unmodifiableSortedMap(findAvailableCompressorOutputStreamProviders());
720        }
721        return compressorOutputStreamProviders;
722    }
723
724    // For Unit tests
725    boolean getDecompressConcatenated() {
726        return decompressConcatenated;
727    }
728
729    public Boolean getDecompressUntilEOF() {
730        return decompressUntilEOF;
731    }
732
733    @Override
734    public Set<String> getInputStreamCompressorNames() {
735        return Sets.newHashSet(GZIP, BROTLI, BZIP2, XZ, LZMA, PACK200, DEFLATE, SNAPPY_RAW, SNAPPY_FRAMED, Z, LZ4_BLOCK,
736            LZ4_FRAMED, ZSTANDARD, DEFLATE64);
737    }
738
739    @Override
740    public Set<String> getOutputStreamCompressorNames() {
741        return Sets.newHashSet(GZIP, BZIP2, XZ, LZMA, PACK200, DEFLATE, SNAPPY_FRAMED, LZ4_BLOCK, LZ4_FRAMED, ZSTANDARD);
742    }
743
744    /**
745     * Whether to decompress the full input or only the first stream in formats
746     * supporting multiple concatenated input streams.
747     *
748     * <p>
749     * This setting applies to the gzip, bzip2 and xz formats only.
750     * </p>
751     *
752     * @param decompressConcatenated
753     *            if true, decompress until the end of the input; if false, stop
754     *            after the first stream and leave the input position to point
755     *            to the next byte after the stream
756     * @since 1.5
757     * @deprecated 1.10 use the {@link #CompressorStreamFactory(boolean)}
758     *             constructor instead
759     * @throws IllegalStateException
760     *             if the constructor {@link #CompressorStreamFactory(boolean)}
761     *             was used to create the factory
762     */
763    @Deprecated
764    public void setDecompressConcatenated(final boolean decompressConcatenated) {
765        if (this.decompressUntilEOF != null) {
766            throw new IllegalStateException("Cannot override the setting defined by the constructor");
767        }
768        this.decompressConcatenated = decompressConcatenated;
769    }
770
771}