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