File Input Output Java

/**
 * 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;
         }
      }
   }
}