001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.io;
003
004import java.io.File;
005import java.io.IOException;
006import java.io.InputStream;
007import java.io.OutputStream;
008import java.nio.charset.StandardCharsets;
009import java.nio.file.Files;
010import java.util.zip.GZIPInputStream;
011import java.util.zip.GZIPOutputStream;
012import java.util.zip.ZipEntry;
013import java.util.zip.ZipInputStream;
014import java.util.zip.ZipOutputStream;
015
016import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
017import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
018import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
019import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
020import org.openstreetmap.josm.tools.Logging;
021import org.openstreetmap.josm.tools.Utils;
022
023/**
024 * An enum representing the compression type of a resource.
025 */
026public enum Compression {
027    /**
028     * no compression
029     */
030    NONE,
031    /**
032     * bzip2 compression
033     */
034    BZIP2,
035    /**
036     * gzip compression
037     */
038    GZIP,
039    /**
040     * zip compression
041     */
042    ZIP,
043    /**
044     * xz compression
045     */
046    XZ;
047
048    /**
049     * Determines the compression type depending on the suffix of {@code name}.
050     * @param name File name including extension
051     * @return the compression type
052     */
053    public static Compression byExtension(String name) {
054        return name != null && name.endsWith(".gz")
055                ? GZIP
056                : name != null && (name.endsWith(".bz2") || name.endsWith(".bz"))
057                ? BZIP2
058                : name != null && name.endsWith(".zip")
059                ? ZIP
060                : name != null && name.endsWith(".xz")
061                ? XZ
062                : NONE;
063    }
064
065    /**
066     * Determines the compression type based on the content type (MIME type).
067     * @param contentType the content type
068     * @return the compression type
069     */
070    public static Compression forContentType(String contentType) {
071        switch (contentType) {
072        case "application/zip":
073            return ZIP;
074        case "application/x-gzip":
075            return GZIP;
076        case "application/x-bzip2":
077            return BZIP2;
078        case "application/x-xz":
079            return XZ;
080        default:
081            return NONE;
082        }
083    }
084
085    /**
086     * Returns an un-compressing {@link InputStream} for {@code in}.
087     * @param in raw input stream
088     * @return un-compressing input stream
089     *
090     * @throws IOException if any I/O error occurs
091     */
092    public InputStream getUncompressedInputStream(InputStream in) throws IOException {
093        switch (this) {
094            case BZIP2:
095                return getBZip2InputStream(in);
096            case GZIP:
097                return getGZipInputStream(in);
098            case ZIP:
099                return getZipInputStream(in);
100            case XZ:
101                return getXZInputStream(in);
102            case NONE:
103            default:
104                return in;
105        }
106    }
107
108    /**
109     * Returns a XZ input stream wrapping given input stream.
110     * @param in The raw input stream
111     * @return a XZ input stream wrapping given input stream, or {@code null} if {@code in} is {@code null}
112     * @throws IOException if the given input stream does not contain valid BZ2 header
113     * @since 13350
114     */
115    public static XZCompressorInputStream getXZInputStream(InputStream in) throws IOException {
116        if (in == null) {
117            return null;
118        }
119        return new XZCompressorInputStream(in, true);
120    }
121
122    /**
123     * Returns a Bzip2 input stream wrapping given input stream.
124     * @param in The raw input stream
125     * @return a Bzip2 input stream wrapping given input stream, or {@code null} if {@code in} is {@code null}
126     * @throws IOException if the given input stream does not contain valid BZ2 header
127     * @since 12772 (moved from {@link Utils}, there since 7867)
128     */
129    public static BZip2CompressorInputStream getBZip2InputStream(InputStream in) throws IOException {
130        if (in == null) {
131            return null;
132        }
133        return new BZip2CompressorInputStream(in, /* see #9537 */ true);
134    }
135
136    /**
137     * Returns a Gzip input stream wrapping given input stream.
138     * @param in The raw input stream
139     * @return a Gzip input stream wrapping given input stream, or {@code null} if {@code in} is {@code null}
140     * @throws IOException if an I/O error has occurred
141     * @since 12772 (moved from {@link Utils}, there since 7119)
142     */
143    public static GZIPInputStream getGZipInputStream(InputStream in) throws IOException {
144        if (in == null) {
145            return null;
146        }
147        return new GZIPInputStream(in);
148    }
149
150    /**
151     * Returns a Zip input stream wrapping given input stream.
152     * @param in The raw input stream
153     * @return a Zip input stream wrapping given input stream, or {@code null} if {@code in} is {@code null}
154     * @throws IOException if an I/O error has occurred
155     * @since 12772 (moved from {@link Utils}, there since 7119)
156     */
157    public static ZipInputStream getZipInputStream(InputStream in) throws IOException {
158        if (in == null) {
159            return null;
160        }
161        ZipInputStream zis = new ZipInputStream(in, StandardCharsets.UTF_8);
162        // Positions the stream at the beginning of first entry
163        ZipEntry ze = zis.getNextEntry();
164        if (ze != null && Logging.isDebugEnabled()) {
165            Logging.debug("Zip entry: {0}", ze.getName());
166        }
167        return zis;
168    }
169
170    /**
171     * Returns an un-compressing {@link InputStream} for the {@link File} {@code file}.
172     * @param file file
173     * @return un-compressing input stream
174     * @throws IOException if any I/O error occurs
175     */
176    public static InputStream getUncompressedFileInputStream(File file) throws IOException {
177        InputStream in = Files.newInputStream(file.toPath());
178        try {
179            return byExtension(file.getName()).getUncompressedInputStream(in);
180        } catch (IOException e) {
181            Utils.close(in);
182            throw e;
183        }
184    }
185
186    /**
187     * Returns a compressing {@link OutputStream} for {@code out}.
188     * @param out raw output stream
189     * @return compressing output stream
190     *
191     * @throws IOException if any I/O error occurs
192     */
193    public OutputStream getCompressedOutputStream(OutputStream out) throws IOException {
194        switch (this) {
195            case BZIP2:
196                return new BZip2CompressorOutputStream(out);
197            case GZIP:
198                return new GZIPOutputStream(out);
199            case ZIP:
200                return new ZipOutputStream(out, StandardCharsets.UTF_8);
201            case XZ:
202                return new XZCompressorOutputStream(out);
203            case NONE:
204            default:
205                return out;
206        }
207    }
208
209    /**
210     * Returns a compressing {@link OutputStream} for the {@link File} {@code file}.
211     * @param file file
212     * @return compressing output stream
213     *
214     * @throws IOException if any I/O error occurs
215     */
216    public static OutputStream getCompressedFileOutputStream(File file) throws IOException {
217        OutputStream out = Files.newOutputStream(file.toPath());
218        try {
219            return byExtension(file.getName()).getCompressedOutputStream(out);
220        } catch (IOException e) {
221            Utils.close(out);
222            throw e;
223        }
224    }
225}