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