things.common.tools.FileTools.java Source code

Java tutorial

Introduction

Here is the source code for things.common.tools.FileTools.java

Source

/**
 * THINGS/THINGER 2009
 * Copyright Erich P Gatejen (c) 2001 through 2009  ALL RIGHTS RESERVED
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package things.common.tools;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;

import org.apache.commons.io.CopyUtils;

import things.common.ThingsConstants;
import things.common.ThingsException;
import things.common.ThingsNamespace;
import things.thinger.SystemException;
import things.thinger.SystemNamespace;

/**
 * File manipulation utilities.
 * <p>
 * @author Erich P. Gatejen
 * @version 1.0
 * <p>
 * <i>Version History</i>
 * <pre>EPG - Initial - 24 NOV 04
 * </pre> 
 */
@SuppressWarnings("deprecation")
public class FileTools {

    /**
     * Max number of delete retries.
     */
    private final static int FILE_DELETE_RETRIES = 10;

    /**
     * Copy a file.
     * @param source
     * @param destination
     * @throws Throwable
     */
    public static void copy(File source, File destination) throws Throwable {

        InputStream ins = null;
        OutputStream outs = null;
        try {

            ins = new FileInputStream(source);
            outs = new FileOutputStream(destination);
            CopyUtils.copy(ins, outs);

        } catch (Throwable t) {
            throw t;
        } finally {
            try {
                ins.close();
            } catch (Throwable tt) {
            }
            try {
                outs.close();
            } catch (Throwable tt) {
            }
        }
    }

    /**
     * Copy a stream.  I'll implement this myself some day.
     * @param source
     * @param destination
     * @throws Throwable
     */
    public static void copy(InputStream source, OutputStream destination) throws Throwable {
        try {
            CopyUtils.copy(source, destination);
        } catch (Throwable t) {
            throw t;
        }
    }

    /**
     * Delete a file.
     * @param theFile the file.  Null will be ignored.
     * @throws Throwable
     */
    public static void delete(File theFile) throws Throwable {
        if ((theFile == null) || (!theFile.exists()))
            return;// Dismiss null or not there.
        try {
            int tries = 5;
            while (theFile.exists() && !theFile.delete()) {
                System.gc();
                try {
                    Thread.sleep(10);
                } catch (InterruptedException ex) {
                }
                tries--;
                if (tries == 0)
                    throw new Exception("Exceeded delete try.  File doesn't want to die.");
            }
        } catch (Exception te) {
            throw new ThingsException("Delete failed.", ThingsException.FILESYSTEM_ERROR_DELETE_FAILED, te,
                    SystemNamespace.ATTR_PLATFORM_FILE_PATH, theFile.getAbsolutePath());
        }
    }

    /**
     * Destroy a file or directory and all it's contents (including sub directories).
     * @param theFile the file to destroy (it can be a real file or a directory).
     * @throws Throwable
     */
    public static void destroy(File theFile) throws Throwable {
        destroy(theFile, true);
    }

