/*
 * $Id$
 *
 * Copyright 2006, The jCoderZ.org Project. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *    * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following
 *      disclaimer in the documentation and/or other materials
 *      provided with the distribution.
 *    * Neither the name of the jCoderZ.org Project nor the names of
 *      its contributors may be used to endorse or promote products
 *      derived from this software without specific prior written
 *      permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.jcoderz.commons.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;


/**
 * This class collects some nifty file utility functions.
 *
 * TODO: Cleanup check vs. IoUtil
 *
 * @author Michael Griffel
 * @author Andreas Mandel
 */
public final class FileUtils
{
   private static final int RND_FILENAME_FACTOR = 100000;

   private static final int BUFFER_SIZE = 4096;

   /**
    * Only utility functions -- no instances allowed.
    */
   private FileUtils ()
   {
      // no instances allowed - provides only static utility functions
   }

   /**
    * Copies the file or directory <code>src</code> to the
    * directory <code>destinationDir</code>.
    *
    * @param src the source file or directory.
    * @param destinationDir the destination directory.
    * @throws IOException in case of an I/O error.
    */
   public static void copy (File src, File destinationDir)
         throws IOException
   {
      if (src.isFile())
      {
         copyFile(src, destinationDir);
      }
      else if (src.isDirectory())
      {
         final File subdir = new File(destinationDir, src.getName());
         if (!subdir.exists())
         {
            if (!subdir.mkdir())
            {
               throw new IOException("Failed to create subdir '" + subdir
                     + "'.");
            }
         }
         final File [] files = src.listFiles();
         for (int i = 0; i < files.length; i++)
         {
            copy(files[i], subdir);
         }
      }
   }

   /**
    * Copies the file <code>src</code> to the file or directory
    * <code>dest</code>.
    *
    * @param src The source file.
    * @param dest The destination file or directory.
    * @throws FileNotFoundException if the source file does not exists.
    * @throws IOException in case of an I/O error.
    */
   public static void copyFile (File src, File dest)
         throws FileNotFoundException, IOException
   {
      FileInputStream in = null;
      FileOutputStream out = null;
      try
      {
         in = new FileInputStream(src);
         if (dest.isDirectory())
         {
            out = new FileOutputStream(new File(dest, src.getName()));
         }
         else
         {
            out = new FileOutputStream(dest);
         }
         copy(in, out);
      }
      finally
      {
         close(in);
         close(out);
      }
   }


   /**
    * Copy from an input stream to an output stream.
    *
    * @param in The input stream.
    * @param out The output stream.
    * @throws IOException when an error happens during a read or a write
    *       operation.
    */
   public static void copy (InputStream in, OutputStream out)
         throws IOException
   {
      final byte[] buffer = new byte[BUFFER_SIZE];
      int read;
      while ((read = in.read(buffer)) != -1)
      {
         out.write(buffer, 0, read);
      }
   }


   /**
    * Copy all files under <code>srcDir</code> to the directory
    * <code>dst</code>.
    *
    * Unix command:
    * <pre>
    * $ cp "$scrDir/*" "$dst"
    * </pre>
    *
    * @param srcDir the source directory.
    * @param dst the destination directory.
    * @throws IOException in case of an I/O error.
    */
   public static void copySlashStar (File srcDir, File dst)
         throws IOException
   {
      if (srcDir.isDirectory())
      {
         final File[] files = srcDir.listFiles();
         for (int i = 0; i < files.length; i++)
         {
            copy(files[i], dst);
         }
      }
      else
      {
         throw new IllegalArgumentException("Souce must be a directory. ('"
               + srcDir + "')");
      }
   }

   /**
    * Creates a temporary directory.
    * @param baseDir the base directory.
    * @param prefix prefix for the temporary directory.
    * @return a temporary directory.
    * @throws IOException in case of an I/O error.
    */
   public static File createTempDir (File baseDir, String prefix)
         throws IOException
   {
      final String dirname = prefix
            + String.valueOf((int) (Math.random() * RND_FILENAME_FACTOR));

      final File tempDir = new File(baseDir, dirname);

      if (! tempDir.mkdir())
      {
         throw new IOException("Cannot create temp directory '"
               + tempDir + "'");
      }
      return tempDir;
   }

   /**
    * Closes the input stream (safe).
    *
    * This method tries to close the given input stream and
    * if an IOException occurs a message with the level
    * {@link Level#FINE} is logged. It's safe to pass a
    * <code>null</code> reference for the argument.
    *
    * @param in the input stream that should be closed.
    * @deprecated use IoUtil.close(InputStream)
    */
   public static void close (InputStream in)
   {
       IoUtil.close(in);
   }

   /**
    * Closes the output stream (safe).
    *
    * This method tries to close the given output stream and
    * if an IOException occurs a message with the level
    * {@link Level#FINE} is logged. It's safe to pass a
    * <code>null</code> reference for the argument.
    *
    * @param out the output stream that should be closed.
    * @deprecated use IoUtil.close(OutputStream)
    */
   public static void close (OutputStream out)
   {
       IoUtil.close(out);
   }

