ch.bender.evacuate.Helper.java Source code

Java tutorial

Introduction

Here is the source code for ch.bender.evacuate.Helper.java

Source

/**
 * Copyright (c) 2015 by the original author or authors.
 *
 * This code is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package ch.bender.evacuate;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.text.DecimalFormat;
import java.util.Map;

import org.apache.commons.io.FilenameUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * Several independant helper methods 
 *
 * @author Heri
 */
public final class Helper {

    /** logger for this class */
    static Logger myLog = LogManager.getLogger(Helper.class);

    /**
     * Deletes a whole directory (recursively)
     * <p>
     * @param aDir
     *        a folder to be deleted (must not be null)
     * @throws IOException
     */
    public static void deleteDirRecursive(Path aDir) throws IOException {
        if (aDir == null) {
            throw new IllegalArgumentException("aDir must not be null");
        }

        if (Files.notExists(aDir)) {
            return;
        }

        if (!Files.isDirectory(aDir)) {
            throw new IllegalArgumentException("given aDir is not a directory");
        }

        Files.walkFileTree(aDir, new SimpleFileVisitor<Path>() {
            /**
             * @see java.nio.file.SimpleFileVisitor#visitFileFailed(java.lang.Object, java.io.IOException)
             */
            @Override
            public FileVisitResult visitFileFailed(Path aFile, IOException aExc) throws IOException {
                if ("System Volume Information".equals((aFile.getFileName().toString()))) {
                    return FileVisitResult.SKIP_SUBTREE;
                }

                throw aExc;
            }

            /**
             * @see java.nio.file.SimpleFileVisitor#preVisitDirectory(java.lang.Object, java.nio.file.attribute.BasicFileAttributes)
             */
            @Override
            public FileVisitResult preVisitDirectory(Path aFile, BasicFileAttributes aAttrs) throws IOException {
                if ("System Volume Information".equals((aFile.getFileName()))) {
                    return FileVisitResult.SKIP_SUBTREE;
                }

                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Files.delete(file);
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                if (dir.isAbsolute() && dir.getRoot().equals(dir)) {
                    myLog.debug("root cannot be deleted: " + dir.toString());
                    return FileVisitResult.CONTINUE;
                }

                Files.delete(dir);
                return FileVisitResult.CONTINUE;
            }

        });
    }

    /**
     * If the given target is already present, the method retains this older version in a kind of
     * FIFO buffer (but persistent on disk). The given MaxBackups number indicates how many such
     * backups are kept.
     * <p>
     * This routine is valid for files and directories. With files, the numbering suffix is done
     * before the last dot in the file name, with directories the number suffix is appended at the
     * end. 
     * <p>
     * Example: target is "Target.txt" and there are already present:
     * <pre>
     *     Target.txt
     *     Target_01.txt
     *     Target_02.txt
     * <pre>
     * Target_02.txt is renamed to Target_03.txt, Target_01.txt to Target_02.txt and Target.txt Target_01.txt.
     * <p>
     * If MaxBackup would be 3, then Target_02.txt would have been deleted instead renamed.
     * <p>   
     * 
     * @param aTarget
     * @param aMaxBackups
     * @param aFailedPreparations 
     * @throws IOException
     */
    public static void prepareTrashChain(Path aTarget, int aMaxBackups, Map<Path, Throwable> aFailedPreparations) {
        myLog.debug("preparing trash chain for " + aTarget.toString());

        try {

            int i = aMaxBackups - 1;

            while (i > 0) {
                Path targetUpper = appendNumberSuffix(aTarget, i);
                Path targetLower = (i > 1) ? appendNumberSuffix(aTarget, i - 1) : aTarget;

                i--;

                if (Files.notExists(targetUpper) && Files.notExists(targetLower)) {
                    continue;
                }

                if (Files.exists(targetUpper)) {
                    myLog.info("There are already " + (i + 2) + " trashed versions of " + aTarget.toString()
                            + ". Deleting the oldest one");

                    if (Files.exists(targetUpper)) {
                        if (Files.isDirectory(targetUpper)) {
                            Helper.deleteDirRecursive(targetUpper);
                        } else {
                            Files.delete(targetUpper);
                        }
                    }
                }

                if (Files.notExists(targetLower)) {
                    continue;
                }

                myLog.debug("Renaming " + targetLower.toString() + " to " + targetUpper.toString());
                Files.move(targetLower, targetUpper, StandardCopyOption.ATOMIC_MOVE);
            }
        } catch (Throwable e) {
            aFailedPreparations.put(aTarget, e);
        }
    }

    /**
     * Appends a number suffix to the name of the Path object.
     * <p>
     * This routine is valid for files and directories. With files, the numbering suffix is done
     * before the last dot in the file name, with directories the number suffix is appended at the
     * end. 
     * <p>
     * If the path object does not exist it is assumed to be a directory. Numbering a non existing
     * file can be done by overloaded method {@link #appendNumberSuffix(Path, int, boolean)} setting
     * third parameter to true.
     * <p>
     * Example: 
     * <pre>
     *     path/to/prefix.directory    ->     path/to/prefix.directory_01    
     *     path/to/prefix.file.txt     ->     path/to/prefix.file_01.txt    
     * <pre>
     * 
     * @param aPath
     *        the path whose name should be suffixed
     * @param aNumber
     *        the number to suffix
     * @return the new path
     */
    public static Path appendNumberSuffix(Path aPath, int aNumber) {
        boolean beforeExtension = false;

        if (Files.exists(aPath) && !Files.isDirectory(aPath)) {
            beforeExtension = true;
        }

        return appendNumberSuffix(aPath, aNumber, beforeExtension);
    }

    /**
     * Appends a number suffix to the name of the Path object. If the flag BeforeExtension is
     * <code>true</code>, the number is appended before the last dot in the file name.
     * <p>
     * See examples in {@link #appendNumberSuffix(Path, int)}
     * <p>
     * 
     * @param aPath
     *        the path whose name should be suffixed
     * @param aNumber
     *        the number to suffix
     * @param aBeforeExtension 
     *        <code>true</code>: the extension stays the last part of the filename.
     * @return the new path
     */
    public static Path appendNumberSuffix(Path aPath, int aNumber, boolean aBeforeExtension) {
        DecimalFormat df = new DecimalFormat("00");
        String suffix = "_" + df.format(aNumber);

        String parent = aPath.getParent() == null ? "" : aPath.getParent().toString();
        String extension = "";
        String name = aPath.getFileName().toString();

        if (aBeforeExtension) {
            extension = FilenameUtils.getExtension(name);

            if (extension.length() > 0) {
                extension = "." + extension;
            }
            name = FilenameUtils.getBaseName(name);
        }

        return Paths.get(parent, name + suffix + extension);
    }

    /**
     * Constructor
     *
     */
    private Helper() {
        super();
    }

}