    /**
     * Destroy a file or directory and all it's contents (including sub directories).
     * @param theFile the file to destroy (it can be a real file or a directory).
     * @param recurse if true, recurse into directories
     * @throws Throwable
     */
    private static void destroy(File theFile, boolean recurse) throws Throwable {

        // If it is a file, kill it.
        if (theFile.isFile()) {
            int tries = FILE_DELETE_RETRIES;
            while (!theFile.delete()) {
                System.gc(); // Give it a breather
                tries--;
                if (tries == 0) {
                    if (theFile.exists())
                        throw new SystemException("Exceeded delete try.  File doesn't want to die.",
                                SystemException.FILESYSTEM_ERROR_FILE_WONT_DELETE,
                                SystemNamespace.ATTR_PLATFORM_FS_PATH_ABSOLUTE, theFile.getAbsolutePath());
                }
            }

        } else {

            // Must be a directory
            File[] subpaths = theFile.listFiles();
            if (subpaths == null)
                SystemException.softwareProblem(
                        "FSFileSystem.internalDelete got a File that isn't isFile() or isDirectory().  wtf?",
                        new Exception(), SystemNamespace.ATTR_PLATFORM_FS_PATH_ABSOLUTE, theFile.getAbsolutePath());

            // Whack any subs.  We'll hazard some recursion for this.
            if (subpaths.length > 0) {

                // make the recursion decision here to save time
                if (recurse) {
                    for (int index = 0; index < subpaths.length; index++) {
                        destroy(subpaths[index], true);
                    }
                } else {

                    // Files only
                    for (int index = 0; index < subpaths.length; index++) {
                        if (subpaths[index].isFile()) {
                            destroy(subpaths[index], false);
                        }
                    }
                }

            } // end IF SUBDIRECTORIES

            // Get rid of the directory (the error means it wasn't empty)
            // Try once 
            if (!theFile.delete()) {

                // Is it really there?
                if (theFile.exists()) {

                    // Is it empty?  Check above first as a fast fail, before we hit the filesystem again.
                    if ((subpaths.length > 0) && (theFile.listFiles().length <= 0)) {

                        // Ok, go to work on it, because it is supposed to be gone
                        int tries = FILE_DELETE_RETRIES;
                        while (!theFile.delete()) {
                            System.gc(); // Give it a breather
                            tries--;
                            if (tries == 0)
                                throw new SystemException("Exceeded delete try.  File doesn't want to die.",
                                        SystemException.FILESYSTEM_ERROR_FILE_WONT_DELETE,
                                        SystemNamespace.ATTR_PLATFORM_FS_PATH, theFile.getAbsolutePath());
                        }

                    } else {
                        if (recurse)
                            throw new SystemException(
                                    "Directory will not delete because it is not empty (though it should have been emptied already).",
                                    SystemException.FILESYSTEM_ERROR_FILE_WONT_DELETE,
                                    SystemNamespace.ATTR_PLATFORM_FS_PATH, theFile.getAbsolutePath());
                    }

                }

            } // end kill DIRECTORY

        } // end if FILE
    }

    /**
     * Make a file.  If the file already exists, delete it.  
     * It will let any exception escape.
     * @param name Path to the file
     * @return A file object
     * @throws Throwable
     */
    public static File makeFile(String name) throws Throwable {

        File directory;
        String directoryPath;
        int fileSegment;

        // construct the path
        File target = new File(name);

        if (target.exists()) {
            int tries = ThingsConstants.FS_FILE_DELETE_RETRIES;
            boolean status = target.delete();
            while (status != true) {
                tries--;
                if (tries == 0)
                    throw new ThingsException("Could not delete existing file where call wanted to makeFile().",
                            ThingsException.FILESYSTEM_ERROR_COULD_NOT_REPLACE,
                            ThingsNamespace.ATTR_PLATFORM_FILE_PATH);
                System.gc();
                Thread.yield();
                status = target.delete();
            }
        } else {
            fileSegment = name.lastIndexOf('/');
            if (fileSegment > 1) {
                directoryPath = name.substring(0, fileSegment);
                directory = new File(directoryPath);
                if (!directory.exists()) {
                    directory.mkdirs();
                }
            }
        }
        return target;
    }

    /**
     * Make a file.  If the file already exists, it will be available for write.
     * It will let any exception escape.
     * @param name Path to the file
     * @return A file object
     * @throws Exception
     */
    public static File appendFile(String name) throws Exception {

        File directory;
        String directoryPath;
        int fileSegment;

        // construct the path
        File target = new File(name);

        if (!target.exists()) {

            fileSegment = name.lastIndexOf('/');
            if (fileSegment > 1) {
                directoryPath = name.substring(0, fileSegment);
                directory = new File(directoryPath);
                if (!directory.exists()) {
                    directory.mkdirs();
                }
            }
        }
        return target;
    }

