FileUtils.java Source code

Java tutorial

Introduction

Here is the source code for FileUtils.java

Source

/**
 * Copyright (c) 2003 - 2007 OpenSubsystems s.r.o. Slovak Republic. All rights reserved.
 * 
 * Project: OpenSubsystems
 * 
 * $Id: FileUtils.java,v 1.12 2007/02/01 07:18:32 bastafidli Exp $
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License. 
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.logging.Logger;

/**
 * Collection of methods to make work with files easier.
 * 
 * @version $Id: FileUtils.java,v 1.12 2007/02/01 07:18:32 bastafidli Exp $
 * @author Miro Halas
 * @code.reviewer Miro Halas
 * @code.reviewed 1.7 2006/05/21 03:45:37 bastafidli
 */
public class FileUtils {
    // Configuration settings ///////////////////////////////////////////////////

    /**
     * Default 10 digit file storage distribution array. This means that if I 
     * want to name file as 10 digit number e.g. number 123 as 0000000123 or 
     * number 123456789 as 01234567890. Then the path constructed from number 
     * 1234567890 using distribution 2/2/2/4 would be 12/34/56/0123456789 
     */
    public static final int[] DEFAULT_STRORAGE_TREE_DISTRIBUTION = { 2, 2, 2, 4 };

    /**
     * How big buffer to use to process files.
     */
    public static final int BUFFER_SIZE = 65536;

    // Cached values ////////////////////////////////////////////////////////////

    /**
     * Temporary directory to use. It is guarantee that it ends with \ (or /)
     */
    protected static String s_strTempDirectory;

    // Constructors /////////////////////////////////////////////////////////////

    /**
     * Move file to a new location. If the destination is on different volume,
     * this file will be copied and then original file will be deleted.
     * If the destination already exists, this method renames it with different
     * name and leaves it in that directory and moves the new file along side 
     * the renamed one.
     * 
     * @param flCurrent - file to move
     * @param flDestination - destination file
     * @throws IOException - error message
     * @throws OSSException - error message
     */
    public static void moveFile(File flCurrent, File flDestination) throws IOException {
        // Make sure that the source exist, it might be already moved from 
        // a directory and we just don't know about it
        if (flCurrent.exists()) {
            // Next check if the destination file exists
            if (flDestination.exists()) {
                // If the destination exists, that means something went wrong
                // Rename the destination file under temporaty name and try to  
                // move the new file instead of it

                renameToTemporaryName(flDestination, "old");
            }

            // Make sure the directory exists and if not create it
            File flFolder;

            flFolder = flDestination.getParentFile();
            if ((flFolder != null) && (!flFolder.exists())) {
                if (!flFolder.mkdirs()) {
                    // Do not throw the exception if the directory already exists
                    // because it was created meanwhile for example by a different 
                    // thread
                    if (!flFolder.exists()) {
                        throw new IOException("Cannot create directory " + flFolder);
                    }
                }
            }

            // Now everything should exist so try to rename the file first
            // After testing, this renames files even between volumes C to H 
            // so we don't have to do anything else on Windows but we still
            // have to handle erro on Unix 
            if (!flCurrent.renameTo(flDestination)) {
                // Try to copy and delete since the rename doesn't work on Solaris
                // between file systems
                copyFile(flCurrent, flDestination);

                // Now delete the file
                if (!flCurrent.delete()) {
                    // Delete the destination file first since we haven't really moved
                    // the file
                    flDestination.delete();
                    throw new IOException("Cannot delete already copied file " + flCurrent);
                }
            }
        }
    }

