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