    /**
     * This will detect if the path has any relative pointers (such as ..).  
     * @param path The path
     * @return true if it does, otherwise false.
     */
    public static boolean detectRelativePath(String path) {
        if (path != null) {
            try {
                // detect no root
                String trimmed = path.trim();
                if (((trimmed.charAt(1) != ':') && (trimmed.charAt(0) != '/') && (trimmed.charAt(0) != '\\'))
                        || (trimmed.charAt(0) == '.'))
                    return true;

                // find dots
                StringReader rin = new StringReader(path);
                State rpState = State.SEEK;
                int currentChar = rin.read();
                while (currentChar >= 0) {
                    switch (rpState) {
                    case SEEK:
                        if (currentChar == '\\' || currentChar == '/')
                            rpState = State.START;
                        break;
                    case START:
                        if (currentChar == '.')
                            rpState = State.DOT;
                        else if (currentChar != '\\' && currentChar != '/')
                            rpState = State.SEEK;
                        break;
                    case DOT:
                        if (currentChar == '\\' || currentChar == '/')
                            return true;
                        if (currentChar != '.')
                            rpState = State.START;
                        break;
                    }
                    currentChar = rin.read();
                }
            } catch (Exception ee) {
                // Don't care.  Should only be a spurious EOF
            }
        }
        return false;
    }

    private enum State {
        SEEK, START, DOT
    }

    /**
     * Read a file into a String.  This should be used only is absolutely needed.
     * @param path File to load
     * @return String if successful or otherwise null
     * @throws Certain IOException, like if the file cannot be found or opened.
     */
    public static String loadFile2String(File path) throws IOException {
        String result = null;
        if (path == null)
            throw new IOException("Path is null in loadFile2String(path).");

        try {
            // Setup
            StringBuffer buffer = new StringBuffer();
            char[] buf = new char[1024];
            int sbuf;
            BufferedReader bis = new BufferedReader(new FileReader(path));

            // Run it
            sbuf = bis.read(buf, 0, 1024);
            while (sbuf > 0) {
                buffer.append(buf, 0, sbuf);
                sbuf = bis.read(buf, 0, 1024);
            }

            result = buffer.toString();

        } catch (IOException ioe) {
            throw ioe;
        } catch (Throwable t) {
            //throw new IOException("Failed to loadFile2String.", t);
            throw new IOException("Failed to loadFile2String." + t.getMessage());
        }

        return result;
    }

    /**
     * Read a stream into a buffer, as much as can fit in the buffer, .  This should be used only is absolutely needed.
     * @param stream the input stream
     * @param buffer the buffer;
     * @return the number of bytes read intot he buffer.
     * @throws Certain IOException, like if the file cannot be found or opened.
     */
    public static int loadStream2Buffer(InputStream stream, byte[] buffer) throws IOException {
        int bufferCursor = 0;
        try {
            int actualRead = stream.read(buffer, bufferCursor, buffer.length - bufferCursor);
            bufferCursor = actualRead;
            while ((actualRead > 0) && (bufferCursor < buffer.length)) {
                actualRead = stream.read(buffer, bufferCursor, buffer.length - bufferCursor);
                bufferCursor += actualRead;
            }

        } catch (IOException ioe) {
            throw ioe;
        } catch (Throwable t) {
            //throw new IOException("Failed to loadFile2String.", t);
            throw new IOException("Failed to loadStream2Buffer. " + t.getMessage());
        }
        return bufferCursor;
    }

    /**
     * Save a string to a file.  This should be used only is absolutely needed.
     * @param path File to save
     * @param theString the string.
     * @throws Certain IOException, like if the file cannot be found or opened.
     */
    public static void saveString2File(File path, String theString) throws IOException {
        if (path == null)
            throw new IOException("Path is null in saveString2File(path).");
        if (theString == null)
            throw new IOException("theString is null in saveString2File(path).");
        OutputStream os = null;

        try {
            os = new BufferedOutputStream(new FileOutputStream(path));
            os.write(theString.getBytes());

        } catch (IOException ioe) {
            throw ioe;
        } catch (Throwable t) {
            throw new IOException("Failed to saveString2File." + t.getMessage(), t);
        } finally {
            try {
                os.close();
            } catch (Throwable tt) {
                // Oh well.
            }
        }
    }
}