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