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