   /**
    * Closes the reader (safe).
    *
    * This method tries to close the given reader and if an IOException occurs
    * a message with the level {@link Level#FINE} is logged. It's safe
    * to pass a <code>null</code> reference for the argument.
    *
    * @param reader the reader that should be closed.
    * @deprecated use IoUtil.close(Reader)
    */
   public static void safeClose (Reader reader)
   {
       IoUtil.close(reader);
   }

   /**
    * Closes the writer (safe).
    *
    * This method tries to close the given writer and if an IOException occurs
    * a message with the level {@link Level#FINE} is logged. It's safe
    * to pass a <code>null</code> reference for the argument.
    *
    * @param writer the writer that should be closed.
    * @deprecated use IoUtil.close(Writer)
    */
   public static void safeClose (Writer writer)
   {
       IoUtil.close(writer);
   }


   /**
    * Search for files in a directory hierarchy.
    *
    * Unix command:
    * <pre>
    * find path -name pattern
    * </pre>
    * @param path root directory.
    * @param pattern filename pattern.
    * @return a list of files matching the given <code>pattern</code>
    *         under <code>path</code>.
    */
   public static List findFile (File path, String pattern)
   {
      final List ret = new ArrayList();

      // Check whether the path exists
      if (!path.exists())
      {
         throw new IllegalArgumentException(
               "The specified path does not exist! ('"
               + path + "')");
      }

      findFile(path, pattern, ret);

      return ret;
   }

   private static void findFile (File file, String pattern, List found)
   {
      if (file.isDirectory())
      {
         final File[] files = file.listFiles();
         for (int i = 0; i < files.length; i++)
         {
            findFile(files[i], pattern, found);
         }
      }
      else
      {
         if (file.getName().matches(pattern))
         {
            found.add(file);
         }
      }
   }

   /**
    * Remove file or directory.
    *
    * Unix command:
    * <pre>
    * rm -rf file
    * </pre>
    * @param file the file or directory to delete.
    * @throws IOException in case of an I/O error.
    */
   public static void rmdir (File file)
         throws IOException
   {
      if (file == null)
      {
         // done...
      }
      else if (file.isDirectory())
      {
         final File [] files = file.listFiles();
         for (int i = 0; i < files.length; i++)
         {
            rmdir(files[i]);
         }
         if (!file.delete())
         {
            throw new IOException("Failed to delete directory " + file + ".");
         }
      }
      else
      {
         if (!file.delete())
         {
            throw new IOException("Failed to delete file " + file + ".");
         }
      }
   }

   /**
    * Returns the relative path of <code>file</code> to the file
    * <code>basedir</code>.
    * @param baseDir the base directory or file.
    * @param file the file.
    * @return the relative path of the file to the basedir.
    * @throws IOException in case of an I/O error.
    */
   public static String getRelativePath (File baseDir, File file)
         throws IOException
   {
      final String base = baseDir.getCanonicalPath();
      String fileName = file.getCanonicalPath();

      if (fileName.startsWith(base))
      {
         fileName = fileName.substring(base.length());
         if (fileName.charAt(0) == '/')
         {
            fileName = fileName.substring(1);
         }
      }
      else
      {
         throw new RuntimeException("Cannot add file '" + file
               + "' with different baseDir '" + baseDir + "'.");
      }
      return fileName;
   }

   /**
    * Renames the file <code>aFile</code>.
    *
    * @param  aFile The file to be renamed.
    * @param  dest  The new abstract pathname for the named file
    * @throws IOException if the the renaming was not successful.
    */
   public static void rename (File aFile, File dest)
         throws IOException
   {
      if (!aFile.renameTo(dest))
      {
         throw new IOException("Failed to rename " + aFile + " to " + dest);
      }
   }

   /**
    * Deletes the given file <code>aFile</code>.
    *
    * @param  aFile The file to be deleted.
    * @throws IOException if the the deletion was not successful.
    */
   public static void delete (File aFile)
         throws IOException
   {
      if (aFile.exists())
      {
         if (!aFile.delete())
         {
            throw new IOException("Failed to delete " + aFile);
         }
      }
   }

   /**
    * Creates the given directories.
    *
    * @param dirs the directories to be created.
    * @throws IOException the directories could not be created.
    * @see File#mkdirs()
    */
   public static void mkdirs (File dirs)
         throws IOException
   {
      if (!dirs.exists() || !dirs.isDirectory())
      {
         if (!dirs.mkdirs())
         {
            throw new IOException("Failed to create directories " + dirs);
         }
      }
   }

   /**
    * Creates the given directory.
    *
    * @param dir the directory to be created.
    * @throws IOException if the file could not be created.
    * @see File#mkdir()
    */
   public static void mkdir (File dir)
         throws IOException
   {
      if (!dir.exists() || !dir.isDirectory())
      {
         if (!dir.mkdir())
         {
            throw new IOException("Failed to create directory " + dir);
         }
      }
   }

   /**
    * Creates the given file.
    * @param newFile the file to create
    * @throws IOException the file could not be created.
    * @see File#createNewFile()
    */
    public static void createNewFile (File newFile) 
        throws IOException
    {
        if (!newFile.createNewFile())
        {
            throw new IOException("Failed to create new File " + newFile);
        }
    }
}

