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;
024
025import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
026import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
027import org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream;
028import org.apache.commons.compress.compressors.deflate.DeflateCompressorOutputStream;
029import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
030import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
031import org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream;
032import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
033import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
034import org.apache.commons.compress.compressors.xz.XZUtils;
035import org.apache.commons.compress.compressors.pack200.Pack200CompressorInputStream;
036import org.apache.commons.compress.compressors.pack200.Pack200CompressorOutputStream;
037import org.apache.commons.compress.compressors.snappy.FramedSnappyCompressorInputStream;
038import org.apache.commons.compress.compressors.snappy.SnappyCompressorInputStream;
039import org.apache.commons.compress.compressors.z.ZCompressorInputStream;
040import org.apache.commons.compress.utils.IOUtils;
041
042/**
043 * <p>Factory to create Compressor[In|Out]putStreams from names. To add other
044 * implementations you should extend CompressorStreamFactory and override the
045 * appropriate methods (and call their implementation from super of course).</p>
046 * 
047 * Example (Compressing a file):
048 * 
049 * <pre>
050 * final OutputStream out = new FileOutputStream(output); 
051 * CompressorOutputStream cos = 
052 *      new CompressorStreamFactory().createCompressorOutputStream(CompressorStreamFactory.BZIP2, out);
053 * IOUtils.copy(new FileInputStream(input), cos);
054 * cos.close();
055 * </pre>
056 * 
057 * Example (Decompressing a file):
058 * <pre>
059 * final InputStream is = new FileInputStream(input); 
060 * CompressorInputStream in = 
061 *      new CompressorStreamFactory().createCompressorInputStream(CompressorStreamFactory.BZIP2, is);
062 * IOUtils.copy(in, new FileOutputStream(output));
063 * in.close();
064 * </pre>
065 * 
066 * @Immutable
067 */
068public class CompressorStreamFactory {
069
070    /**
071     * Constant used to identify the BZIP2 compression algorithm.
072     * @since 1.1
073     */
074    public static final String BZIP2 = "bzip2";
075
076    /**
077     * Constant used to identify the GZIP compression algorithm.
078     * @since 1.1
079     */
080    public static final String GZIP = "gz";
081    /**
082     * Constant used to identify the PACK200 compression algorithm.
083     * @since 1.3
084     */
085    public static final String PACK200 = "pack200";
086
087    /**
088     * Constant used to identify the XZ compression method.
089     * @since 1.4
090     */
091    public static final String XZ = "xz";
092
093    /**
094     * Constant used to identify the LZMA compression method.
095     * @since 1.6
096     */
097    public static final String LZMA = "lzma";
098
099    /**
100     * Constant used to identify the "framed" Snappy compression method.
101     * @since 1.7
102     */
103    public static final String SNAPPY_FRAMED = "snappy-framed";
104
105    /**
106     * Constant used to identify the "raw" Snappy compression method.
107     * @since 1.7
108     */
109    public static final String SNAPPY_RAW = "snappy-raw";
110
111    /**
112     * Constant used to identify the traditional Unix compress method.
113     * @since 1.7
114     */
115    public static final String Z = "z";
116
117    /**
118     * Constant used to identify the Deflate compress method.
119     * @since 1.9
120     */
121    public static final String DEFLATE = "deflate";
122
123    private boolean decompressConcatenated = false;
124
125    /**
126     * Whether to decompress the full input or only the first stream
127     * in formats supporting multiple concatenated input streams.
128     *
129     * <p>This setting applies to the gzip, bzip2 and xz formats only.</p>
130     *
131     * @param       decompressConcatenated
132     *                          if true, decompress until the end of the
133     *                          input; if false, stop after the first
134     *                          stream and leave the input position to point
135     *                          to the next byte after the stream
136     * @since 1.5
137     */
138    public void setDecompressConcatenated(boolean decompressConcatenated) {
139        this.decompressConcatenated = decompressConcatenated;
140    }
141
142    /**
143     * Create an compressor input stream from an input stream, autodetecting
144     * the compressor type from the first few bytes of the stream. The InputStream
145     * must support marks, like BufferedInputStream.
146     * 
147     * @param in the input stream
148     * @return the compressor input stream
149     * @throws CompressorException if the compressor name is not known
150     * @throws IllegalArgumentException if the stream is null or does not support mark
151     * @since 1.1
152     */
153    public CompressorInputStream createCompressorInputStream(final InputStream in)
154            throws CompressorException {
155        if (in == null) {
156            throw new IllegalArgumentException("Stream must not be null.");
157        }
158
159        if (!in.markSupported()) {
160            throw new IllegalArgumentException("Mark is not supported.");
161        }
162
163        final byte[] signature = new byte[12];
164        in.mark(signature.length);
165        try {
166            int signatureLength = IOUtils.readFully(in, signature);
167            in.reset();
168
169            if (BZip2CompressorInputStream.matches(signature, signatureLength)) {
170                return new BZip2CompressorInputStream(in, decompressConcatenated);
171            }
172
173            if (GzipCompressorInputStream.matches(signature, signatureLength)) {
174                return new GzipCompressorInputStream(in, decompressConcatenated);
175            }
176
177            if (XZUtils.isXZCompressionAvailable() &&
178                XZCompressorInputStream.matches(signature, signatureLength)) {
179                return new XZCompressorInputStream(in, decompressConcatenated);
180            }
181
182            if (Pack200CompressorInputStream.matches(signature, signatureLength)) {
183                return new Pack200CompressorInputStream(in);
184            }
185
186            if (FramedSnappyCompressorInputStream.matches(signature, signatureLength)) {
187                return new FramedSnappyCompressorInputStream(in);
188            }
189
190            if (ZCompressorInputStream.matches(signature, signatureLength)) {
191                return new ZCompressorInputStream(in);
192            }
193
194        } catch (IOException e) {
195            throw new CompressorException("Failed to detect Compressor from InputStream.", e);
196        }
197
198        throw new CompressorException("No Compressor found for the stream signature.");
199    }
200
201    /**
202     * Create a compressor input stream from a compressor name and an input stream.
203     * 
204     * @param name of the compressor, i.e. "gz", "bzip2", "xz",
205     *        "lzma", "snappy-raw", "snappy-framed", "pack200", "z"
206     * @param in the input stream
207     * @return compressor input stream
208     * @throws CompressorException if the compressor name is not known
209     * @throws IllegalArgumentException if the name or input stream is null
210     */
211    public CompressorInputStream createCompressorInputStream(final String name,
212            final InputStream in) throws CompressorException {
213        if (name == null || in == null) {
214            throw new IllegalArgumentException(
215                    "Compressor name and stream must not be null.");
216        }
217
218        try {
219
220            if (GZIP.equalsIgnoreCase(name)) {
221                return new GzipCompressorInputStream(in, decompressConcatenated);
222            }
223
224            if (BZIP2.equalsIgnoreCase(name)) {
225                return new BZip2CompressorInputStream(in, decompressConcatenated);
226            }
227
228            if (XZ.equalsIgnoreCase(name)) {
229                return new XZCompressorInputStream(in, decompressConcatenated);
230            }
231
232            if (LZMA.equalsIgnoreCase(name)) {
233                return new LZMACompressorInputStream(in);
234            }
235
236            if (PACK200.equalsIgnoreCase(name)) {
237                return new Pack200CompressorInputStream(in);
238            }
239
240            if (SNAPPY_RAW.equalsIgnoreCase(name)) {
241                return new SnappyCompressorInputStream(in);
242            }
243
244            if (SNAPPY_FRAMED.equalsIgnoreCase(name)) {
245                return new FramedSnappyCompressorInputStream(in);
246            }
247
248            if (Z.equalsIgnoreCase(name)) {
249                return new ZCompressorInputStream(in);
250            }
251
252            if (DEFLATE.equalsIgnoreCase(name)) {
253                return new DeflateCompressorInputStream(in);
254            }
255
256        } catch (IOException e) {
257            throw new CompressorException(
258                    "Could not create CompressorInputStream.", e);
259        }
260        throw new CompressorException("Compressor: " + name + " not found.");
261    }
262
263    /**
264     * Create an compressor output stream from an compressor name and an input stream.
265     * 
266     * @param name the compressor name, i.e. "gz", "bzip2", "xz", or "pack200"
267     * @param out the output stream
268     * @return the compressor output stream
269     * @throws CompressorException if the archiver name is not known
270     * @throws IllegalArgumentException if the archiver name or stream is null
271     */
272    public CompressorOutputStream createCompressorOutputStream(
273            final String name, final OutputStream out)
274            throws CompressorException {
275        if (name == null || out == null) {
276            throw new IllegalArgumentException(
277                    "Compressor name and stream must not be null.");
278        }
279
280        try {
281
282            if (GZIP.equalsIgnoreCase(name)) {
283                return new GzipCompressorOutputStream(out);
284            }
285
286            if (BZIP2.equalsIgnoreCase(name)) {
287                return new BZip2CompressorOutputStream(out);
288            }
289
290            if (XZ.equalsIgnoreCase(name)) {
291                return new XZCompressorOutputStream(out);
292            }
293
294            if (PACK200.equalsIgnoreCase(name)) {
295                return new Pack200CompressorOutputStream(out);
296            }
297
298            if (DEFLATE.equalsIgnoreCase(name)) {
299                return new DeflateCompressorOutputStream(out);
300            }
301
302        } catch (IOException e) {
303            throw new CompressorException(
304                    "Could not create CompressorOutputStream", e);
305        }
306        throw new CompressorException("Compressor: " + name + " not found.");
307    }
308}