FileUtils.java

/*
 * SmartSprites Project
 *
 * Copyright (C) 2007-2009, Stanisław Osiński.
 * 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 SmartSprites Project nor the names of its contributors
 *   may  be used  to endorse  or  promote  products derived   from  this  software
 *   without specific prior written permission.
 *
 * - We kindly request that you include in the end-user documentation provided with
 *   the redistribution and/or in the software itself an acknowledgement equivalent
 *   to  the  following: "This product includes software developed by the SmartSprites
 *   Project."
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER OR 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.carrot2.util;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.regex.Pattern;

import org.apache.commons.io.FilenameUtils;

/**
 * Various utility methods for working with {@link File}s.
 */
public class FileUtils {

    /**
     * Instantiates a new file utils.
     */
    private FileUtils() {
        // Prevent Instantiation
    }

    /**
     * Creates a new {@link File} from the provided path and attempts to execute {@link File#getCanonicalFile()}. In
     * case of a failure, returns the result of {@link File#getAbsoluteFile()}.
     *
     * @param path
     *            the path
     *
     * @return the canonical or absolute file
     */
    public static File getCanonicalOrAbsoluteFile(String path) {
        File file = new File(path);
        try {
            return file.getCanonicalFile();
        } catch (final IOException e) {
            return file.getAbsoluteFile();
        }
    }

    /**
     * Changes the root directory of a file. For example, file is /a/b/c/d/e and oldRoot is /a/b/c, and newRoot is /x/y,
     * the result will be /x/y/d/e.
     *
     * @param file
     *            the file
     * @param oldRoot
     *            the old root
     * @param newRoot
     *            the new root
     *
     * @return the string
     */
    public static String changeRoot(String file, String oldRoot, String newRoot) {
        // File is assumed to be a subpath of oldRoot, so PathUtils.getRelativeFilePath()
        // shouldn't return null here.
        final String relativePath = PathUtils.getRelativeFilePath(oldRoot, file);
        return FilenameUtils.concat(newRoot, relativePath);
    }

    /**
     * Removes useless segments in relative paths, e.g. replaces <code>../path/../other/file.css</code> with
     * <code>../other/file.css</code>
     *
     * @param path
     *            the path
     * @param separator
     *            the separator
     *
     * @return the string
     */
    public static String canonicalize(String path, String separator) {
        String replaced = path;
        String toReplace = null;
        final String separatorEscaped = Pattern.quote(separator);
        final Pattern pattern = Pattern
                .compile("[^" + separatorEscaped + "\\.]+" + separatorEscaped + "\\.\\." + separatorEscaped + "?");
        while (!replaced.equals(toReplace)) {
            toReplace = replaced;
            replaced = pattern.matcher(toReplace).replaceFirst("");
        }
        return replaced;
    }

    /**
     * Attempts to delete the provided files and throws an {@link IOException} in case {@link File#delete()} returns
     * <code>false</code> for any of them.
     *
     * @param files
     *            the files
     *
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    public static void deleteThrowingExceptions(File... files) throws IOException {
        if (files == null) {
            return;
        }

        final ArrayList<String> undeletedFiles = new ArrayList<>();
        for (File file : files) {
            if (file == null) {
                continue;
            }

            if (file.exists() && !file.delete()) {
                undeletedFiles.add(file.getPath());
            }
        }

        if (!undeletedFiles.isEmpty()) {
            throw new IOException("Unable to delete files: " + undeletedFiles.toString());
        }
    }

    /**
     * Calls {@link File#mkdirs()} on the provided argument and throws an {@link IOException} if the call returns
     * <code>false</code>.
     *
     * @param dirs
     *            the dirs
     *
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    public static void mkdirsThrowingExceptions(File dirs) throws IOException {
        if (dirs.exists()) {
            return;
        }

        if (!dirs.mkdirs()) {
            throw new IOException("Unable to create directories: " + dirs.getPath());
        }
    }

    /**
     * Returns <code>true</code> if file is contained in the parent directory or any parent of the parent directory.
     *
     * @param file
     *            the file
     * @param parent
     *            the parent
     *
     * @return true, if is file in parent
     */
    public static boolean isFileInParent(File file, File parent) {
        final File fileParent = file.getParentFile();
        if (fileParent == null) {
            return false;
        }

        if (fileParent.equals(parent)) {
            return true;
        }

        return isFileInParent(fileParent, parent);
    }
}