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     */
019    package org.apache.commons.compress.compressors;
020    
021    import java.io.IOException;
022    import java.io.InputStream;
023    import java.io.OutputStream;
024    
025    import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
026    import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
027    import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
028    import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
029    import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
030    import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
031    import org.apache.commons.compress.compressors.xz.XZUtils;
032    import org.apache.commons.compress.compressors.pack200.Pack200CompressorInputStream;
033    import 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     */
061    public class CompressorStreamFactory {
062    
063        /**
064         * Constant used to identify the BZIP2 compression algorithm.
065         * @since Commons Compress 1.1
066         */
067        public static final String BZIP2 = "bzip2";
068    
069        /**
070         * Constant used to identify the GZIP compression algorithm.
071         * @since Commons Compress 1.1
072         */
073        public static final String GZIP = "gz";
074        /**
075         * Constant used to identify the PACK200 compression algorithm.
076         * @since Commons Compress 1.3
077         */
078        public static final String PACK200 = "pack200";
079    
080        /**
081         * Constant used to identify the XZ compression method.
082         * @since Commons Compress 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 Commons Compress 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 Commons Compress 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    }