CompressorStreamFactory.java

  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. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.OutputStream;
  23. import java.security.AccessController;
  24. import java.security.PrivilegedAction;
  25. import java.util.Collections;
  26. import java.util.Locale;
  27. import java.util.ServiceLoader;
  28. import java.util.Set;
  29. import java.util.SortedMap;
  30. import java.util.TreeMap;

  31. import org.apache.commons.compress.compressors.brotli.BrotliCompressorInputStream;
  32. import org.apache.commons.compress.compressors.brotli.BrotliUtils;
  33. import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
  34. import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
  35. import org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream;
  36. import org.apache.commons.compress.compressors.deflate.DeflateCompressorOutputStream;
  37. import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream;
  38. import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
  39. import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
  40. import org.apache.commons.compress.compressors.lz4.BlockLZ4CompressorInputStream;
  41. import org.apache.commons.compress.compressors.lz4.BlockLZ4CompressorOutputStream;
  42. import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorInputStream;
  43. import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorOutputStream;
  44. import org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream;
  45. import org.apache.commons.compress.compressors.lzma.LZMACompressorOutputStream;
  46. import org.apache.commons.compress.compressors.lzma.LZMAUtils;
  47. import org.apache.commons.compress.compressors.pack200.Pack200CompressorInputStream;
  48. import org.apache.commons.compress.compressors.pack200.Pack200CompressorOutputStream;
  49. import org.apache.commons.compress.compressors.snappy.FramedSnappyCompressorInputStream;
  50. import org.apache.commons.compress.compressors.snappy.FramedSnappyCompressorOutputStream;
  51. import org.apache.commons.compress.compressors.snappy.SnappyCompressorInputStream;
  52. import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
  53. import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
  54. import org.apache.commons.compress.compressors.xz.XZUtils;
  55. import org.apache.commons.compress.compressors.z.ZCompressorInputStream;
  56. import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream;
  57. import org.apache.commons.compress.compressors.zstandard.ZstdCompressorOutputStream;
  58. import org.apache.commons.compress.compressors.zstandard.ZstdUtils;
  59. import org.apache.commons.compress.utils.IOUtils;
  60. import org.apache.commons.compress.utils.Sets;

  61. /**
  62.  * <p>
  63.  * Factory to create Compressor[In|Out]putStreams from names. To add other implementations you should extend CompressorStreamFactory and override the
  64.  * appropriate methods (and call their implementation from super of course).
  65.  * </p>
  66.  *
  67.  * Example (Compressing a file):
  68.  *
  69.  * <pre>
  70.  * final OutputStream out = Files.newOutputStream(output.toPath());
  71.  * CompressorOutputStream cos = new CompressorStreamFactory().createCompressorOutputStream(CompressorStreamFactory.BZIP2, out);
  72.  * IOUtils.copy(Files.newInputStream(input.toPath()), cos);
  73.  * cos.close();
  74.  * </pre>
  75.  *
  76.  * Example (Decompressing a file):
  77.  *
  78.  * <pre>
  79.  * final InputStream is = Files.newInputStream(input.toPath());
  80.  * CompressorInputStream in = new CompressorStreamFactory().createCompressorInputStream(CompressorStreamFactory.BZIP2, is);
  81.  * IOUtils.copy(in, Files.newOutputStream(output.toPath()));
  82.  * in.close();
  83.  * </pre>
  84.  *
  85.  * @Immutable provided that the deprecated method setDecompressConcatenated is not used.
  86.  * @ThreadSafe even if the deprecated method setDecompressConcatenated is used
  87.  */
  88. public class CompressorStreamFactory implements CompressorStreamProvider {

  89.     private static final CompressorStreamFactory SINGLETON = new CompressorStreamFactory();

  90.     /**
  91.      * Constant (value {@value}) used to identify the BROTLI compression algorithm.
  92.      *
  93.      * @since 1.14
  94.      */
  95.     public static final String BROTLI = "br";

  96.     /**
  97.      * Constant (value {@value}) used to identify the BZIP2 compression algorithm.
  98.      *
  99.      * @since 1.1
  100.      */
  101.     public static final String BZIP2 = "bzip2";

  102.     /**
  103.      * Constant (value {@value}) used to identify the GZIP compression algorithm.
  104.      *
  105.      * @since 1.1
  106.      */
  107.     public static final String GZIP = "gz";

  108.     /**
  109.      * Constant (value {@value}) used to identify the PACK200 compression algorithm.
  110.      *
  111.      * @since 1.3
  112.      */
  113.     public static final String PACK200 = "pack200";

  114.     /**
  115.      * Constant (value {@value}) used to identify the XZ compression method.
  116.      *
  117.      * @since 1.4
  118.      */
  119.     public static final String XZ = "xz";

  120.     /**
  121.      * Constant (value {@value}) used to identify the LZMA compression method.
  122.      *
  123.      * @since 1.6
  124.      */
  125.     public static final String LZMA = "lzma";

  126.     /**
  127.      * Constant (value {@value}) used to identify the "framed" Snappy compression method.
  128.      *
  129.      * @since 1.7
  130.      */
  131.     public static final String SNAPPY_FRAMED = "snappy-framed";

  132.     /**
  133.      * Constant (value {@value}) used to identify the "raw" Snappy compression method. Not supported as an output stream type.
  134.      *
  135.      * @since 1.7
  136.      */
  137.     public static final String SNAPPY_RAW = "snappy-raw";

  138.     /**
  139.      * Constant (value {@value}) used to identify the traditional UNIX compress method. Not supported as an output stream type.
  140.      *
  141.      * @since 1.7
  142.      */
  143.     public static final String Z = "z";

  144.     /**
  145.      * Constant (value {@value}) used to identify the Deflate compress method.
  146.      *
  147.      * @since 1.9
  148.      */
  149.     public static final String DEFLATE = "deflate";

  150.     /**
  151.      * Constant (value {@value}) used to identify the Deflate64 compress method.
  152.      *
  153.      * @since 1.16
  154.      */
  155.     public static final String DEFLATE64 = "deflate64";

  156.     /**
  157.      * Constant (value {@value}) used to identify the block LZ4 compression method.
  158.      *
  159.      * @since 1.14
  160.      */
  161.     public static final String LZ4_BLOCK = "lz4-block";

  162.     /**
  163.      * Constant (value {@value}) used to identify the frame LZ4 compression method.
  164.      *
  165.      * @since 1.14
  166.      */
  167.     public static final String LZ4_FRAMED = "lz4-framed";

  168.     /**
  169.      * Constant (value {@value}) used to identify the Zstandard compression algorithm. Not supported as an output stream type.
  170.      *
  171.      * @since 1.16
  172.      */
  173.     public static final String ZSTANDARD = "zstd";

  174.     private static final String YOU_NEED_BROTLI_DEC = youNeed("Google Brotli Dec", "https://github.com/google/brotli/");
  175.     private static final String YOU_NEED_XZ_JAVA = youNeed("XZ for Java", "https://tukaani.org/xz/java.html");
  176.     private static final String YOU_NEED_ZSTD_JNI = youNeed("Zstd JNI", "https://github.com/luben/zstd-jni");

  177.     private static final Set<String> ALL_NAMES = Sets.newHashSet(BZIP2, GZIP, PACK200, SNAPPY_FRAMED, Z, DEFLATE, XZ, LZMA, LZ4_FRAMED, ZSTANDARD);

  178.     private static Iterable<CompressorStreamProvider> archiveStreamProviderIterable() {
  179.         return ServiceLoader.load(CompressorStreamProvider.class, ClassLoader.getSystemClassLoader());
  180.     }

  181.     /**
  182.      * Detects the type of compressor stream.
  183.      *
  184.      * @param inputStream input stream
  185.      * @return type of compressor stream detected
  186.      * @throws CompressorException      if no compressor stream type was detected or if something else went wrong
  187.      * @throws IllegalArgumentException if stream is null or does not support mark
  188.      *
  189.      * @since 1.14
  190.      */
  191.     public static String detect(final InputStream inputStream) throws CompressorException {
  192.         return detect(inputStream, ALL_NAMES);
  193.     }

  194.     /**
  195.      * Detects the type of compressor stream while limiting the type to the provided set of compressor names.
  196.      *
  197.      * @param inputStream     input stream
  198.      * @param compressorNames compressor names to limit autodetection
  199.      * @return type of compressor stream detected
  200.      * @throws CompressorException      if no compressor stream type was detected or if something else went wrong
  201.      * @throws IllegalArgumentException if stream is null or does not support mark
  202.      */
  203.     static String detect(final InputStream inputStream, final Set<String> compressorNames) throws CompressorException {
  204.         if (inputStream == null) {
  205.             throw new IllegalArgumentException("Stream must not be null.");
  206.         }

  207.         if (compressorNames == null || compressorNames.isEmpty()) {
  208.             throw new IllegalArgumentException("Compressor names cannot be null or empty");
  209.         }

  210.         if (!inputStream.markSupported()) {
  211.             throw new IllegalArgumentException("Mark is not supported.");
  212.         }

  213.         final byte[] signature = new byte[12];
  214.         inputStream.mark(signature.length);
  215.         int signatureLength = -1;
  216.         try {
  217.             signatureLength = IOUtils.readFully(inputStream, signature);
  218.             inputStream.reset();
  219.         } catch (final IOException e) {
  220.             throw new CompressorException("IOException while reading signature.", e);
  221.         }

  222.         if (compressorNames.contains(BZIP2) && BZip2CompressorInputStream.matches(signature, signatureLength)) {
  223.             return BZIP2;
  224.         }

  225.         if (compressorNames.contains(GZIP) && GzipCompressorInputStream.matches(signature, signatureLength)) {
  226.             return GZIP;
  227.         }

  228.         if (compressorNames.contains(PACK200) && Pack200CompressorInputStream.matches(signature, signatureLength)) {
  229.             return PACK200;
  230.         }

  231.         if (compressorNames.contains(SNAPPY_FRAMED) && FramedSnappyCompressorInputStream.matches(signature, signatureLength)) {
  232.             return SNAPPY_FRAMED;
  233.         }

  234.         if (compressorNames.contains(Z) && ZCompressorInputStream.matches(signature, signatureLength)) {
  235.             return Z;
  236.         }

  237.         if (compressorNames.contains(DEFLATE) && DeflateCompressorInputStream.matches(signature, signatureLength)) {
  238.             return DEFLATE;
  239.         }

  240.         if (compressorNames.contains(XZ) && XZUtils.matches(signature, signatureLength)) {
  241.             return XZ;
  242.         }

  243.         if (compressorNames.contains(LZMA) && LZMAUtils.matches(signature, signatureLength)) {
  244.             return LZMA;
  245.         }

  246.         if (compressorNames.contains(LZ4_FRAMED) && FramedLZ4CompressorInputStream.matches(signature, signatureLength)) {
  247.             return LZ4_FRAMED;
  248.         }

  249.         if (compressorNames.contains(ZSTANDARD) && ZstdUtils.matches(signature, signatureLength)) {
  250.             return ZSTANDARD;
  251.         }

  252.         throw new CompressorException("No Compressor found for the stream signature.");
  253.     }

  254.     /**
  255.      * Constructs a new sorted map from input stream provider names to provider objects.
  256.      *
  257.      * <p>
  258.      * The map returned by this method will have one entry for each provider for which support is available in the current Java virtual machine. If two or more
  259.      * supported provider have the same name then the resulting map will contain just one of them; which one it will contain is not specified.
  260.      * </p>
  261.      *
  262.      * <p>
  263.      * The invocation of this method, and the subsequent use of the resulting map, may cause time-consuming disk or network I/O operations to occur. This method
  264.      * is provided for applications that need to enumerate all of the available providers, for example to allow user provider selection.
  265.      * </p>
  266.      *
  267.      * <p>
  268.      * This method may return different results at different times if new providers are dynamically made available to the current Java virtual machine.
  269.      * </p>
  270.      *
  271.      * @return An immutable, map from names to provider objects
  272.      * @since 1.13
  273.      */
  274.     public static SortedMap<String, CompressorStreamProvider> findAvailableCompressorInputStreamProviders() {
  275.         return AccessController.doPrivileged((PrivilegedAction<SortedMap<String, CompressorStreamProvider>>) () -> {
  276.             final TreeMap<String, CompressorStreamProvider> map = new TreeMap<>();
  277.             putAll(SINGLETON.getInputStreamCompressorNames(), SINGLETON, map);
  278.             archiveStreamProviderIterable().forEach(provider -> putAll(provider.getInputStreamCompressorNames(), provider, map));
  279.             return map;
  280.         });
  281.     }

  282.     /**
  283.      * Constructs a new sorted map from output stream provider names to provider objects.
  284.      *
  285.      * <p>
  286.      * The map returned by this method will have one entry for each provider for which support is available in the current Java virtual machine. If two or more
  287.      * supported provider have the same name then the resulting map will contain just one of them; which one it will contain is not specified.
  288.      * </p>
  289.      *
  290.      * <p>
  291.      * The invocation of this method, and the subsequent use of the resulting map, may cause time-consuming disk or network I/O operations to occur. This method
  292.      * is provided for applications that need to enumerate all of the available providers, for example to allow user provider selection.
  293.      * </p>
  294.      *
  295.      * <p>
  296.      * This method may return different results at different times if new providers are dynamically made available to the current Java virtual machine.
  297.      * </p>
  298.      *
  299.      * @return An immutable, map from names to provider objects
  300.      * @since 1.13
  301.      */
  302.     public static SortedMap<String, CompressorStreamProvider> findAvailableCompressorOutputStreamProviders() {
  303.         return AccessController.doPrivileged((PrivilegedAction<SortedMap<String, CompressorStreamProvider>>) () -> {
  304.             final TreeMap<String, CompressorStreamProvider> map = new TreeMap<>();
  305.             putAll(SINGLETON.getOutputStreamCompressorNames(), SINGLETON, map);
  306.             archiveStreamProviderIterable().forEach(provider -> putAll(provider.getOutputStreamCompressorNames(), provider, map));
  307.             return map;
  308.         });
  309.     }

  310.     public static String getBrotli() {
  311.         return BROTLI;
  312.     }

  313.     public static String getBzip2() {
  314.         return BZIP2;
  315.     }

  316.     public static String getDeflate() {
  317.         return DEFLATE;
  318.     }

  319.     /**
  320.      * @since 1.16
  321.      * @return the constant {@link #DEFLATE64}
  322.      */
  323.     public static String getDeflate64() {
  324.         return DEFLATE64;
  325.     }

  326.     public static String getGzip() {
  327.         return GZIP;
  328.     }

  329.     public static String getLZ4Block() {
  330.         return LZ4_BLOCK;
  331.     }

  332.     public static String getLZ4Framed() {
  333.         return LZ4_FRAMED;
  334.     }

  335.     public static String getLzma() {
  336.         return LZMA;
  337.     }

  338.     public static String getPack200() {
  339.         return PACK200;
  340.     }

  341.     public static CompressorStreamFactory getSingleton() {
  342.         return SINGLETON;
  343.     }

  344.     public static String getSnappyFramed() {
  345.         return SNAPPY_FRAMED;
  346.     }

  347.     public static String getSnappyRaw() {
  348.         return SNAPPY_RAW;
  349.     }

  350.     public static String getXz() {
  351.         return XZ;
  352.     }

  353.     public static String getZ() {
  354.         return Z;
  355.     }

  356.     public static String getZstandard() {
  357.         return ZSTANDARD;
  358.     }

  359.     static void putAll(final Set<String> names, final CompressorStreamProvider provider, final TreeMap<String, CompressorStreamProvider> map) {
  360.         names.forEach(name -> map.put(toKey(name), provider));
  361.     }

  362.     private static String toKey(final String name) {
  363.         return name.toUpperCase(Locale.ROOT);
  364.     }

  365.     private static String youNeed(final String name, final String url) {
  366.         return " In addition to Apache Commons Compress you need the " + name + " library - see " + url;
  367.     }

  368.     /**
  369.      * If true, decompress until the end of the input. If false, stop after the first stream and leave the input position to point to the next byte after the
  370.      * stream
  371.      */
  372.     private final Boolean decompressUntilEOF;
  373.     // This is Boolean so setDecompressConcatenated can determine whether it has
  374.     // been set by the ctor
  375.     // once the setDecompressConcatenated method has been removed, it can revert
  376.     // to boolean

  377.     private SortedMap<String, CompressorStreamProvider> compressorInputStreamProviders;

  378.     private SortedMap<String, CompressorStreamProvider> compressorOutputStreamProviders;

  379.     /**
  380.      * If true, decompress until the end of the input. If false, stop after the first stream and leave the input position to point to the next byte after the
  381.      * stream
  382.      */
  383.     private volatile boolean decompressConcatenated;

  384.     private final int memoryLimitInKb;

  385.     /**
  386.      * Constructs an instance with the decompress Concatenated option set to false.
  387.      */
  388.     public CompressorStreamFactory() {
  389.         this.decompressUntilEOF = null;
  390.         this.memoryLimitInKb = -1;
  391.     }

  392.     /**
  393.      * Constructs an instance with the provided decompress Concatenated option.
  394.      *
  395.      * @param decompressUntilEOF if true, decompress until the end of the input; if false, stop after the first stream and leave the input position to point to
  396.      *                           the next byte after the stream. This setting applies to the gzip, bzip2 and XZ formats only.
  397.      * @since 1.10
  398.      */
  399.     public CompressorStreamFactory(final boolean decompressUntilEOF) {
  400.         this(decompressUntilEOF, -1);
  401.     }

  402.     /**
  403.      * Constructs an instance with the provided decompress Concatenated option.
  404.      *
  405.      * @param decompressUntilEOF if true, decompress until the end of the input; if false, stop after the first stream and leave the input position to point to
  406.      *                           the next byte after the stream. This setting applies to the gzip, bzip2 and XZ formats only.
  407.      * @param memoryLimitInKb    Some streams require allocation of potentially significant byte arrays/tables, and they can offer checks to prevent OOMs on
  408.      *                           corrupt files. Set the maximum allowed memory allocation in KBs.
  409.      *
  410.      * @since 1.14
  411.      */
  412.     public CompressorStreamFactory(final boolean decompressUntilEOF, final int memoryLimitInKb) {
  413.         this.decompressUntilEOF = decompressUntilEOF;
  414.         // Also copy to existing variable so can continue to use that as the
  415.         // current value
  416.         this.decompressConcatenated = decompressUntilEOF;
  417.         this.memoryLimitInKb = memoryLimitInKb;
  418.     }

  419.     /**
  420.      * Creates a compressor input stream from an input stream, auto-detecting the compressor type from the first few bytes of the stream. The InputStream must
  421.      * support marks, like BufferedInputStream.
  422.      *
  423.      * @param in the input stream
  424.      * @return the compressor input stream
  425.      * @throws CompressorException      if the compressor name is not known
  426.      * @throws IllegalArgumentException if the stream is null or does not support mark
  427.      * @since 1.1
  428.      */
  429.     public CompressorInputStream createCompressorInputStream(final InputStream in) throws CompressorException {
  430.         return createCompressorInputStream(detect(in), in);
  431.     }

  432.     /**
  433.      * Creates a compressor input stream from an input stream, auto-detecting the compressor type from the first few bytes of the stream while limiting the
  434.      * detected type to the provided set of compressor names. The InputStream must support marks, like BufferedInputStream.
  435.      *
  436.      * @param in              the input stream
  437.      * @param compressorNames compressor names to limit autodetection
  438.      * @return the compressor input stream
  439.      * @throws CompressorException      if the autodetected compressor is not in the provided set of compressor names
  440.      * @throws IllegalArgumentException if the stream is null or does not support mark
  441.      * @since 1.25.0
  442.      */
  443.     public CompressorInputStream createCompressorInputStream(final InputStream in, final Set<String> compressorNames) throws CompressorException {
  444.         return createCompressorInputStream(detect(in, compressorNames), in);
  445.     }

  446.     /**
  447.      * Creates a compressor input stream from a compressor name and an input stream.
  448.      *
  449.      * @param name of the compressor, i.e. {@value #GZIP}, {@value #BZIP2}, {@value #XZ}, {@value #LZMA}, {@value #PACK200}, {@value #SNAPPY_RAW},
  450.      *             {@value #SNAPPY_FRAMED}, {@value #Z}, {@value #LZ4_BLOCK}, {@value #LZ4_FRAMED}, {@value #ZSTANDARD}, {@value #DEFLATE64} or
  451.      *             {@value #DEFLATE}
  452.      * @param in   the input stream
  453.      * @return compressor input stream
  454.      * @throws CompressorException      if the compressor name is not known or not available, or if there's an IOException or MemoryLimitException thrown during
  455.      *                                  initialization
  456.      * @throws IllegalArgumentException if the name or input stream is null
  457.      */
  458.     public CompressorInputStream createCompressorInputStream(final String name, final InputStream in) throws CompressorException {
  459.         return createCompressorInputStream(name, in, decompressConcatenated);
  460.     }

  461.     @Override
  462.     public CompressorInputStream createCompressorInputStream(final String name, final InputStream in, final boolean actualDecompressConcatenated)
  463.             throws CompressorException {
  464.         if (name == null || in == null) {
  465.             throw new IllegalArgumentException("Compressor name and stream must not be null.");
  466.         }

  467.         try {

  468.             if (GZIP.equalsIgnoreCase(name)) {
  469.                 return new GzipCompressorInputStream(in, actualDecompressConcatenated);
  470.             }

  471.             if (BZIP2.equalsIgnoreCase(name)) {
  472.                 return new BZip2CompressorInputStream(in, actualDecompressConcatenated);
  473.             }

  474.             if (BROTLI.equalsIgnoreCase(name)) {
  475.                 if (!BrotliUtils.isBrotliCompressionAvailable()) {
  476.                     throw new CompressorException("Brotli compression is not available." + YOU_NEED_BROTLI_DEC);
  477.                 }
  478.                 return new BrotliCompressorInputStream(in);
  479.             }

  480.             if (XZ.equalsIgnoreCase(name)) {
  481.                 if (!XZUtils.isXZCompressionAvailable()) {
  482.                     throw new CompressorException("XZ compression is not available." + YOU_NEED_XZ_JAVA);
  483.                 }
  484.                 return new XZCompressorInputStream(in, actualDecompressConcatenated, memoryLimitInKb);
  485.             }

  486.             if (ZSTANDARD.equalsIgnoreCase(name)) {
  487.                 if (!ZstdUtils.isZstdCompressionAvailable()) {
  488.                     throw new CompressorException("Zstandard compression is not available." + YOU_NEED_ZSTD_JNI);
  489.                 }
  490.                 return new ZstdCompressorInputStream(in);
  491.             }

  492.             if (LZMA.equalsIgnoreCase(name)) {
  493.                 if (!LZMAUtils.isLZMACompressionAvailable()) {
  494.                     throw new CompressorException("LZMA compression is not available" + YOU_NEED_XZ_JAVA);
  495.                 }
  496.                 return new LZMACompressorInputStream(in, memoryLimitInKb);
  497.             }

  498.             if (PACK200.equalsIgnoreCase(name)) {
  499.                 return new Pack200CompressorInputStream(in);
  500.             }

  501.             if (SNAPPY_RAW.equalsIgnoreCase(name)) {
  502.                 return new SnappyCompressorInputStream(in);
  503.             }

  504.             if (SNAPPY_FRAMED.equalsIgnoreCase(name)) {
  505.                 return new FramedSnappyCompressorInputStream(in);
  506.             }

  507.             if (Z.equalsIgnoreCase(name)) {
  508.                 return new ZCompressorInputStream(in, memoryLimitInKb);
  509.             }

  510.             if (DEFLATE.equalsIgnoreCase(name)) {
  511.                 return new DeflateCompressorInputStream(in);
  512.             }

  513.             if (DEFLATE64.equalsIgnoreCase(name)) {
  514.                 return new Deflate64CompressorInputStream(in);
  515.             }

  516.             if (LZ4_BLOCK.equalsIgnoreCase(name)) {
  517.                 return new BlockLZ4CompressorInputStream(in);
  518.             }

  519.             if (LZ4_FRAMED.equalsIgnoreCase(name)) {
  520.                 return new FramedLZ4CompressorInputStream(in, actualDecompressConcatenated);
  521.             }

  522.         } catch (final IOException e) {
  523.             throw new CompressorException("Could not create CompressorInputStream.", e);
  524.         }
  525.         final CompressorStreamProvider compressorStreamProvider = getCompressorInputStreamProviders().get(toKey(name));
  526.         if (compressorStreamProvider != null) {
  527.             return compressorStreamProvider.createCompressorInputStream(name, in, actualDecompressConcatenated);
  528.         }

  529.         throw new CompressorException("Compressor: " + name + " not found.");
  530.     }

  531.     /**
  532.      * Creates a compressor output stream from a compressor name and an output stream.
  533.      *
  534.      * @param name the compressor name, i.e. {@value #GZIP}, {@value #BZIP2}, {@value #XZ}, {@value #PACK200}, {@value #SNAPPY_FRAMED}, {@value #LZ4_BLOCK},
  535.      *             {@value #LZ4_FRAMED}, {@value #ZSTANDARD} or {@value #DEFLATE}
  536.      * @param out  the output stream
  537.      * @return the compressor output stream
  538.      * @throws CompressorException      if the archiver name is not known
  539.      * @throws IllegalArgumentException if the archiver name or stream is null
  540.      */
  541.     @Override
  542.     public CompressorOutputStream<?> createCompressorOutputStream(final String name, final OutputStream out) throws CompressorException {
  543.         if (name == null || out == null) {
  544.             throw new IllegalArgumentException("Compressor name and stream must not be null.");
  545.         }

  546.         try {

  547.             if (GZIP.equalsIgnoreCase(name)) {
  548.                 return new GzipCompressorOutputStream(out);
  549.             }

  550.             if (BZIP2.equalsIgnoreCase(name)) {
  551.                 return new BZip2CompressorOutputStream(out);
  552.             }

  553.             if (XZ.equalsIgnoreCase(name)) {
  554.                 return new XZCompressorOutputStream(out);
  555.             }

  556.             if (PACK200.equalsIgnoreCase(name)) {
  557.                 return new Pack200CompressorOutputStream(out);
  558.             }

  559.             if (LZMA.equalsIgnoreCase(name)) {
  560.                 return new LZMACompressorOutputStream(out);
  561.             }

  562.             if (DEFLATE.equalsIgnoreCase(name)) {
  563.                 return new DeflateCompressorOutputStream(out);
  564.             }

  565.             if (SNAPPY_FRAMED.equalsIgnoreCase(name)) {
  566.                 return new FramedSnappyCompressorOutputStream(out);
  567.             }

  568.             if (LZ4_BLOCK.equalsIgnoreCase(name)) {
  569.                 return new BlockLZ4CompressorOutputStream(out);
  570.             }

  571.             if (LZ4_FRAMED.equalsIgnoreCase(name)) {
  572.                 return new FramedLZ4CompressorOutputStream(out);
  573.             }

  574.             if (ZSTANDARD.equalsIgnoreCase(name)) {
  575.                 return new ZstdCompressorOutputStream(out);
  576.             }
  577.         } catch (final IOException e) {
  578.             throw new CompressorException("Could not create CompressorOutputStream", e);
  579.         }
  580.         final CompressorStreamProvider compressorStreamProvider = getCompressorOutputStreamProviders().get(toKey(name));
  581.         if (compressorStreamProvider != null) {
  582.             return compressorStreamProvider.createCompressorOutputStream(name, out);
  583.         }
  584.         throw new CompressorException("Compressor: " + name + " not found.");
  585.     }

  586.     public SortedMap<String, CompressorStreamProvider> getCompressorInputStreamProviders() {
  587.         if (compressorInputStreamProviders == null) {
  588.             compressorInputStreamProviders = Collections.unmodifiableSortedMap(findAvailableCompressorInputStreamProviders());
  589.         }
  590.         return compressorInputStreamProviders;
  591.     }

  592.     public SortedMap<String, CompressorStreamProvider> getCompressorOutputStreamProviders() {
  593.         if (compressorOutputStreamProviders == null) {
  594.             compressorOutputStreamProviders = Collections.unmodifiableSortedMap(findAvailableCompressorOutputStreamProviders());
  595.         }
  596.         return compressorOutputStreamProviders;
  597.     }

  598.     /** For tests. */
  599.     boolean getDecompressConcatenated() {
  600.         return decompressConcatenated;
  601.     }

  602.     public Boolean getDecompressUntilEOF() {
  603.         return decompressUntilEOF;
  604.     }

  605.     @Override
  606.     public Set<String> getInputStreamCompressorNames() {
  607.         return Sets.newHashSet(GZIP, BROTLI, BZIP2, XZ, LZMA, PACK200, DEFLATE, SNAPPY_RAW, SNAPPY_FRAMED, Z, LZ4_BLOCK, LZ4_FRAMED, ZSTANDARD, DEFLATE64);
  608.     }

  609.     @Override
  610.     public Set<String> getOutputStreamCompressorNames() {
  611.         return Sets.newHashSet(GZIP, BZIP2, XZ, LZMA, PACK200, DEFLATE, SNAPPY_FRAMED, LZ4_BLOCK, LZ4_FRAMED, ZSTANDARD);
  612.     }

  613.     /**
  614.      * Sets whether to decompress the full input or only the first stream in formats supporting multiple concatenated input streams.
  615.      *
  616.      * <p>
  617.      * This setting applies to the gzip, bzip2 and XZ formats only.
  618.      * </p>
  619.      *
  620.      * @param decompressConcatenated if true, decompress until the end of the input; if false, stop after the first stream and leave the input position to point
  621.      *                               to the next byte after the stream
  622.      * @since 1.5
  623.      * @deprecated 1.10 use the {@link #CompressorStreamFactory(boolean)} constructor instead
  624.      * @throws IllegalStateException if the constructor {@link #CompressorStreamFactory(boolean)} was used to create the factory
  625.      */
  626.     @Deprecated
  627.     public void setDecompressConcatenated(final boolean decompressConcatenated) {
  628.         if (this.decompressUntilEOF != null) {
  629.             throw new IllegalStateException("Cannot override the setting defined by the constructor");
  630.         }
  631.         this.decompressConcatenated = decompressConcatenated;
  632.     }

  633. }