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