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 February 23, 2025
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,',?,*,&lt;,&gt;,|,",:,~,@,!,#,[,],=,+,;, 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     * Sets the Java "user.dir" environment variable to the absolute path of the
142     * specified directory name. If the specified directory doesn't exist, this
143     * function tries to create it before setting the environment variable to it.
144     *
145     * @param directory_name The directory name to set the "user.dir" to.
146     * @return Returns true if the directory was set and false if it was not.
147     * @see getCurrentDirectory
148     */
149    public static boolean setCurrentDirectory(String directory_name) {
150        boolean result = false;  // Boolean indicating whether directory was set
151 
152        File directory = new File(directory_name).getAbsoluteFile();
153        if (directory.exists() || directory.mkdirs()) {
154            result = (System.setProperty("user.dir", directory.getAbsolutePath()) != null);
155        }
156 
157        return result;
158    }
159 
160    /**
161     * Return the current working directory as defined by the "user.dir" environment
162     * variable as a File object.
163     *
164     * @return A File object representing the "user.dir" directory.
165     * @see setCurrentDirectory
166     */
167    public static File getCurrentDirectory() {
168        String path = System.getProperty("user.dir");
169        return new File(path).getAbsoluteFile();
170    }
171    
172    /**
173     * Copy a file from the source to the destination locations.
174     *
175     * @param src The source file. May not be null.
176     * @param dst The destination file to copy the source file to. May not be null.
177     * @throws java.io.IOException if there is any problem reading from or writing to the
178     * files.
179     */
180    public static void copy(File src, File dst) throws IOException {
181        requireNonNull(src, "src == null");
182        requireNonNull(dst, "dst == null");
183
184        // If src and dst are the same; hence no copying is required.
185        if (sameFile(src, dst))
186            return;
187
188        InputStream in = null;
189        OutputStream out = null;
190        try {
191
192            in = new FileInputStream(src);
193            out = new FileOutputStream(dst);
194            copy(in, out);
195
196        } finally {
197            if (in != null)
198                in.close();
199            if (out != null)
200                out.close();
201        }
202    }
203
204    /**
205     * Copy the input stream to the output stream.
206     *
207     * @param in  The source input stream. May not be null.
208     * @param out The destination output stream. May not be null.
209     * @throws java.io.IOException if there is any problem reading from or writing to the
210     * streams.
211     */
212    public static void copy(InputStream in, OutputStream out) throws IOException {
213        requireNonNull(in, "in == null");
214        requireNonNull(out, "out == null");
215
216        byte[] buf = new byte[1024];
217        int len;
218        while ((len = in.read(buf)) > 0) {
219            out.write(buf, 0, len);
220        }
221    }
222
223    /**
224     * Recursively copy the contents of an entire directory tree from source to
225     * destination. This also works if a single file is passed as the source and
226     * destination.
227     *
228     * @param source      The source directory or file to copy. May not be null.
229     * @param destination The destination directory or file. May not be null.
230     * @throws java.io.IOException If there is any problem copying the directories or
231     * files.
232     */
233    public static void copyDirectory(File source, File destination) throws IOException {
234        if (source.isDirectory()) {
235            if (!destination.exists()) {
236                boolean success = destination.mkdirs();
237                if (!success)
238                    throw new IOException("Could not create directory " + destination);
239            }
240
241            String files[] = source.list();
242
243            for (String file : files) {
244                File srcFile = new File(source, file);
245                File destFile = new File(destination, file);
246
247                copyDirectory(srcFile, destFile);
248            }
249        } else {
250            copy(source, destination);
251        }
252    }
253
254    /**
255     * Attempt to rename a file from the source File location to the destination File
256     * location. If the standard atomic rename fails, this method falls back on copying
257     * the file which is dangerous as it is not atomic. The fall back will not work if the
258     * source or destination is a directory and an exception is thrown in that case.
259     *
260     * @param src The source file to be renamed. Upon exit, this object MAY point to the
261     *            same location as "dst" or it may be left unchanged. Either way, barring
262     *            an exception, the file will no longer exist at the input src path
263     *            location. May not be null.
264     * @param dst The destination path to rename the source file to. May not be null.
265     * @throws java.io.IOException if there is any problem renaming the file.
266     */
267    public static void rename(File src, File dst) throws IOException {
268        requireNonNull(dst, "dst == null");
269
270        if (!src.exists())
271            throw new FileNotFoundException("\"" + src.getPath() + "\" not found.");
272
273        //  If the source and destination are the same, no rename is required.
274        if (sameFile(src, dst))
275            return;
276
277        //  Make sure we can write to the destination location.
278        if (dst.exists()) {
279            //  The file already exists, can we write to it?
280            if (!dst.canWrite())
281                throw new IOException("Can not write to \"" + dst.getPath() + "\".");
282
283        } else {
284            //  The file does not already exist.  Can we create a file here?
285            boolean success = dst.createNewFile();
286            if (!success)
287                throw new IOException("Can not write to \"" + dst.getPath() + "\".");
288            dst.delete();
289        }
290
291        try {
292            //  Try to use the atomic rename first.
293            boolean success = src.renameTo(dst);
294            if (!success) {
295                //  Delete the destination file, if it exists.  Then wait a second and try again.
296                if (dst.exists())
297                    dst.delete();
298                Thread.sleep(1000);
299                success = src.renameTo(dst);
300
301                if (!success) {
302                    //  If renaming the file doesn't work, fall back on copying it.
303                    //  This doesn't work for directories.
304                    if (!src.isFile())
305                        throw new IOException("Move failed, source is dir: \"" + src.getPath() + "\".");
306                    if (dst.isDirectory())
307                        throw new IOException("Move failed, destination is dir: \"" + dst.getPath() + "\".");
308                    copy(src, dst);
309                    src.delete();
310                }
311            }
312        } catch (InterruptedException e) {
313            throw new InterruptedIOException();
314        }
315    }
316
317    /**
318     * Returns a list of String objects each of which represents a line in the specified
319     * file. The file may optionally be GZIP compressed.
320     *
321     * @param file The possibly GZIP compressed file to be read in. May not be null.
322     * @return A list of String objects, one for each line in the specified file.
323     * @throws java.io.IOException if there is any problem reading from the file.
324     */
325    public static List<String> readlines(File file) throws IOException {
326        requireNonNull(file, "file == null");
327        FileInputStream instream = new FileInputStream(file);
328        return readlines(instream);
329    }
330
331    /**
332     * Returns a list of String objects each of which represents a line in the specified
333     * input stream. The input stream may optionally be GZIP compressed.
334     *
335     * @param instream The input stream to be read in. May optionally be GZIP compressed.
336     *                 May not be null.
337     * @return A list of String objects, one for each line in the specified input stream.
338     * @throws java.io.IOException if there is any problem reading from the stream.
339     */
340    public static List<String> readlines(InputStream instream) throws IOException {
341        requireNonNull(instream, "instream == null");
342        
343        //  Deal with the possibility that the input stream is GZIP compressed.
344        InputStream in = new BufferedInputStream(instream);
345        if (isGZIPCompressed((BufferedInputStream)in))
346            in = new GZIPInputStream(in);
347        
348        List<String> output = new ArrayList();
349        try (BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
350            String aLine;
351            do {
352                aLine = reader.readLine();
353                if (aLine != null)
354                    output.add(aLine);
355
356            } while (aLine != null);
357
358        }
359
360        return output;
361    }
362
363    /**
364     * Write out a list of String objects which represent a line to the specified output
365     * stream. A line ending character is appended to each line.
366     *
367     * @param outFile The path to the output file to be written to. May not be null.
368     * @param lines   The list of String objects to be written out. May not be null.
369     * @throws IOException if there is any problem writing to the output file.
370     */
371    public static void writelines(String outFile, List<String> lines) throws IOException {
372        requireNonNull(outFile, "outFile == null");
373        requireNonNull(lines, "lines ==  null");
374
375        File file = new File(outFile);
376        writelines(file, lines);
377    }
378
379    /**
380     * Write out a list of String objects which represent a line to the specified output
381     * stream. A line ending character is appended to each line.
382     *
383     * @param outFile The output file to be written to. May not be null.
384     * @param lines   The list of String objects to be written out. May not be null.
385     * @throws IOException if there is any problem writing to the output file.
386     */
387    public static void writelines(File outFile, List<String> lines) throws IOException {
388        requireNonNull(outFile, "outFile == null");
389        requireNonNull(lines, "lines ==  null");
390
391        try (FileOutputStream outstream = new FileOutputStream(outFile)) {
392            writelines(outstream, lines);
393        }
394    }
395
396    /**
397     * Write out a list of String objects which represent a line to the specified output
398     * stream. A line ending character is appended to each line.
399     *
400     * @param outstream The output stream to be written to. May not be null.
401     * @param lines     The list of String objects to be written out. May not be null.
402     * @throws IOException if there is any problem writing to the output stream.
403     */
404    public static void writelines(OutputStream outstream, List<String> lines) throws IOException {
405        requireNonNull(outstream, "outstream == null");
406        requireNonNull(lines, "lines == null");
407
408        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outstream))) {
409
410            //  Write out the lines to the output file.
411            int size = lines.size();
412            for (int i = 0; i < size; ++i) {
413                writer.write(lines.get(i));
414                writer.newLine();
415            }
416
417        }
418
419    }
420
421    /**
422     * Returns a buffer that contains the contents of the specified file.
423     *
424     * @param file The file to be read into a new ByteBuffer. May not be null.
425     * @return The buffer containing the contents of the specified file.
426     * @throws java.io.IOException if there is any problem reading from the file.
427     */
428    public static byte[] file2Buffer(File file) throws IOException {
429
430        //  Read the file into a byte buffer.
431        if (file.length() > (long)Integer.MAX_VALUE)
432            throw new IOException(file.getName() + " is to large to convert into a ByteBuffer!");
433
434        int fileSize = (int)file.length();
435        byte[] buffer = new byte[fileSize];
436        try (FileInputStream fis = new FileInputStream(file)) {
437            fis.read(buffer, 0, fileSize);
438        }
439
440        return buffer;
441    }
442
443    /**
444     * Write the entire contents of a byte array to the specified file.
445     *
446     * @param buffer The buffer to be written to the file. May not be null.
447     * @param file   The file to write the buffer to (it's existing contents will be
448     *               overwritten). May not be null.
449     * @throws java.io.IOException if there is any problem writing to the file.
450     */
451    public static void buffer2File(byte[] buffer, File file) throws IOException {
452        requireNonNull(buffer, "buffer == null");
453        requireNonNull(file, "file == null");
454
455        try (FileOutputStream fos = new FileOutputStream(file)) {
456            fos.write(buffer);
457        }
458
459    }
460
461    /**
462     * Returns a ByteBuffer that contains the contents of the specified file.
463     *
464     * @param file The file to be read into a new ByteBuffer. May not be null.
465     * @return The ByteBuffer containing the contents of the specified file.
466     * @throws java.io.IOException if there is any problem reading from the file.
467     */
468    public static ByteBuffer file2ByteBuffer(File file) throws IOException {
469
470        //  Read the file into a byte buffer.
471        if (file.length() > Integer.MAX_VALUE)
472            throw new IOException(file.getName() + " is to large to convert into a ByteBuffer!");
473
474        int fileSize = (int)file.length();
475        byte[] mybytearray = new byte[fileSize];
476        try (FileInputStream fis = new FileInputStream(file)) {
477            fis.read(mybytearray, 0, fileSize);
478        }
479
480        ByteBuffer buffer = ByteBuffer.wrap(mybytearray);
481
482        return buffer;
483    }
484
485    /**
486     * Write the contents of a ByteBuffer, from the beginning up to the current position,
487     * to the specified file.
488     *
489     * @param buffer The buffer to be written to the file. May not be null.
490     * @param file   The file to write the buffer to (it's existing contents will be
491     *               overwritten). May not be null.
492     * @throws java.io.IOException if there is any problem writing the byte buffer to the
493     * file.
494     */
495    public static void byteBuffer2File(ByteBuffer buffer, File file) throws IOException {
496        requireNonNull(file);
497        int pos = buffer.position();
498        buffer.flip();
499        byte[] mybytearray = new byte[buffer.limit()];
500        buffer.get(mybytearray);
501
502        try (FileOutputStream fos = new FileOutputStream(file)) {
503            fos.write(mybytearray);
504        }
505
506        buffer.position(pos);
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     * @return A 2D double array of the values in the table file.
516     * @throws IOException if there is any problem reading from the file or parsing the
517     * results.
518     */
519    public static double[][] loadtxt(String filePath) throws IOException {
520        requireNonNull(filePath, "filePath == null");
521        return loadtxt(new File(filePath), "\\s+", 0);
522    }
523    
524    /**
525     * Read in an optionally GZIP compressed, delimited text file that contains a regular
526     * array of Double values. The delimiter may be any whitespace.
527     *
528     * @param filePath Path to/Name of the possibly GZIP compressed table file being read
529     *                 in.
530     * @param skiprows Indicates the number of rows to skip at the top of the file (to
531     *                 skip over a header for instance).
532     * @return A 2D double array of the values in the table file.
533     * @throws IOException if there is any problem reading from the file or parsing the
534     * results.
535     */
536    public static double[][] loadtxt(String filePath, int skiprows) throws IOException {
537        requireNonNull(filePath, "filePath == null");
538        return loadtxt(new File(filePath), "\\s+", skiprows);
539    }
540    
541    /**
542     * Read in an optionally GZIP compressed, delimited text file that contains a regular
543     * array of Double values. The delimiter may be any whitespace.
544     *
545     * @param filePath Path to/Name of the possibly GZIP compressed table file being read
546     *                 in.
547     * @param delimiter The delimiter to use to separate columns (such as "," for commas,
548     *                  or "\\s+" for any whitespace.
549     * @param skiprows Indicates the number of rows to skip at the top of the file (to
550     *                 skip over a header for instance).
551     * @return A 2D double array of the values in the table file.
552     * @throws IOException if there is any problem reading from the file or parsing the
553     * results.
554     */
555    public static double[][] loadtxt(String filePath, String delimiter, int skiprows) throws IOException {
556        requireNonNull(filePath, "filePath == null");
557        requireNonNull(delimiter, "delimiter == null");
558        return loadtxt(new File(filePath), delimiter, skiprows);
559    }
560    
561    /**
562     * Read in an optionally GZIP compressed, delimited text file that contains a regular
563     * array of Double values. The delimiter may be any whitespace.
564     *
565     * @param txtFile  The possibly GZIP compressed table file being read in.
566     * @return A 2D double array of the values in the table file.
567     * @throws IOException if there is any problem reading from the file or parsing the
568     * results.
569     */
570    public static double[][] loadtxt(File txtFile) throws IOException {
571        requireNonNull(txtFile, "txtFile == null");
572        return loadtxt(txtFile, "\\s+", 0);
573    }
574    
575    /**
576     * Read in an optionally GZIP compressed, delimited text file that contains a regular
577     * array of Double values. The delimiter may be any whitespace.
578     *
579     * @param txtFile  The possibly GZIP compressed table file being read in.
580     * @param skiprows  Indicates the number of rows to skip at the top of the file (to
581     *                  skip over a header for instance).
582     * @return A 2D double array of the values in the table file.
583     * @throws IOException if there is any problem reading from the file or parsing the
584     * results.
585     */
586    public static double[][] loadtxt(File txtFile, int skiprows) throws IOException {
587        requireNonNull(txtFile, "txtFile == null");
588        return loadtxt(txtFile, "\\s+", skiprows);
589    }
590    
591    /**
592     * Read in an optionally GZIP compressed, delimited text file that contains a regular
593     * array of Double values.
594     *
595     * @param txtFile   The possibly GZIP compressed table file being read in.
596     * @param delimiter The delimiter to use to separate columns (such as "," for commas,
597     *                  or "\\s+" for any whitespace.
598     * @param skiprows  Indicates the number of rows to skip at the top of the file (to
599     *                  skip over a header for instance).
600     * @return A 2D double array of the values in the table file.
601     * @throws IOException if there is any problem reading from the file or parsing the
602     * results.
603     */
604    public static double[][] loadtxt(File txtFile, String delimiter, int skiprows) throws IOException {
605        requireNonNull(txtFile, "txtFile == null");
606        requireNonNull(delimiter, "delimiter == null");
607        
608        //  Deal with bad input to skiprows.
609        if (skiprows < 0)
610            throw new IllegalArgumentException("skiprows must be >= 0");
611        
612        //  Read in the lines from the file.
613        List<String> lines = readlines(txtFile);
614        
615        int size = lines.size();
616        int numRows = size - skiprows;
617        if (numRows < 1)
618            return new double[0][0];
619        
620        double[][] output = new double[numRows][];
621        int col_size = -1;
622        
623        //  Loop over all the rows in the file.
624        for (int row=0; row < numRows; ++row) {
625            String[] cols = lines.get(row + skiprows).trim().split(delimiter);
626            
627            if (col_size < 0)
628                col_size = cols.length;
629            else if (col_size != cols.length)
630                throw new IOException("The number of columns on line " + (row+skiprows+1) + 
631                        " is different from previous lines.");
632            
633            //  Allocate memory for this row of the table.
634            double[] row_v = new double[col_size];
635            output[row] = row_v;
636            
637            //  Convert all the column strings to floats.
638            for (int col=0; col < col_size; ++col) {
639                row_v[col] = Double.parseDouble(cols[col]);
640            }
641        }
642        
643        return output;
644    }
645    
646    /**
647     * Create a temporary directory using the specified prefix.
648     *
649     * @param prefix The prefix string to be used in generating the file's name; must be
650     *               at least three characters long. May not be null.
651     * @return a reference to a temporary directory using the specified prefix.
652     * @throws java.io.IOException if there is any problem creating the temporary
653     * directory.
654     */
655    public static File createTempDirectory(String prefix) throws IOException {
656        requireNonNull(prefix, "prefix == null");
657
658        File tempFile = File.createTempFile(prefix, "", null);
659        if (!tempFile.delete() || !tempFile.mkdir())
660            throw new IOException("Could not create temporary directory: " + tempFile.getPath());
661
662        return tempFile;
663    }
664
665    /**
666     * Recursively deletes the directory tree indicated by the specified path. The
667     * directory and all of it's contents are deleted.
668     *
669     * @param path The directory to be deleted. If a plain file is passed in rather than a
670     *             directory, it is simply deleted. May not be null.
671     * @return true if the directory and it's contents were successfully deleted.
672     */
673    public static boolean deleteDirectory(File path) {
674        if (path.exists() && path.isDirectory()) {
675            File[] files = path.listFiles();
676            for (File file : files) {
677                if (file.isDirectory())
678                    deleteDirectory(file);
679                else
680                    file.delete();
681            }
682        }
683        return (path.delete());
684    }
685
686    /**
687     * Returns <code>true</code> if and only if the two File objects refer to the same
688     * file in the file system.
689     *
690     * @param f1 The first file to check. May not be null.
691     * @param f2 The 2nd file to check with the 1st one. May not be null.
692     * @return true if and only if the two File objects refer to the same file in the file
693     *         system.
694     * @throws IOException If an I/O error occurs, which is possible because the
695     * construction of the canonical pathname may require file system queries.
696     */
697    public static boolean sameFile(File f1, File f2) throws IOException {
698        return f1.getCanonicalPath().equals(f2.getCanonicalPath());
699    }
700
701    /**
702     * GZIP compress the src file writing to the destination file. The source &amp;
703     * destination files may be identical.
704     *
705     * @param src The source file to be compressed. May not be null.
706     * @param dst The destination file to compress the source file to. This may be
707     *            identical to the source location if the change is to be made in place.
708     *            May not be null.
709     * @throws java.io.IOException if there is any problem reading from or writing to the
710     * files.
711     */
712    public static void gzip(File src, File dst) throws IOException {
713        requireNonNull(src, "src == null");
714        requireNonNull(dst, "dst = null");
715
716        File dst2 = dst;
717        if (sameFile(src, dst))
718            dst2 = File.createTempFile("gzip", null);
719
720        InputStream in = null;
721        OutputStream out = null;
722        try {
723
724            in = new FileInputStream(src);
725            out = new FileOutputStream(dst2);
726            gzip(in, out);
727
728        } finally {
729            if (in != null)
730                in.close();
731            if (out != null)
732                out.close();
733
734            if (dst2 != dst) {
735                //  If we used a temporary file, move it to the originally
736                //  desired location.
737                rename(dst2, dst);
738
739                //  Delete the temporary file.
740                dst2.delete();
741            }
742        }
743    }
744
745    /**
746     * Copy a file to the specified destination directory while GZIP compressing the file.
747     * The original file is not modified. The file in the output directory will have the
748     * same name as the source file, but with ".gz" appended.
749     *
750     * @param src     The source file to be compressed. May not be null.
751     * @param destDir The directory to copy the compressed file into. May not be null.
752     * @throws java.io.IOException if there is any problem reading from or writing to the
753     * files.
754     */
755    public static void copyAndGzip(File src, File destDir) throws IOException {
756        requireNonNull(src, "src == null");
757        requireNonNull(destDir, "destDir == null");
758
759        File dst = new File(destDir, src.getName() + ".gz");
760
761        InputStream in = null;
762        OutputStream out = null;
763        try {
764
765            in = new FileInputStream(src);
766            out = new FileOutputStream(dst);
767            gzip(in, out);
768
769        } finally {
770            if (in != null)
771                in.close();
772            if (out != null)
773                out.close();
774        }
775
776    }
777
778    /**
779     * Un-GZIP the compressed src file writing the uncompressed data to the destination
780     * file. The source &amp; destination files may be identical.
781     *
782     * @param src The GZIP compressed source file to be de-compressed. May not be null.
783     * @param dst The destination file to uncompress the source file to. This may be
784     *            identical to the source location if the change is to be made in place.
785     *            May not be null.
786     * @throws java.io.IOException if there is any problem reading from or writing to the
787     * files.
788     */
789    public static void ungzip(File src, File dst) throws IOException {
790        requireNonNull(src, "src == null");
791        requireNonNull(dst, "dst == null");
792
793        File dst2 = dst;
794        if (sameFile(src, dst))
795            dst2 = File.createTempFile("gzip", null);
796
797        InputStream in = null;
798        OutputStream out = null;
799        try {
800
801            in = new FileInputStream(src);
802            out = new FileOutputStream(dst2);
803            ungzip(in, out);
804
805        } finally {
806            if (in != null)
807                in.close();
808            if (out != null)
809                out.close();
810
811            if (dst2 != dst) {
812                //  If we used a temporary file, move it to the originally
813                //  desired location.
814                rename(dst2, dst);
815
816                //  Delete the temporary file.
817                dst2.delete();
818            }
819        }
820    }
821
822    /**
823     * Copy a file to the specified destination directory while decompressing the GZIP
824     * file. The source file is not modified. A new file is created in the destination
825     * directory that has the same name as the source file, but without the ".gz"
826     * extension (if there is one).
827     *
828     * @param src     The source file GZIP file. May not be null.
829     * @param destDir The directory to copy the uncompressed file into. May not be null.
830     * @throws java.io.IOException if there is any problem reading from or writing to the
831     * files.
832     */
833    public static void copyAndUngzip(File src, File destDir) throws IOException {
834        requireNonNull(src, "src == null");
835        requireNonNull(destDir, "destDir == null");
836
837        String name = src.getName();
838        File dst;
839        if (name.toLowerCase().endsWith(".gz")) {
840            int len = name.length();
841            dst = new File(destDir, src.getName().substring(0, len - 3));
842        } else
843            dst = new File(destDir, name);
844
845        InputStream in = null;
846        OutputStream out = null;
847        try {
848
849            in = new FileInputStream(src);
850            out = new FileOutputStream(dst);
851            ungzip(in, out);
852
853        } finally {
854            if (in != null)
855                in.close();
856            if (out != null)
857                out.close();
858        }
859    }
860
861    /**
862     * GZIP compress the input stream and write it to the output stream.
863     *
864     * @param in  The source input stream. May not be null.
865     * @param out The destination output stream where GZIP compressed data is to be
866     *            written. May not be null.
867     * @throws java.io.IOException if there is any problem reading from or writing to the
868     * streams.
869     */
870    public static void gzip(InputStream in, OutputStream out) throws IOException {
871        requireNonNull(in, "in == null");
872        requireNonNull(out, "out == null");
873
874        GZIPOutputStream gzipOut = new GZIPOutputStream(out);
875        copy(in, gzipOut);
876        gzipOut.finish();
877    }
878
879    /**
880     * Un-GZIP the compressed input stream and write the uncompressed data to the
881     * specified output stream.
882     *
883     * @param in  The source input stream pointing to GZIP compressed data. May not be
884     *            null.
885     * @param out The destination output stream where uncompressed data is to be written.
886     *            May not be null.
887     * @throws java.io.IOException if there is any problem reading from or writing to the
888     * streams.
889     */
890    public static void ungzip(InputStream in, OutputStream out) throws IOException {
891        requireNonNull(in, "in == null");
892        requireNonNull(out, "out == null");
893
894        GZIPInputStream gzipIn = new GZIPInputStream(in);
895        copy(gzipIn, out);
896    }
897
898    /**
899     * Returns <code>true</code> if the specified input file is pointing at a GZIP
900     * compressed data set.
901     *
902     * @param file The input file to be tested. May not be null.
903     * @return true if the specified input file is pointing at a GZIP compressed data set.
904     * @throws java.io.IOException if there is a problem reading from the specified file.
905     */
906    public static boolean isGZIPCompressed(File file) throws IOException {
907        requireNonNull(file, "file == null");
908        try (BufferedInputStream is = new BufferedInputStream(new FileInputStream(file), 4)) {
909            return isGZIPCompressed(is);
910        }
911    }
912
913    /**
914     * Returns <code>true</code> if the specified input stream is pointing at a GZIP
915     * compressed data set.
916     *
917     * @param in The input stream to be tested. May not be null.
918     * @return true if the specified input stream is pointing at a GZIP compressed data
919     *         set.
920     * @throws java.io.IOException if there is a problem reading from the input stream.
921     */
922    public static boolean isGZIPCompressed(BufferedInputStream in) throws IOException {
923        requireNonNull(in, "in == null");
924        byte[] signature = new byte[2];
925
926        in.mark(4);
927        in.read(signature); //read the signature
928        in.reset();
929
930        return isGZIPCompressed(signature);
931    }
932
933    /**
934     * Returns <code>true</code> if the specified array of bytes represent a GZIP
935     * compressed data set.
936     *
937     * @param bytes the array of bytes to be tested. May not be null.
938     * @return true if the specified array of bytes represent a GZIP compressed data set.
939     */
940    public static boolean isGZIPCompressed(byte[] bytes) {
941        if (bytes.length < 2) {
942            return false;
943
944        } else {
945            return ((bytes[0] == (byte)(GZIPInputStream.GZIP_MAGIC))
946                    && (bytes[1] == (byte)(GZIPInputStream.GZIP_MAGIC >> 8)));
947        }
948    }
949}