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