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