    /**
     * Copy the current file to the destination file.
     * 
     * @param flCurrent - source file
     * @param flDestination - destination file
     * @throws IOException - error message
     * @throws OSSException - error message
     */
    public static void copyFile(File flCurrent, File flDestination) throws IOException {
        // Make sure the directory exists and if not create it
        File flFolder;

        flFolder = flDestination.getParentFile();
        if ((flFolder != null) && (!flFolder.exists())) {
            if (!flFolder.mkdirs()) {
                // Do not throw the exception if the directory already exists
                // because it was created meanwhile for example by a different 
                // thread
                if (!flFolder.exists()) {
                    throw new IOException("Cannot create directory " + flFolder);
                }
            }
        }

        // FileChannel srcChannel = null;
        // FileChannel dstChannel = null;
        FileInputStream finInput = null;

        //MHALAS: This code is not working reliably on Solaris 8 with 1.4.1_01
        // Getting exceptions from native code
        /*
        // Create channel on the source
        srcChannel = new FileInputStream(flCurrent).getChannel();
        // Create channel on the destination
        dstChannel = new FileOutputStream(flDestination).getChannel();
            
        // Copy file contents from source to destination
        dstChannel.transferFrom(srcChannel, 0, srcChannel.size());   
               
        Don't forget to close the channels if you enable this code again
        */
        try {
            finInput = new FileInputStream(flCurrent);
        } catch (IOException ioExec) {
            if (finInput != null) {
                try {
                    finInput.close();
                } catch (Throwable thr) {

                }
            }
            throw ioExec;
        }

        FileUtils.copyStreamToFile(finInput, flDestination);
    }

    /**
     * Rename the file to temporaty name with given prefix
     * 
     * @param flFileToRename - file to rename
     * @param strPrefix - prefix to use
     * @throws IOException - error message
     */
    public static void renameToTemporaryName(File flFileToRename, String strPrefix) throws IOException {
        assert strPrefix != null : "Prefix cannot be null.";

        String strParent;
        StringBuffer sbBuffer = new StringBuffer();
        File flTemp;
        int iIndex = 0;

        strParent = flFileToRename.getParent();

        // Generate new name for the file in a deterministic way
        do {
            iIndex++;
            sbBuffer.delete(0, sbBuffer.length());
            if (strParent != null) {
                sbBuffer.append(strParent);
                sbBuffer.append(File.separatorChar);
            }

            sbBuffer.append(strPrefix);
            sbBuffer.append("_");
            sbBuffer.append(iIndex);
            sbBuffer.append("_");
            sbBuffer.append(flFileToRename.getName());

            flTemp = new File(sbBuffer.toString());
        } while (flTemp.exists());

        // Now we should have unique name
        if (!flFileToRename.renameTo(flTemp)) {
            throw new IOException(
                    "Cannot rename " + flFileToRename.getAbsolutePath() + " to " + flTemp.getAbsolutePath());
        }
    }

    /** 
     * Delete all files and directories in directory but do not delete the
     * directory itself.
     * 
     * @param strDir - string that specifies directory to delete
     * @return boolean - sucess flag
     */
    public static boolean deleteDirectoryContent(String strDir) {
        return ((strDir != null) && (strDir.length() > 0)) ? deleteDirectoryContent(new File(strDir)) : false;
    }

    /** 
     * Delete all files and directories in directory but do not delete the
     * directory itself.
     * 
     * @param fDir - directory to delete
     * @return boolean - sucess flag
     */
    public static boolean deleteDirectoryContent(File fDir) {
        boolean bRetval = false;

        if (fDir != null && fDir.isDirectory()) {
            File[] files = fDir.listFiles();

            if (files != null) {
                bRetval = true;
                boolean dirDeleted;

                for (int index = 0; index < files.length; index++) {
                    if (files[index].isDirectory()) {
                        // TODO: Performance: Implement this as a queue where you add to
                        // the end and take from the beginning, it will be more efficient
                        // than the recursion
                        dirDeleted = deleteDirectoryContent(files[index]);
                        if (dirDeleted) {
                            bRetval = bRetval && files[index].delete();
                        } else {
                            bRetval = false;
                        }
                    } else {
                        bRetval = bRetval && files[index].delete();
                    }
                }
            }
        }

        return bRetval;
    }

    /**
     * Deletes all files and subdirectories under the specified directory including 
     * the specified directory
     * 
     * @param strDir - string that specifies directory to be deleted
     * @return boolean - true if directory was successfully deleted
     */
    public static boolean deleteDir(String strDir) {
        return ((strDir != null) && (strDir.length() > 0)) ? deleteDir(new File(strDir)) : false;
    }

    /**
     * Deletes all files and subdirectories under the specified directory including 
     * the specified directory
     * 
     * @param fDir - directory to be deleted
     * @return boolean - true if directory was successfully deleted
     */
    public static boolean deleteDir(File fDir) {
        boolean bRetval = false;
        if (fDir != null && fDir.exists()) {
            bRetval = deleteDirectoryContent(fDir);
            if (bRetval) {
                bRetval = bRetval && fDir.delete();
            }
        }
        return bRetval;
    }

