/* JFox, the OpenSource J2EE Application Server
 *
 * Distributable under GNU LGPL license by gun.org
 * more details please visit http://www.huihoo.org/jfox
 */

package org.jfox.framework.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * @author <a href="mailto:young_yy@hotmail.com">Young Yang</a>
 */

public class Files {

    /**
     * <p/>
     * Delete a file. If file is a directory, delete it and all sub-directories.
     * </p>
     * <p/>
     * The difference between File.delete() and this method are:
     * </p>
     * <ul>
     * <li>A directory to be deleted does not have to be empty.</li>
     * <li>You get exceptions when a file or directory cannot be deleted.
     * (java.io.File methods returns a boolean)</li>
     * </ul>
     *
     * @param file file or directory to delete.
     */
    public static boolean delete(File file) {
        if(!file.exists()) {
            return true;
        }
        if(file.isDirectory()) {
            return deleteDirectory(file);
        }
        else {
            return file.delete();
        }
    }

    /**
     * Delete a file, or a directory and all of its contents.
     *
     * @param dir The directory or file to delete.
     * @return True if all delete operations were successfull.
     */
    public static boolean deleteDirectory(File dir) {

        if(!dir.exists()) return true;
        boolean result = cleanDirectory(dir);
        return result & dir.delete();
    }

    /**
     * Clean a directory without deleting it.
     *
     * @param directory directory to clean
     */
    public static boolean cleanDirectory(File directory) {
        if(!directory.exists()) {
            return true;
        }
        if(!directory.isDirectory()) {
            return false;
        }
        boolean result = true;

        File[] files = directory.listFiles();
        for(int i = 0; i < files.length; i++) {
            File file = files[i];
            result = result && delete(file);
        }
        return result;
    }

    /**
     * Schedule a file to be deleted when JVM exits.
     * If file is directory delete it and all sub-directories.
     *
     * @param file file or directory to delete.
     * @throws IOException in case deletion is unsuccessful
     */
    public static void deleteOnExit(File file) throws IOException {
        if(file.isDirectory()) {
            deleteDirectoryOnExit(file);
        }
        else {
            file.deleteOnExit();
        }
    }

    /**
     * Recursively schedule directory for deletion on JVM exit.
     *
     * @param directory directory to delete.
     * @throws IOException in case deletion is unsuccessful
     */
    private static void deleteDirectoryOnExit(File directory)
            throws IOException {
        if(!directory.exists()) {
            return;
        }

        cleanDirectoryOnExit(directory);
        directory.deleteOnExit();
    }

    /**
     * Clean a directory without deleting it.
     *
     * @param directory directory to clean.
     * @throws IOException in case cleaning is unsuccessful
     */
    private static void cleanDirectoryOnExit(File directory)
            throws IOException {
        if(!directory.exists()) {
            // do nothing
            return;
        }

        if(!directory.isDirectory()) {
            String message = directory + " is not a directory";
            throw new IllegalArgumentException(message);
        }

        IOException exception = null;

        File[] files = directory.listFiles();
        for(int i = 0; i < files.length; i++) {
            File file = files[i];
            try {
                deleteOnExit(file);
            }
            catch(IOException ioe) {
                exception = ioe;
            }
        }

        if(null != exception) {
            throw exception;
        }
    }

    /**
     * Copy a file.
     *
     * @param source Source file to copy.
     * @param dir    Destination target dir.
     * @throws java.io.IOException Failed to copy file.
     */
    public static File copy(final File source, final File dir) throws IOException {
        if(source.getParentFile().equals(dir)) {
            return source;
        }
        if(!dir.exists()) {
            dir.mkdirs();
        }
        if(!dir.isDirectory()) {
            throw new IOException("Destination must be a directory.");
        }

        File target = new File(dir, source.getName());
        BufferedInputStream in = new BufferedInputStream(new FileInputStream(source));
        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(target));
        try {
            IOs.write(in, out);
        }
        finally {
            out.flush();
            IOs.close(in);
            IOs.close(out);
        }
        return target;
    }

    /**
     * move the source file to the targe directory
     *
     * @param source
     * @param dir
     */
    public static File move(final File source, final File dir) throws IOException {
        File target = copy(source, dir);
        source.delete();
        return target;
    }

    /**
     * Implements the same behaviour as the "touch" utility on Unix. It creates
     * a new file with size 0 or, if the file exists already, it is opened and
     * closed without modifying it, but updating the file date and time.
     *
     * @param file the File to touch
     * @throws IOException If an I/O problem occurs
     */
    public static void touch(File file) throws IOException {
        OutputStream out = new java.io.FileOutputStream(file);
        IOs.close(out);
    }

    /**
     * Convert the array of Files into a list of URLs.
     *
     * @param files the array of files
     * @return the array of URLs
     * @throws IOException if an error occurs
     */
    public static URL[] toURLs(File[] files) throws IOException {
        URL[] urls = new URL[files.length];

        for(int i = 0; i < urls.length; i++) {
            urls[i] = files[i].toURL();
        }

        return urls;
    }

    /**
     * Convert from a <code>URL</code> to a <code>File</code>.
     *
     * @param url File URL.
     * @return The equivalent <code>File</code> object, or <code>null</code> if the URL's protocol
     *         is not <code>file</code>
     */
    public static File toFile(URL url) {
        if(url.getProtocol().equals("file") == false) {
            return null;
        }
        else {
            String filename =
                    url.getFile().replace('/', File.separatorChar);
            return new File(filename);
        }
    }

    /**
     * get file content
     */
    public static String content(File file) throws IOException {
        InputStream in = new java.io.FileInputStream(file);
        try {
            return IOs.toString(in);
        }
        finally {
            IOs.close(in);
        }
    }

    /**
     * list Files recursively
     *
     * @param filter
     * @return File list
     */
    public static List listFiles(File dir, FileFilter filter) {
        List files = new ArrayList();
        if(!dir.exists() || dir.isFile()) return files;
        listFiles(files, dir, filter);
        return files;
    }

    private static void listFiles(List filesList, File dir, FileFilter filter) {
        File[] files = dir.listFiles(filter);
        List temp = Arrays.asList(files);
        Collections.sort(temp);
        filesList.addAll(temp);

        File[] subDirs = dir.listFiles(FileFilters.directoryFileFilter());
        for(int i = 0; i < subDirs.length; i++) {
            listFiles(filesList, subDirs[i], filter);
        }
    }
}

