001/**
002 * Please feel free to use any fragment of the code in this file that you need in your own
003 * work. As far as I am concerned, it's in the public domain. No permission is necessary
004 * or required. Credit is always appreciated if you use a large chunk or base a
005 * significant product on one of my examples, but that's not required either.
006 *
007 * This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
008 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
009 * PURPOSE.
010 *
011 * --- Joseph A. Huwaldt
012 */
013package jahuwaldt.io;
014
015import java.io.*;
016import java.nio.ByteBuffer;
017import java.util.ArrayList;
018import java.util.List;
019import static java.util.Objects.requireNonNull;
020import java.util.zip.GZIPInputStream;
021import java.util.zip.GZIPOutputStream;
022
023/**
024 * This is a utility class of static methods for working with files.
025 *
026 * <p> Modified by: Joseph A. Huwaldt </p>
027 *
028 * @author Joseph A. Huwaldt, Date: November 27, 2009
029 * @version July 7, 2018
030 */
031public final class FileUtils {
032    //  This class now requires Java 1.7 or later!
033
034    /**
035     * A list of characters that are illegal on some of the file systems supported by this
036     * program. The characters in this list include:
037     * \n,\r,\t,\0,\f,',?,*,<,>,|,",:,~,@,!,#,[,],=,+,;, and ','.
038     */
039    private static final char[] ILLEGAL_CHARACTERS
040            = {'\n', '\r', '\t', '\0', '\f', '`', '?', '*', '<', '>', '|', '\"', ':', '~', '@', '!', '#',
041                '[', ']', '=', '+', ';', ','};
042
043    /**
044     * Prevent instantiation of this utility class.
045     */
046    private FileUtils() {
047    }
048
049    /**
050     * Replace any potentially illegal characters from a file name with '_'.
051     *
052     * @param name The file name to be cleaned of potentially illegal characters. May not
053     *             be null.
054     * @return The input file name with potentially illegal characters replaced with "_".
055     */
056    public static String cleanFileName(String name) {
057        if (name.length() == 0)
058            return name;
059        for (char c : ILLEGAL_CHARACTERS) {
060            name = name.replace(c, '_');
061        }
062        return name;
063    }
064
065    /**
066     * Returns true if the supplied file name contains characters that are illegal on some
067     * file systems.
068     *
069     * @param name The file name to be checked for potentially illegal characters. May not
070     *             be null.
071     * @return true if the file name contains potentially illegal characters, false if it
072     *         is safe.
073     */
074    public static boolean hasIllegalChars(String name) {
075        if (name.length() == 0)
076            return false;
077        for (char c : ILLEGAL_CHARACTERS) {
078            if (name.indexOf(c) >= 0)
079                return true;
080        }
081        return false;
082    }
083
084    /**
085     * Return the file name of the specified file without the extension.
086     *
087     * @param file The file to have the name without extension returned. May not be null.
088     * @return The name of the specified file without the extension (if there is one).
089     */
090    public static String getFileNameWithoutExtension(File file) {
091        String name = file.getName();
092        return getFileNameWithoutExtension(name);
093    }
094
095    /**
096     * Return the file name of the specified file without the extension.
097     *
098     * @param name The file name to have the name without extension returned. May not be
099     *             null.
100     * @return The name of the specified file without the extension (if there is one).
101     */
102    public static String getFileNameWithoutExtension(String name) {
103        int index = name.lastIndexOf('.');
104        if (index > 0 && index <= name.length() - 2) {
105            name = name.substring(0, index);
106        }
107        return name;
108    }
109
110    /**
111     * Return the extension portion of the file's name. The extension will always be
112     * returned in lower case and without the ".".
113     *
114     * @param file The file for which the extension is to be returned. May not be null.
115     * @return The extension portion of the file's name without the "." or "" if there is
116     *         no extension.
117     * @see #getExtension(java.lang.String) 
118     */
119    public static String getExtension(File file) {
120        requireNonNull(file, "file == null");
121        return getExtension(file.getName());
122    }
123
124    /**
125     * Return the extension portion of the file's name. The extension will always be
126     * returned in lower case and without the ".".
127     *
128     * @param name The name of the file, including the extension. May not be null.
129     * @return The extension portion of the file's name without the "." or "" if there is
130     *         no extension.
131     * @see #getExtension(java.io.File) 
132     */
133    public static String getExtension(String name) {
134        int i = name.lastIndexOf('.');
135        if (i > 0 && i < name.length() - 1)
136            return name.substring(i + 1).toLowerCase();
137        return "";
138    }
139
140    /**
141     * Copy a file from the source to the destination locations.
142     *
143     * @param src The source file. May not be null.
144     * @param dst The destination file to copy the source file to. May not be null.
145     * @throws java.io.IOException if there is any problem reading from or writing to the
146     * files.
147     */
148    public static void copy(File src, File dst) throws IOException {
149        requireNonNull(src, "src == null");
150        requireNonNull(dst, "dst == null");
151
152        // If src and dst are the same; hence no copying is required.
153        if (sameFile(src, dst))
154            return;
155
156        InputStream in = null;
157        OutputStream out = null;
158        try {
159
160            in = new FileInputStream(src);
161            out = new FileOutputStream(dst);
162            copy(in, out);
163
164        } finally {
165            if (in != null)
166                in.close();
167            if (out != null)
168                out.close();
169        }
170    }
171
172    /**
173     * Copy the input stream to the output stream.
174     *
175     * @param in  The source input stream. May not be null.
176     * @param out The destination output stream. May not be null.
177     * @throws java.io.IOException if there is any problem reading from or writing to the
178     * streams.
179     */
180    public static void copy(InputStream in, OutputStream out) throws IOException {
181        requireNonNull(in, "in == null");
182        requireNonNull(out, "out == null");
183
184        byte[] buf = new byte[1024];
185        int len;
186        while ((len = in.read(buf)) > 0) {
187            out.write(buf, 0, len);
188        }
189    }
190
191    /**
192     * Recursively copy the contents of an entire directory tree from source to
193     * destination. This also works if a single file is passed as the source and
194     * destination.
195     *
196     * @param source      The source directory or file to copy. May not be null.
197     * @param destination The destination directory or file. May not be null.
198     * @throws java.io.IOException If there is any problem copying the directories or
199     * files.
200     */
201    public static void copyDirectory(File source, File destination) throws IOException {
202        if (source.isDirectory()) {
203            if (!destination.exists()) {
204                boolean success = destination.mkdirs();
205                if (!success)
206                    throw new IOException("Could not create directory " + destination);
207            }
208
209            String files[] = source.list();
210
211            for (String file : files) {
212                File srcFile = new File(source, file);
213                File destFile = new File(destination, file);
214
215                copyDirectory(srcFile, destFile);
216            }
217        } else {
218            copy(source, destination);
219        }
220    }
221
222    /**
223     * Attempt to rename a file from the source File location to the destination File
224     * location. If the standard atomic rename fails, this method falls back on copying
225     * the file which is dangerous as it is not atomic. The fall back will not work if the
226     * source or destination is a directory and an exception is thrown in that case.
227     *
228     * @param src The source file to be renamed. Upon exit, this object MAY point to the
229     *            same location as "dst" or it may be left unchanged. Either way, barring
230     *            an exception, the file will no longer exist at the input src path
231     *            location. May not be null.
232     * @param dst The destination path to rename the source file to. May not be null.
233     * @throws java.io.IOException if there is any problem renaming the file.
234     */
235    public static void rename(File src, File dst) throws IOException {
236        requireNonNull(dst, "dst == null");
237
238        if (!src.exists())
239            throw new FileNotFoundException("\"" + src.getPath() + "\" not found.");
240
241        //  If the source and destination are the same, no rename is required.
242        if (sameFile(src, dst))
243            return;
244
245        //  Make sure we can write to the destination location.
246        if (dst.exists()) {
247            //  The file already exists, can we write to it?
248            if (!dst.canWrite())
249                throw new IOException("Can not write to \"" + dst.getPath() + "\".");
250
251        } else {
252            //  The file does not already exist.  Can we create a file here?
253            boolean success = dst.createNewFile();
254            if (!success)
255                throw new IOException("Can not write to \"" + dst.getPath() + "\".");
256            dst.delete();
257        }
258
259        try {
260            //  Try to use the atomic rename first.
261            boolean success = src.renameTo(dst);
262            if (!success) {
263                //  Delete the destination file, if it exists.  Then wait a second and try again.
264                if (dst.exists())
265                    dst.delete();
266                Thread.sleep(1000);
267                success = src.renameTo(dst);
268
269                if (!success) {
270                    //  If renaming the file doesn't work, fall back on copying it.
271                    //  This doesn't work for directories.
272                    if (!src.isFile())
273                        throw new IOException("Move failed, source is dir: \"" + src.getPath() + "\".");
274                    if (dst.isDirectory())
275                        throw new IOException("Move failed, destination is dir: \"" + dst.getPath() + "\".");
276                    copy(src, dst);
277                    src.delete();
278                }
279            }
280        } catch (InterruptedException e) {
281            throw new InterruptedIOException();
282        }
283    }
284
285    /**
286     * Returns a list of String objects each of which represents a line in the specified
287     * file. The file may optionally be GZIP compressed.
288     *
289     * @param file The possibly GZIP compressed file to be read in. May not be null.
290     * @return A list of String objects, one for each line in the specified file.
291     * @throws java.io.IOException if there is any problem reading from the file.
292     */
293    public static List<String> readlines(File file) throws IOException {
294        requireNonNull(file, "file == null");
295        FileInputStream instream = new FileInputStream(file);
296        return readlines(instream);
297    }
298
299    /**
300     * Returns a list of String objects each of which represents a line in the specified
301     * input stream. The input stream may optionally be GZIP compressed.
302     *
303     * @param instream The input stream to be read in. May optionally be GZIP compressed.
304     *                 May not be null.
305     * @return A list of String objects, one for each line in the specified input stream.
306     * @throws java.io.IOException if there is any problem reading from the stream.
307     */
308    public static List<String> readlines(InputStream instream) throws IOException {
309        requireNonNull(instream, "instream == null");
310        
311        //  Deal with the possibility that the input stream is GZIP compressed.
312        InputStream in = new BufferedInputStream(instream);
313        if (isGZIPCompressed((BufferedInputStream)in))
314            in = new GZIPInputStream(in);
315        
316        List<String> output = new ArrayList();
317        try (BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
318            String aLine;
319            do {
320                aLine = reader.readLine();
321                if (aLine != null)
322                    output.add(aLine);
323
324            } while (aLine != null);
325
326        }
327
328        return output;
329    }
330
331    /**
332     * Write out a list of String objects which represent a line to the specified output
333     * stream. A line ending character is appended to each line.
334     *
335     * @param outFile The path to the output file to be written to. May not be null.
336     * @param lines   The list of String objects to be written out. May not be null.
337     * @throws IOException if there is any problem writing to the output file.
338     */
339    public static void writelines(String outFile, List<String> lines) throws IOException {
340        requireNonNull(outFile, "outFile == null");
341        requireNonNull(lines, "lines ==  null");
342
343        File file = new File(outFile);
344        writelines(file, lines);
345    }
346
347    /**
348     * Write out a list of String objects which represent a line to the specified output
349     * stream. A line ending character is appended to each line.
350     *
351     * @param outFile The output file to be written to. May not be null.
352     * @param lines   The list of String objects to be written out. May not be null.
353     * @throws IOException if there is any problem writing to the output file.
354     */
355    public static void writelines(File outFile, List<String> lines) throws IOException {
356        requireNonNull(outFile, "outFile == null");
357        requireNonNull(lines, "lines ==  null");
358
359        try (FileOutputStream outstream = new FileOutputStream(outFile)) {
360            writelines(outstream, lines);
361        }
362    }
363
364    /**
365     * Write out a list of String objects which represent a line to the specified output
366     * stream. A line ending character is appended to each line.
367     *
368     * @param outstream The output stream to be written to. May not be null.
369     * @param lines     The list of String objects to be written out. May not be null.
370     * @throws IOException if there is any problem writing to the output stream.
371     */
372    public static void writelines(OutputStream outstream, List<String> lines) throws IOException {
373        requireNonNull(outstream, "outstream == null");
374        requireNonNull(lines, "lines == null");
375
376        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outstream))) {
377
378            //  Write out the lines to the output file.
379            int size = lines.size();
380            for (int i = 0; i < size; ++i) {
381                writer.write(lines.get(i));
382                writer.newLine();
383            }
384
385        }
386
387    }
388
389    /**
390     * Returns a buffer that contains the contents of the specified file.
391     *
392     * @param file The file to be read into a new ByteBuffer. May not be null.
393     * @return The buffer containing the contents of the specified file.
394     * @throws java.io.IOException if there is any problem reading from the file.
395     */
396    public static byte[] file2Buffer(File file) throws IOException {
397
398        //  Read the file into a byte buffer.
399        if (file.length() > (long)Integer.MAX_VALUE)
400            throw new IOException(file.getName() + " is to large to convert into a ByteBuffer!");
401
402        int fileSize = (int)file.length();
403        byte[] buffer = new byte[fileSize];
404        try (FileInputStream fis = new FileInputStream(file)) {
405            fis.read(buffer, 0, fileSize);
406        }
407
408        return buffer;
409    }
410
411    /**
412     * Write the entire contents of a byte array to the specified file.
413     *
414     * @param buffer The buffer to be written to the file. May not be null.
415     * @param file   The file to write the buffer to (it's existing contents will be
416     *               overwritten). May not be null.
417     * @throws java.io.IOException if there is any problem writing to the file.
418     */
419    public static void buffer2File(byte[] buffer, File file) throws IOException {
420        requireNonNull(buffer, "buffer == null");
421        requireNonNull(file, "file == null");
422
423        try (FileOutputStream fos = new FileOutputStream(file)) {
424            fos.write(buffer);
425        }
426
427    }
428
429    /**
430     * Returns a ByteBuffer that contains the contents of the specified file.
431     *
432     * @param file The file to be read into a new ByteBuffer. May not be null.
433     * @return The ByteBuffer containing the contents of the specified file.
434     * @throws java.io.IOException if there is any problem reading from the file.
435     */
436    public static ByteBuffer file2ByteBuffer(File file) throws IOException {
437
438        //  Read the file into a byte buffer.
439        if (file.length() > Integer.MAX_VALUE)
440            throw new IOException(file.getName() + " is to large to convert into a ByteBuffer!");
441
442        int fileSize = (int)file.length();
443        byte[] mybytearray = new byte[fileSize];
444        try (FileInputStream fis = new FileInputStream(file)) {
445            fis.read(mybytearray, 0, fileSize);
446        }
447
448        ByteBuffer buffer = ByteBuffer.wrap(mybytearray);
449
450        return buffer;
451    }
452
453    /**
454     * Write the contents of a ByteBuffer, from the beginning up to the current position,
455     * to the specified file.
456     *
457     * @param buffer The buffer to be written to the file. May not be null.
458     * @param file   The file to write the buffer to (it's existing contents will be
459     *               overwritten). May not be null.
460     * @throws java.io.IOException if there is any problem writing the byte buffer to the
461     * file.
462     */
463    public static void byteBuffer2File(ByteBuffer buffer, File file) throws IOException {
464        requireNonNull(file);
465        int pos = buffer.position();
466        buffer.flip();
467        byte[] mybytearray = new byte[buffer.limit()];
468        buffer.get(mybytearray);
469
470        try (FileOutputStream fos = new FileOutputStream(file)) {
471            fos.write(mybytearray);
472        }
473
474        buffer.position(pos);
475    }
476
477    /**
478     * Read in an optionally GZIP compressed, delimited text file that contains a regular
479     * array of Double values. The delimiter may be any whitespace.
480     *
481     * @param filePath Path to/Name of the possibly GZIP compressed table file being read
482     *                 in.
483     * @return A 2D double array of the values in the table file.
484     * @throws IOException if there is any problem reading from the file or parsing the
485     * results.
486     */
487    public static double[][] loadtxt(String filePath) throws IOException {
488        requireNonNull(filePath, "filePath == null");
489        return loadtxt(new File(filePath), "\\s+", 0);
490    }
491    
492    /**
493     * Read in an optionally GZIP compressed, delimited text file that contains a regular
494     * array of Double values. The delimiter may be any whitespace.
495     *
496     * @param filePath Path to/Name of the possibly GZIP compressed table file being read
497     *                 in.
498     * @param skiprows Indicates the number of rows to skip at the top of the file (to
499     *                 skip over a header for instance).
500     * @return A 2D double array of the values in the table file.
501     * @throws IOException if there is any problem reading from the file or parsing the
502     * results.
503     */
504    public static double[][] loadtxt(String filePath, int skiprows) throws IOException {
505        requireNonNull(filePath, "filePath == null");
506        return loadtxt(new File(filePath), "\\s+", skiprows);
507    }
508    
509    /**
510     * Read in an optionally GZIP compressed, delimited text file that contains a regular
511     * array of Double values. The delimiter may be any whitespace.
512     *
513     * @param filePath Path to/Name of the possibly GZIP compressed table file being read
514     *                 in.
515     * @param delimiter The delimiter to use to separate columns (such as "," for commas,
516     *                  or "\\s+" for any whitespace.
517     * @param skiprows Indicates the number of rows to skip at the top of the file (to
518     *                 skip over a header for instance).
519     * @return A 2D double array of the values in the table file.
520     * @throws IOException if there is any problem reading from the file or parsing the
521     * results.
522     */
523    public static double[][] loadtxt(String filePath, String delimiter, int skiprows) throws IOException {
524        requireNonNull(filePath, "filePath == null");
525        requireNonNull(delimiter, "delimiter == null");
526        return loadtxt(new File(filePath), delimiter, skiprows);
527    }
528    
529    /**
530     * Read in an optionally GZIP compressed, delimited text file that contains a regular
531     * array of Double values. The delimiter may be any whitespace.
532     *
533     * @param txtFile  The possibly GZIP compressed table file being read in.
534     * @return A 2D double array of the values in the table file.
535     * @throws IOException if there is any problem reading from the file or parsing the
536     * results.
537     */
538    public static double[][] loadtxt(File txtFile) throws IOException {
539        requireNonNull(txtFile, "txtFile == null");
540        return loadtxt(txtFile, "\\s+", 0);
541    }
542    
543    /**
544     * Read in an optionally GZIP compressed, delimited text file that contains a regular
545     * array of Double values. The delimiter may be any whitespace.
546     *
547     * @param txtFile  The possibly GZIP compressed table file being read in.
548     * @param skiprows  Indicates the number of rows to skip at the top of the file (to
549     *                  skip over a header for instance).
550     * @return A 2D double array of the values in the table file.
551     * @throws IOException if there is any problem reading from the file or parsing the
552     * results.
553     */
554    public static double[][] loadtxt(File txtFile, int skiprows) throws IOException {
555        requireNonNull(txtFile, "txtFile == null");
556        return loadtxt(txtFile, "\\s+", skiprows);
557    }
558    
559    /**
560     * Read in an optionally GZIP compressed, delimited text file that contains a regular
561     * array of Double values.
562     *
563     * @param txtFile   The possibly GZIP compressed table file being read in.
564     * @param delimiter The delimiter to use to separate columns (such as "," for commas,
565     *                  or "\\s+" for any whitespace.
566     * @param skiprows  Indicates the number of rows to skip at the top of the file (to
567     *                  skip over a header for instance).
568     * @return A 2D double array of the values in the table file.
569     * @throws IOException if there is any problem reading from the file or parsing the
570     * results.
571     */
572    public static double[][] loadtxt(File txtFile, String delimiter, int skiprows) throws IOException {
573        requireNonNull(txtFile, "txtFile == null");
574        requireNonNull(delimiter, "delimiter == null");
575        
576        //  Deal with bad input to skiprows.
577        if (skiprows < 0)
578            throw new IllegalArgumentException("skiprows must be >= 0");
579        
580        //  Read in the lines from the file.
581        List<String> lines = readlines(txtFile);
582        
583        int size = lines.size();
584        int numRows = size - skiprows;
585        if (numRows < 1)
586            return new double[0][0];
587        
588        double[][] output = new double[numRows][];
589        int col_size = -1;
590        
591        //  Loop over all the rows in the file.
592        for (int row=0; row < numRows; ++row) {
593            String[] cols = lines.get(row + skiprows).trim().split(delimiter);
594            
595            if (col_size < 0)
596                col_size = cols.length;
597            else if (col_size != cols.length)
598                throw new IOException("The number of columns on line " + (row+skiprows+1) + 
599                        " is different from previous lines.");
600            
601            //  Allocate memory for this row of the table.
602            double[] row_v = new double[col_size];
603            output[row] = row_v;
604            
605            //  Convert all the column strings to floats.
606            for (int col=0; col < col_size; ++col) {
607                row_v[col] = Double.parseDouble(cols[col]);
608            }
609        }
610        
611        return output;
612    }
613    
614    /**
615     * Create a temporary directory using the specified prefix.
616     *
617     * @param prefix The prefix string to be used in generating the file's name; must be
618     *               at least three characters long. May not be null.
619     * @return a reference to a temporary directory using the specified prefix.
620     * @throws java.io.IOException if there is any problem creating the temporary
621     * directory.
622     */
623    public static File createTempDirectory(String prefix) throws IOException {
624        requireNonNull(prefix, "prefix == null");
625
626        File tempFile = File.createTempFile(prefix, "", null);
627        if (!tempFile.delete() || !tempFile.mkdir())
628            throw new IOException("Could not create temporary directory: " + tempFile.getPath());
629
630        return tempFile;
631    }
632
633    /**
634     * Recursively deletes the directory tree indicated by the specified path. The
635     * directory and all of it's contents are deleted.
636     *
637     * @param path The directory to be deleted. If a plain file is passed in rather than a
638     *             directory, it is simply deleted. May not be null.
639     * @return true if the directory and it's contents were successfully deleted.
640     */
641    public static boolean deleteDirectory(File path) {
642        if (path.exists() && path.isDirectory()) {
643            File[] files = path.listFiles();
644            for (File file : files) {
645                if (file.isDirectory())
646                    deleteDirectory(file);
647                else
648                    file.delete();
649            }
650        }
651        return (path.delete());
652    }
653
654    /**
655     * Returns <code>true</code> if and only if the two File objects refer to the same
656     * file in the file system.
657     *
658     * @param f1 The first file to check. May not be null.
659     * @param f2 The 2nd file to check with the 1st one. May not be null.
660     * @return true if and only if the two File objects refer to the same file in the file
661     *         system.
662     * @throws IOException If an I/O error occurs, which is possible because the
663     * construction of the canonical pathname may require file system queries.
664     */
665    public static boolean sameFile(File f1, File f2) throws IOException {
666        return f1.getCanonicalPath().equals(f2.getCanonicalPath());
667    }
668
669    /**
670     * GZIP compress the src file writing to the destination file. The source &
671     * destination files may be identical.
672     *
673     * @param src The source file to be compressed. May not be null.
674     * @param dst The destination file to compress the source file to. This may be
675     *            identical to the source location if the change is to be made in place.
676     *            May not be null.
677     * @throws java.io.IOException if there is any problem reading from or writing to the
678     * files.
679     */
680    public static void gzip(File src, File dst) throws IOException {
681        requireNonNull(src, "src == null");
682        requireNonNull(dst, "dst = null");
683
684        File dst2 = dst;
685        if (sameFile(src, dst))
686            dst2 = File.createTempFile("gzip", null);
687
688        InputStream in = null;
689        OutputStream out = null;
690        try {
691
692            in = new FileInputStream(src);
693            out = new FileOutputStream(dst2);
694            gzip(in, out);
695
696        } finally {
697            if (in != null)
698                in.close();
699            if (out != null)
700                out.close();
701
702            if (dst2 != dst) {
703                //  If we used a temporary file, move it to the originally
704                //  desired location.
705                rename(dst2, dst);
706
707                //  Delete the temporary file.
708                dst2.delete();
709            }
710        }
711    }
712
713    /**
714     * Copy a file to the specified destination directory while GZIP compressing the file.
715     * The original file is not modified. The file in the output directory will have the
716     * same name as the source file, but with ".gz" appended.
717     *
718     * @param src     The source file to be compressed. May not be null.
719     * @param destDir The directory to copy the compressed file into. May not be null.
720     * @throws java.io.IOException if there is any problem reading from or writing to the
721     * files.
722     */
723    public static void copyAndGzip(File src, File destDir) throws IOException {
724        requireNonNull(src, "src == null");
725        requireNonNull(destDir, "destDir == null");
726
727        File dst = new File(destDir, src.getName() + ".gz");
728
729        InputStream in = null;
730        OutputStream out = null;
731        try {
732
733            in = new FileInputStream(src);
734            out = new FileOutputStream(dst);
735            gzip(in, out);
736
737        } finally {
738            if (in != null)
739                in.close();
740            if (out != null)
741                out.close();
742        }
743
744    }
745
746    /**
747     * Un-GZIP the compressed src file writing the uncompressed data to the destination
748     * file. The source & destination files may be identical.
749     *
750     * @param src The GZIP compressed source file to be de-compressed. May not be null.
751     * @param dst The destination file to uncompress the source file to. This may be
752     *            identical to the source location if the change is to be made in place.
753     *            May not be null.
754     * @throws java.io.IOException if there is any problem reading from or writing to the
755     * files.
756     */
757    public static void ungzip(File src, File dst) throws IOException {
758        requireNonNull(src, "src == null");
759        requireNonNull(dst, "dst == null");
760
761        File dst2 = dst;
762        if (sameFile(src, dst))
763            dst2 = File.createTempFile("gzip", null);
764
765        InputStream in = null;
766        OutputStream out = null;
767        try {
768
769            in = new FileInputStream(src);
770            out = new FileOutputStream(dst2);
771            ungzip(in, out);
772
773        } finally {
774            if (in != null)
775                in.close();
776            if (out != null)
777                out.close();
778
779            if (dst2 != dst) {
780                //  If we used a temporary file, move it to the originally
781                //  desired location.
782                rename(dst2, dst);
783
784                //  Delete the temporary file.
785                dst2.delete();
786            }
787        }
788    }
789
790    /**
791     * Copy a file to the specified destination directory while decompressing the GZIP
792     * file. The source file is not modified. A new file is created in the destination
793     * directory that has the same name as the source file, but without the ".gz"
794     * extension (if there is one).
795     *
796     * @param src     The source file GZIP file. May not be null.
797     * @param destDir The directory to copy the uncompressed file into. May not be null.
798     * @throws java.io.IOException if there is any problem reading from or writing to the
799     * files.
800     */
801    public static void copyAndUngzip(File src, File destDir) throws IOException {
802        requireNonNull(src, "src == null");
803        requireNonNull(destDir, "destDir == null");
804
805        String name = src.getName();
806        File dst;
807        if (name.toLowerCase().endsWith(".gz")) {
808            int len = name.length();
809            dst = new File(destDir, src.getName().substring(0, len - 3));
810        } else
811            dst = new File(destDir, name);
812
813        InputStream in = null;
814        OutputStream out = null;
815        try {
816
817            in = new FileInputStream(src);
818            out = new FileOutputStream(dst);
819            ungzip(in, out);
820
821        } finally {
822            if (in != null)
823                in.close();
824            if (out != null)
825                out.close();
826        }
827    }
828
829    /**
830     * GZIP compress the input stream and write it to the output stream.
831     *
832     * @param in  The source input stream. May not be null.
833     * @param out The destination output stream where GZIP compressed data is to be
834     *            written. May not be null.
835     * @throws java.io.IOException if there is any problem reading from or writing to the
836     * streams.
837     */
838    public static void gzip(InputStream in, OutputStream out) throws IOException {
839        requireNonNull(in, "in == null");
840        requireNonNull(out, "out == null");
841
842        GZIPOutputStream gzipOut = new GZIPOutputStream(out);
843        copy(in, gzipOut);
844        gzipOut.finish();
845    }
846
847    /**
848     * Un-GZIP the compressed input stream and write the uncompressed data to the
849     * specified output stream.
850     *
851     * @param in  The source input stream pointing to GZIP compressed data. May not be
852     *            null.
853     * @param out The destination output stream where uncompressed data is to be written.
854     *            May not be null.
855     * @throws java.io.IOException if there is any problem reading from or writing to the
856     * streams.
857     */
858    public static void ungzip(InputStream in, OutputStream out) throws IOException {
859        requireNonNull(in, "in == null");
860        requireNonNull(out, "out == null");
861
862        GZIPInputStream gzipIn = new GZIPInputStream(in);
863        copy(gzipIn, out);
864    }
865
866    /**
867     * Returns <code>true</code> if the specified input file is pointing at a GZIP
868     * compressed data set.
869     *
870     * @param file The input file to be tested. May not be null.
871     * @return true if the specified input file is pointing at a GZIP compressed data set.
872     * @throws java.io.IOException if there is a problem reading from the specified file.
873     */
874    public static boolean isGZIPCompressed(File file) throws IOException {
875        requireNonNull(file, "file == null");
876        try (BufferedInputStream is = new BufferedInputStream(new FileInputStream(file), 4)) {
877            return isGZIPCompressed(is);
878        }
879    }
880
881    /**
882     * Returns <code>true</code> if the specified input stream is pointing at a GZIP
883     * compressed data set.
884     *
885     * @param in The input stream to be tested. May not be null.
886     * @return true if the specified input stream is pointing at a GZIP compressed data
887     *         set.
888     * @throws java.io.IOException if there is a problem reading from the input stream.
889     */
890    public static boolean isGZIPCompressed(BufferedInputStream in) throws IOException {
891        requireNonNull(in, "in == null");
892        byte[] signature = new byte[2];
893
894        in.mark(4);
895        in.read(signature); //read the signature
896        in.reset();
897
898        return isGZIPCompressed(signature);
899    }
900
901    /**
902     * Returns <code>true</code> if the specified array of bytes represent a GZIP
903     * compressed data set.
904     *
905     * @param bytes the array of bytes to be tested. May not be null.
906     * @return true if the specified array of bytes represent a GZIP compressed data set.
907     */
908    public static boolean isGZIPCompressed(byte[] bytes) {
909        if (bytes.length < 2) {
910            return false;
911
912        } else {
913            return ((bytes[0] == (byte)(GZIPInputStream.GZIP_MAGIC))
914                    && (bytes[1] == (byte)(GZIPInputStream.GZIP_MAGIC >> 8)));
915        }
916    }
917}