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