    /**
     * Compare binary files. Both files must be files (not directories) and exist.
     * 
     * @param first  - first file
     * @param second - second file
     * @return boolean - true if files are binery equal
     * @throws IOException - error in function
     */
    public boolean isFileBinaryEqual(File first, File second) throws IOException {
        // TODO: Test: Missing test
        boolean retval = false;

        if ((first.exists()) && (second.exists()) && (first.isFile()) && (second.isFile())) {
            if (first.getCanonicalPath().equals(second.getCanonicalPath())) {
                retval = true;
            } else {
                FileInputStream firstInput = null;
                FileInputStream secondInput = null;
                BufferedInputStream bufFirstInput = null;
                BufferedInputStream bufSecondInput = null;

                try {
                    firstInput = new FileInputStream(first);
                    secondInput = new FileInputStream(second);
                    bufFirstInput = new BufferedInputStream(firstInput, BUFFER_SIZE);
                    bufSecondInput = new BufferedInputStream(secondInput, BUFFER_SIZE);

                    int firstByte;
                    int secondByte;

                    while (true) {
                        firstByte = bufFirstInput.read();
                        secondByte = bufSecondInput.read();
                        if (firstByte != secondByte) {
                            break;
                        }
                        if ((firstByte < 0) && (secondByte < 0)) {
                            retval = true;
                            break;
                        }
                    }
                } finally {
                    try {
                        if (bufFirstInput != null) {
                            bufFirstInput.close();
                        }
                    } finally {
                        if (bufSecondInput != null) {
                            bufSecondInput.close();
                        }
                    }
                }
            }
        }

        return retval;
    }

    /**
     * Get path which represents temporary directory. It is guarantee that it 
     * ends with \ (or /).
     * 
     * @return String
     */
    public static String getTemporaryDirectory() {
        return s_strTempDirectory;
    }

    /**
     * Copy any input stream to output file. Once the data will be copied
     * the stream will be closed.
     * 
     * @param input  - InputStream to copy from
     * @param output - File to copy to
     * @throws IOException - error in function
     * @throws OSSMultiException - double error in function
     */
    public static void copyStreamToFile(InputStream input, File output) throws IOException {
        FileOutputStream foutOutput = null;

        // open input file as stream safe - it can throw some IOException
        try {
            foutOutput = new FileOutputStream(output);
        } catch (IOException ioExec) {
            if (foutOutput != null) {
                try {
                    foutOutput.close();
                } catch (IOException ioExec2) {

                }
            }

            throw ioExec;
        }

        // all streams including os are closed in copyStreamToStream function 
        // in any case
        copyStreamToStream(input, foutOutput);
    }

    /**
     * Copy any input stream to output stream. Once the data will be copied
     * both streams will be closed.
     * 
     * @param input  - InputStream to copy from
     * @param output - OutputStream to copy to
     * @throws IOException - io error in function
     * @throws OSSMultiException - double error in function
     */
    public static void copyStreamToStream(InputStream input, OutputStream output) throws IOException {
        InputStream is = null;
        OutputStream os = null;
        int ch;

        try {
            if (input instanceof BufferedInputStream) {
                is = input;
            } else {
                is = new BufferedInputStream(input);
            }
            if (output instanceof BufferedOutputStream) {
                os = output;
            } else {
                os = new BufferedOutputStream(output);
            }

            while ((ch = is.read()) != -1) {
                os.write(ch);
            }
            os.flush();
        } finally {
            IOException exec1 = null;
            IOException exec2 = null;
            try {
                // because this close can throw exception we do next close in 
                // finally statement
                if (os != null) {
                    try {
                        os.close();
                    } catch (IOException exec) {
                        exec1 = exec;
                    }
                }
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException exec) {
                        exec2 = exec;
                    }
                }
            }
            if ((exec1 != null) && (exec2 != null)) {
                throw exec1;
            } else if (exec1 != null) {
                throw exec1;
            } else if (exec2 != null) {
                throw exec2;
            }
        }
    }
}