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