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