Home | History | Annotate | Download | only in compressors
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one
      3  * or more contributor license agreements.  See the NOTICE file
      4  * distributed with this work for additional information
      5  * regarding copyright ownership.  The ASF licenses this file
      6  * to you under the Apache License, Version 2.0 (the
      7  * "License"); you may not use this file except in compliance
      8  * with the License.  You may obtain a copy of the License at
      9  *
     10  * http://www.apache.org/licenses/LICENSE-2.0
     11  *
     12  * Unless required by applicable law or agreed to in writing,
     13  * software distributed under the License is distributed on an
     14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     15  * KIND, either express or implied.  See the License for the
     16  * specific language governing permissions and limitations
     17  * under the License.
     18  */
     19 package org.apache.commons.compress.compressors;
     20 
     21 import java.io.IOException;
     22 import java.io.InputStream;
     23 import java.io.OutputStream;
     24 import java.security.AccessController;
     25 import java.security.PrivilegedAction;
     26 import java.util.ArrayList;
     27 import java.util.Collections;
     28 import java.util.Iterator;
     29 import java.util.Locale;
     30 import java.util.Set;
     31 import java.util.SortedMap;
     32 import java.util.TreeMap;
     33 
     34 import org.apache.commons.compress.compressors.brotli.BrotliCompressorInputStream;
     35 import org.apache.commons.compress.compressors.brotli.BrotliUtils;
     36 import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
     37 import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
     38 import org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream;
     39 import org.apache.commons.compress.compressors.deflate.DeflateCompressorOutputStream;
     40 import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream;
     41 import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
     42 import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
     43 import org.apache.commons.compress.compressors.lz4.BlockLZ4CompressorInputStream;
     44 import org.apache.commons.compress.compressors.lz4.BlockLZ4CompressorOutputStream;
     45 import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorInputStream;
     46 import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorOutputStream;
     47 import org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream;
     48 import org.apache.commons.compress.compressors.lzma.LZMACompressorOutputStream;
     49 import org.apache.commons.compress.compressors.lzma.LZMAUtils;
     50 import org.apache.commons.compress.compressors.pack200.Pack200CompressorInputStream;
     51 import org.apache.commons.compress.compressors.pack200.Pack200CompressorOutputStream;
     52 import org.apache.commons.compress.compressors.snappy.FramedSnappyCompressorInputStream;
     53 import org.apache.commons.compress.compressors.snappy.FramedSnappyCompressorOutputStream;
     54 import org.apache.commons.compress.compressors.snappy.SnappyCompressorInputStream;
     55 import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
     56 import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
     57 import org.apache.commons.compress.compressors.xz.XZUtils;
     58 import org.apache.commons.compress.compressors.z.ZCompressorInputStream;
     59 import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream;
     60 import org.apache.commons.compress.compressors.zstandard.ZstdCompressorOutputStream;
     61 import org.apache.commons.compress.compressors.zstandard.ZstdUtils;
     62 import org.apache.commons.compress.utils.IOUtils;
     63 import org.apache.commons.compress.utils.Lists;
     64 import org.apache.commons.compress.utils.ServiceLoaderIterator;
     65 import org.apache.commons.compress.utils.Sets;
     66 
     67 /**
     68  * <p>
     69  * Factory to create Compressor[In|Out]putStreams from names. To add other
     70  * implementations you should extend CompressorStreamFactory and override the
     71  * appropriate methods (and call their implementation from super of course).
     72  * </p>
     73  *
     74  * Example (Compressing a file):
     75  *
     76  * <pre>
     77  * final OutputStream out = Files.newOutputStream(output.toPath());
     78  * CompressorOutputStream cos = new CompressorStreamFactory()
     79  *         .createCompressorOutputStream(CompressorStreamFactory.BZIP2, out);
     80  * IOUtils.copy(Files.newInputStream(input.toPath()), cos);
     81  * cos.close();
     82  * </pre>
     83  *
     84  * Example (Decompressing a file):
     85  *
     86  * <pre>
     87  * final InputStream is = Files.newInputStream(input.toPath());
     88  * CompressorInputStream in = new CompressorStreamFactory().createCompressorInputStream(CompressorStreamFactory.BZIP2,
     89  *         is);
     90  * IOUtils.copy(in, Files.newOutputStream(output.toPath()));
     91  * in.close();
     92  * </pre>
     93  *
     94  * @Immutable provided that the deprecated method setDecompressConcatenated is
     95  *            not used.
     96  * @ThreadSafe even if the deprecated method setDecompressConcatenated is used
     97  */
     98 public class CompressorStreamFactory implements CompressorStreamProvider {
     99 
    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(String name, 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(new PrivilegedAction<SortedMap<String, CompressorStreamProvider>>() {
    249             @Override
    250             public SortedMap<String, CompressorStreamProvider> run() {
    251                 final TreeMap<String, CompressorStreamProvider> map = new TreeMap<>();
    252                 putAll(SINGLETON.getInputStreamCompressorNames(), SINGLETON, map);
    253                 for (final CompressorStreamProvider provider : findCompressorStreamProviders()) {
    254                     putAll(provider.getInputStreamCompressorNames(), provider, map);
    255                 }
    256                 return map;
    257             }
    258         });
    259     }
    260 
    261     /**
    262      * Constructs a new sorted map from output stream provider names to provider
    263      * objects.
    264      *
    265      * <p>
    266      * The map returned by this method will have one entry for each provider for
    267      * which support is available in the current Java virtual machine. If two or
    268      * more supported provider have the same name then the resulting map will
    269      * contain just one of them; which one it will contain is not specified.
    270      * </p>
    271      *
    272      * <p>
    273      * The invocation of this method, and the subsequent use of the resulting
    274      * map, may cause time-consuming disk or network I/O operations to occur.
    275      * This method is provided for applications that need to enumerate all of
    276      * the available providers, for example to allow user provider selection.
    277      * </p>
    278      *
    279      * <p>
    280      * This method may return different results at different times if new
    281      * providers are dynamically made available to the current Java virtual
    282      * machine.
    283      * </p>
    284      *
    285      * @return An immutable, map from names to provider objects
    286      * @since 1.13
    287      */
    288     public static SortedMap<String, CompressorStreamProvider> findAvailableCompressorOutputStreamProviders() {
    289         return AccessController.doPrivileged(new PrivilegedAction<SortedMap<String, CompressorStreamProvider>>() {
    290             @Override
    291             public SortedMap<String, CompressorStreamProvider> run() {
    292                 final TreeMap<String, CompressorStreamProvider> map = new TreeMap<>();
    293                 putAll(SINGLETON.getOutputStreamCompressorNames(), SINGLETON, map);
    294                 for (final CompressorStreamProvider provider : findCompressorStreamProviders()) {
    295                     putAll(provider.getOutputStreamCompressorNames(), provider, map);
    296                 }
    297                 return map;
    298             }
    299 
    300         });
    301     }
    302     private static ArrayList<CompressorStreamProvider> findCompressorStreamProviders() {
    303         return Lists.newArrayList(serviceLoaderIterator());
    304     }
    305 
    306     public static String getBrotli() {
    307         return BROTLI;
    308     }
    309 
    310     public static String getBzip2() {
    311         return BZIP2;
    312     }
    313 
    314     public static String getDeflate() {
    315         return DEFLATE;
    316     }
    317 
    318     /**
    319      * @since 1.16
    320      * @return the constant {@link #DEFLATE64}
    321      */
    322     public static String getDeflate64() {
    323         return DEFLATE64;
    324     }
    325 
    326     public static String getGzip() {
    327         return GZIP;
    328     }
    329 
    330     public static String getLzma() {
    331         return LZMA;
    332     }
    333 
    334     public static String getPack200() {
    335         return PACK200;
    336     }
    337 
    338     public static CompressorStreamFactory getSingleton() {
    339         return SINGLETON;
    340     }
    341 
    342     public static String getSnappyFramed() {
    343         return SNAPPY_FRAMED;
    344     }
    345 
    346     public static String getSnappyRaw() {
    347         return SNAPPY_RAW;
    348     }
    349 
    350     public static String getXz() {
    351         return XZ;
    352     }
    353 
    354     public static String getZ() {
    355         return Z;
    356     }
    357 
    358     public static String getLZ4Framed() {
    359         return LZ4_FRAMED;
    360     }
    361 
    362     public static String getLZ4Block() {
    363         return LZ4_BLOCK;
    364     }
    365 
    366     public static String getZstandard() {
    367         return ZSTANDARD;
    368     }
    369 
    370     static void putAll(final Set<String> names, final CompressorStreamProvider provider,
    371             final TreeMap<String, CompressorStreamProvider> map) {
    372         for (final String name : names) {
    373             map.put(toKey(name), provider);
    374         }
    375     }
    376 
    377     private static Iterator<CompressorStreamProvider> serviceLoaderIterator() {
    378         return new ServiceLoaderIterator<>(CompressorStreamProvider.class);
    379     }
    380 
    381     private static String toKey(final String name) {
    382         return name.toUpperCase(Locale.ROOT);
    383     }
    384 
    385     /**
    386      * If true, decompress until the end of the input. If false, stop after the
    387      * first stream and leave the input position to point to the next byte after
    388      * the stream
    389      */
    390     private final Boolean decompressUntilEOF;
    391     // This is Boolean so setDecompressConcatenated can determine whether it has
    392     // been set by the ctor
    393     // once the setDecompressConcatenated method has been removed, it can revert
    394     // to boolean
    395 
    396     private SortedMap<String, CompressorStreamProvider> compressorInputStreamProviders;
    397 
    398     private SortedMap<String, CompressorStreamProvider> compressorOutputStreamProviders;
    399 
    400     /**
    401      * If true, decompress until the end of the input. If false, stop after the
    402      * first stream and leave the input position to point to the next byte after
    403      * the stream
    404      */
    405     private volatile boolean decompressConcatenated = false;
    406 
    407     private final int memoryLimitInKb;
    408     /**
    409      * Create an instance with the decompress Concatenated option set to false.
    410      */
    411     public CompressorStreamFactory() {
    412         this.decompressUntilEOF = null;
    413         this.memoryLimitInKb = -1;
    414     }
    415 
    416     /**
    417      * Create an instance with the provided decompress Concatenated option.
    418      *
    419      * @param decompressUntilEOF
    420      *            if true, decompress until the end of the input; if false, stop
    421      *            after the first stream and leave the input position to point
    422      *            to the next byte after the stream. This setting applies to the
    423      *            gzip, bzip2 and xz formats only.
    424      *
    425      * @param memoryLimitInKb
    426      *            Some streams require allocation of potentially significant
    427      *            byte arrays/tables, and they can offer checks to prevent OOMs
    428      *            on corrupt files.  Set the maximum allowed memory allocation in KBs.
    429      *
    430      * @since 1.14
    431      */
    432     public CompressorStreamFactory(final boolean decompressUntilEOF, final int memoryLimitInKb) {
    433         this.decompressUntilEOF = decompressUntilEOF;
    434         // Also copy to existing variable so can continue to use that as the
    435         // current value
    436         this.decompressConcatenated = decompressUntilEOF;
    437         this.memoryLimitInKb = memoryLimitInKb;
    438     }
    439 
    440 
    441     /**
    442      * Create an instance with the provided decompress Concatenated option.
    443      *
    444      * @param decompressUntilEOF
    445      *            if true, decompress until the end of the input; if false, stop
    446      *            after the first stream and leave the input position to point
    447      *            to the next byte after the stream. This setting applies to the
    448      *            gzip, bzip2 and xz formats only.
    449      * @since 1.10
    450      */
    451     public CompressorStreamFactory(final boolean decompressUntilEOF) {
    452         this(decompressUntilEOF, -1);
    453     }
    454 
    455     /**
    456      * Try to detect the type of compressor stream.
    457      *
    458      * @param in input stream
    459      * @return type of compressor stream detected
    460      * @throws CompressorException if no compressor stream type was detected
    461      *                             or if something else went wrong
    462      * @throws IllegalArgumentException if stream is null or does not support mark
    463      *
    464      * @since 1.14
    465      */
    466     public static String detect(final InputStream in) throws CompressorException {
    467         if (in == null) {
    468             throw new IllegalArgumentException("Stream must not be null.");
    469         }
    470 
    471         if (!in.markSupported()) {
    472             throw new IllegalArgumentException("Mark is not supported.");
    473         }
    474 
    475         final byte[] signature = new byte[12];
    476         in.mark(signature.length);
    477         int signatureLength = -1;
    478         try {
    479             signatureLength = IOUtils.readFully(in, signature);
    480             in.reset();
    481         } catch (IOException e) {
    482             throw new CompressorException("IOException while reading signature.", e);
    483         }
    484 
    485         if (BZip2CompressorInputStream.matches(signature, signatureLength)) {
    486             return BZIP2;
    487         }
    488 
    489         if (GzipCompressorInputStream.matches(signature, signatureLength)) {
    490             return GZIP;
    491         }
    492 
    493         if (Pack200CompressorInputStream.matches(signature, signatureLength)) {
    494             return PACK200;
    495         }
    496 
    497         if (FramedSnappyCompressorInputStream.matches(signature, signatureLength)) {
    498             return SNAPPY_FRAMED;
    499         }
    500 
    501         if (ZCompressorInputStream.matches(signature, signatureLength)) {
    502             return Z;
    503         }
    504 
    505         if (DeflateCompressorInputStream.matches(signature, signatureLength)) {
    506             return DEFLATE;
    507         }
    508 
    509         if (XZUtils.matches(signature, signatureLength)) {
    510             return XZ;
    511         }
    512 
    513         if (LZMAUtils.matches(signature, signatureLength)) {
    514             return LZMA;
    515         }
    516 
    517         if (FramedLZ4CompressorInputStream.matches(signature, signatureLength)) {
    518             return LZ4_FRAMED;
    519         }
    520 
    521         if (ZstdUtils.matches(signature, signatureLength)) {
    522             return ZSTANDARD;
    523         }
    524 
    525         throw new CompressorException("No Compressor found for the stream signature.");
    526     }
    527     /**
    528      * Create an compressor input stream from an input stream, autodetecting the
    529      * compressor type from the first few bytes of the stream. The InputStream
    530      * must support marks, like BufferedInputStream.
    531      *
    532      * @param in
    533      *            the input stream
    534      * @return the compressor input stream
    535      * @throws CompressorException
    536      *             if the compressor name is not known
    537      * @throws IllegalArgumentException
    538      *             if the stream is null or does not support mark
    539      * @since 1.1
    540      */
    541     public CompressorInputStream createCompressorInputStream(final InputStream in) throws CompressorException {
    542         return createCompressorInputStream(detect(in), in);
    543     }
    544 
    545     /**
    546      * Creates a compressor input stream from a compressor name and an input
    547      * stream.
    548      *
    549      * @param name
    550      *            of the compressor, i.e. {@value #GZIP}, {@value #BZIP2},
    551      *            {@value #XZ}, {@value #LZMA}, {@value #PACK200},
    552      *            {@value #SNAPPY_RAW}, {@value #SNAPPY_FRAMED}, {@value #Z},
    553      *            {@value #LZ4_BLOCK}, {@value #LZ4_FRAMED}, {@value #ZSTANDARD},
    554      *            {@value #DEFLATE64}
    555      *            or {@value #DEFLATE}
    556      * @param in
    557      *            the input stream
    558      * @return compressor input stream
    559      * @throws CompressorException
    560      *             if the compressor name is not known or not available,
    561      *             or if there's an IOException or MemoryLimitException thrown
    562      *             during initialization
    563      * @throws IllegalArgumentException
    564      *             if the name or input stream is null
    565      */
    566     public CompressorInputStream createCompressorInputStream(final String name, final InputStream in)
    567             throws CompressorException {
    568         return createCompressorInputStream(name, in, decompressConcatenated);
    569     }
    570 
    571     @Override
    572     public CompressorInputStream createCompressorInputStream(final String name, final InputStream in,
    573             final boolean actualDecompressConcatenated) throws CompressorException {
    574         if (name == null || in == null) {
    575             throw new IllegalArgumentException("Compressor name and stream must not be null.");
    576         }
    577 
    578         try {
    579 
    580             if (GZIP.equalsIgnoreCase(name)) {
    581                 return new GzipCompressorInputStream(in, actualDecompressConcatenated);
    582             }
    583 
    584             if (BZIP2.equalsIgnoreCase(name)) {
    585                 return new BZip2CompressorInputStream(in, actualDecompressConcatenated);
    586             }
    587 
    588             if (BROTLI.equalsIgnoreCase(name)) {
    589                 if (!BrotliUtils.isBrotliCompressionAvailable()) {
    590                     throw new CompressorException("Brotli compression is not available." + YOU_NEED_BROTLI_DEC);
    591                 }
    592                 return new BrotliCompressorInputStream(in);
    593             }
    594 
    595             if (XZ.equalsIgnoreCase(name)) {
    596                 if (!XZUtils.isXZCompressionAvailable()) {
    597                     throw new CompressorException("XZ compression is not available." + YOU_NEED_XZ_JAVA);
    598                 }
    599                 return new XZCompressorInputStream(in, actualDecompressConcatenated, memoryLimitInKb);
    600             }
    601 
    602             if (ZSTANDARD.equalsIgnoreCase(name)) {
    603                 if (!ZstdUtils.isZstdCompressionAvailable()) {
    604                     throw new CompressorException("Zstandard compression is not available." + YOU_NEED_ZSTD_JNI);
    605                 }
    606                 return new ZstdCompressorInputStream(in);
    607             }
    608 
    609             if (LZMA.equalsIgnoreCase(name)) {
    610                 if (!LZMAUtils.isLZMACompressionAvailable()) {
    611                     throw new CompressorException("LZMA compression is not available" + YOU_NEED_XZ_JAVA);
    612                 }
    613                 return new LZMACompressorInputStream(in, memoryLimitInKb);
    614             }
    615 
    616             if (PACK200.equalsIgnoreCase(name)) {
    617                 return new Pack200CompressorInputStream(in);
    618             }
    619 
    620             if (SNAPPY_RAW.equalsIgnoreCase(name)) {
    621                 return new SnappyCompressorInputStream(in);
    622             }
    623 
    624             if (SNAPPY_FRAMED.equalsIgnoreCase(name)) {
    625                 return new FramedSnappyCompressorInputStream(in);
    626             }
    627 
    628             if (Z.equalsIgnoreCase(name)) {
    629                 return new ZCompressorInputStream(in, memoryLimitInKb);
    630             }
    631 
    632             if (DEFLATE.equalsIgnoreCase(name)) {
    633                 return new DeflateCompressorInputStream(in);
    634             }
    635 
    636             if (DEFLATE64.equalsIgnoreCase(name)) {
    637                 return new Deflate64CompressorInputStream(in);
    638             }
    639 
    640             if (LZ4_BLOCK.equalsIgnoreCase(name)) {
    641                 return new BlockLZ4CompressorInputStream(in);
    642             }
    643 
    644             if (LZ4_FRAMED.equalsIgnoreCase(name)) {
    645                 return new FramedLZ4CompressorInputStream(in, actualDecompressConcatenated);
    646             }
    647 
    648         } catch (final IOException e) {
    649             throw new CompressorException("Could not create CompressorInputStream.", e);
    650         }
    651         final CompressorStreamProvider compressorStreamProvider = getCompressorInputStreamProviders().get(toKey(name));
    652         if (compressorStreamProvider != null) {
    653             return compressorStreamProvider.createCompressorInputStream(name, in, actualDecompressConcatenated);
    654         }
    655 
    656         throw new CompressorException("Compressor: " + name + " not found.");
    657     }
    658 
    659     /**
    660      * Creates an compressor output stream from an compressor name and an output
    661      * stream.
    662      *
    663      * @param name
    664      *            the compressor name, i.e. {@value #GZIP}, {@value #BZIP2},
    665      *            {@value #XZ}, {@value #PACK200}, {@value #SNAPPY_FRAMED},
    666      *            {@value #LZ4_BLOCK}, {@value #LZ4_FRAMED}, {@value #ZSTANDARD}
    667      *            or {@value #DEFLATE}
    668      * @param out
    669      *            the output stream
    670      * @return the compressor output stream
    671      * @throws CompressorException
    672      *             if the archiver name is not known
    673      * @throws IllegalArgumentException
    674      *             if the archiver name or stream is null
    675      */
    676     @Override
    677     public CompressorOutputStream createCompressorOutputStream(final String name, final OutputStream out)
    678             throws CompressorException {
    679         if (name == null || out == null) {
    680             throw new IllegalArgumentException("Compressor name and stream must not be null.");
    681         }
    682 
    683         try {
    684 
    685             if (GZIP.equalsIgnoreCase(name)) {
    686                 return new GzipCompressorOutputStream(out);
    687             }
    688 
    689             if (BZIP2.equalsIgnoreCase(name)) {
    690                 return new BZip2CompressorOutputStream(out);
    691             }
    692 
    693             if (XZ.equalsIgnoreCase(name)) {
    694                 return new XZCompressorOutputStream(out);
    695             }
    696 
    697             if (PACK200.equalsIgnoreCase(name)) {
    698                 return new Pack200CompressorOutputStream(out);
    699             }
    700 
    701             if (LZMA.equalsIgnoreCase(name)) {
    702                 return new LZMACompressorOutputStream(out);
    703             }
    704 
    705             if (DEFLATE.equalsIgnoreCase(name)) {
    706                 return new DeflateCompressorOutputStream(out);
    707             }
    708 
    709             if (SNAPPY_FRAMED.equalsIgnoreCase(name)) {
    710                 return new FramedSnappyCompressorOutputStream(out);
    711             }
    712 
    713             if (LZ4_BLOCK.equalsIgnoreCase(name)) {
    714                 return new BlockLZ4CompressorOutputStream(out);
    715             }
    716 
    717             if (LZ4_FRAMED.equalsIgnoreCase(name)) {
    718                 return new FramedLZ4CompressorOutputStream(out);
    719             }
    720 
    721             if (ZSTANDARD.equalsIgnoreCase(name)) {
    722                 return new ZstdCompressorOutputStream(out);
    723             }
    724         } catch (final IOException e) {
    725             throw new CompressorException("Could not create CompressorOutputStream", e);
    726         }
    727         final CompressorStreamProvider compressorStreamProvider = getCompressorOutputStreamProviders().get(toKey(name));
    728         if (compressorStreamProvider != null) {
    729             return compressorStreamProvider.createCompressorOutputStream(name, out);
    730         }
    731         throw new CompressorException("Compressor: " + name + " not found.");
    732     }
    733 
    734     public SortedMap<String, CompressorStreamProvider> getCompressorInputStreamProviders() {
    735         if (compressorInputStreamProviders == null) {
    736             compressorInputStreamProviders = Collections
    737                     .unmodifiableSortedMap(findAvailableCompressorInputStreamProviders());
    738         }
    739         return compressorInputStreamProviders;
    740     }
    741 
    742     public SortedMap<String, CompressorStreamProvider> getCompressorOutputStreamProviders() {
    743         if (compressorOutputStreamProviders == null) {
    744             compressorOutputStreamProviders = Collections
    745                     .unmodifiableSortedMap(findAvailableCompressorOutputStreamProviders());
    746         }
    747         return compressorOutputStreamProviders;
    748     }
    749 
    750     // For Unit tests
    751     boolean getDecompressConcatenated() {
    752         return decompressConcatenated;
    753     }
    754 
    755     public Boolean getDecompressUntilEOF() {
    756         return decompressUntilEOF;
    757     }
    758 
    759     @Override
    760     public Set<String> getInputStreamCompressorNames() {
    761         return Sets.newHashSet(GZIP, BROTLI, BZIP2, XZ, LZMA, PACK200, DEFLATE, SNAPPY_RAW, SNAPPY_FRAMED, Z, LZ4_BLOCK,
    762             LZ4_FRAMED, ZSTANDARD, DEFLATE64);
    763     }
    764 
    765     @Override
    766     public Set<String> getOutputStreamCompressorNames() {
    767         return Sets.newHashSet(GZIP, BZIP2, XZ, LZMA, PACK200, DEFLATE, SNAPPY_FRAMED, LZ4_BLOCK, LZ4_FRAMED, ZSTANDARD);
    768     }
    769 
    770     /**
    771      * Whether to decompress the full input or only the first stream in formats
    772      * supporting multiple concatenated input streams.
    773      *
    774      * <p>
    775      * This setting applies to the gzip, bzip2 and xz formats only.
    776      * </p>
    777      *
    778      * @param decompressConcatenated
    779      *            if true, decompress until the end of the input; if false, stop
    780      *            after the first stream and leave the input position to point
    781      *            to the next byte after the stream
    782      * @since 1.5
    783      * @deprecated 1.10 use the {@link #CompressorStreamFactory(boolean)}
    784      *             constructor instead
    785      * @throws IllegalStateException
    786      *             if the constructor {@link #CompressorStreamFactory(boolean)}
    787      *             was used to create the factory
    788      */
    789     @Deprecated
    790     public void setDecompressConcatenated(final boolean decompressConcatenated) {
    791         if (this.decompressUntilEOF != null) {
    792             throw new IllegalStateException("Cannot override the setting defined by the constructor");
    793         }
    794         this.decompressConcatenated = decompressConcatenated;
    795     }
    796 
    797 }
    798