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