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