/*
* Copyright (c) 2008-2014, XebiaLabs B.V., All rights reserved.
*
*
* Overthere is licensed under the terms of the GPLv2
* <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most XebiaLabs Libraries.
* There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
* this software, see the FLOSS License Exception
* <http://github.com/xebialabs/overthere/blob/master/LICENSE>.
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation; version 2
* of the License.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
* Floor, Boston, MA 02110-1301 USA
*/
package com.xebialabs.overthere.util;
import java.io.IOException;
import java.util.List;
import com.xebialabs.overthere.OverthereFile;
import com.xebialabs.overthere.RuntimeIOException;
/**
* Abstract class that walks through a directory hierarchy and provides subclasses with convenient hooks to add specific
* behaviour.
* <p/>
* This class operates with a maximum depth to limit the files and direcories visited.
* <p/>
* The following sections describe:
* <ul>
* <li><a href="#example">1. Example Implementation</a> - example <code>FileCleaner</code> implementation.</li>
* </ul>
* <p/>
* <a name="example"></a>
* <h3>1. Example Implementation</h3>
* <p/>
* There are many possible extensions, for example, to delete all files and '.svn' directories
* <p/>
* <pre>
* public class FileCleaner extends OverthereFileDirectoryWalker {
* private List<OverthereFile> results = new ArrayList<OverthereFile>();
*
* public FileCleaner() {
* super();
* }
*
* public List clean(OverthereFile startDirectory) {
* results.clear();
* walk(startDirectory, results);
* return results;
* }
*
* protected boolean handleDirectory(OverthereFile directory, int depth) {
* // delete svn directories and then skip
* if (".svn".equals(directory.getName())) {
* directory.delete();
* return false;
* } else {
* return true;
* }
*
* }
*
* protected void handleFile(OverthereFile file, int depth) {
* // delete file and add to list of deleted
* file.delete();
* results.add(file);
* }
* }
* </pre>
* <p/>
* Adapted from the DirectoryWalker class in Common IO.
* <p/>
* FIXME: Move to its proper place
*/
public abstract class OverthereFileDirectoryWalker {
/**
* The directory level representing the starting directory = 0
*/
public static final int ROOT = 0;
/**
* The limit on the directory depth to walk.
*/
private final int depthLimit;
/**
* Construct an instance with unlimited <i>depth</i>.
*/
protected OverthereFileDirectoryWalker() {
this(-1);
}
/**
* Construct an instance with limit the <i>depth</i> navigated to.
* <p/>
*
* @param depthLimit controls how <i>deep</i> the hierarchy is navigated to (less than 0 means unlimited)
*/
protected OverthereFileDirectoryWalker(int depthLimit) {
this.depthLimit = depthLimit;
}
/**
* Internal method that walks the directory hierarchy in a depth-first manner.
* <p/>
* Users of this class do not need to call this method. This method will be called automatically by another (public)
* method on the specific subclass.
* <p/>
* Writers of subclasses should call this method to start the directory walk. Once called, this method will emit
* events as it walks the hierarchy. The event methods have the prefix <code>handle</code>.
*
* @param startDirectory the directory to start from, not null
* @throws NullPointerException if the start directory is null
* @throws RuntimeIOException if an I/O Error occurs
*/
protected final void walk(OverthereFile startDirectory) throws RuntimeIOException {
try {
if (startDirectory == null) {
throw new NullPointerException("Start Directory is null");
}
handleStart(startDirectory);
walk(startDirectory, 0);
handleEnd();
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
/**
* Main recursive method to examine the directory hierarchy.
*
* @param directory the directory to examine, not null
* @param depth the directory level (starting directory = 0)
* @throws IOException if an I/O Error occurs
*/
private void walk(OverthereFile directory, int depth) throws IOException {
if (handleDirectory(directory, depth)) {
handleDirectoryStart(directory, depth);
int childDepth = depth + 1;
if (depthLimit < 0 || childDepth <= depthLimit) {
List<OverthereFile> childFiles = listFiles(directory);
if (childFiles == null) {
handleRestricted(directory, childDepth);
} else {
for (OverthereFile childFile : childFiles) {
if (childFile.isDirectory()) {
walk(childFile, childDepth);
} else {
handleFile(childFile, childDepth);
}
}
}
}
handleDirectoryEnd(directory, depth);
}
}
/**
* Lists the files in the directory.
*
* @param directory in which to list files.
* @return all the files in the directory as filtering.
*/
protected List<OverthereFile> listFiles(OverthereFile directory) {
return directory.listFiles();
}
/**
* Overridable callback method invoked at the start of processing.
* <p/>
* This implementation does nothing.
*
* @param startDirectory the directory to start from
* @throws IOException if an I/O Error occurs
*/
protected void handleStart(OverthereFile startDirectory) throws IOException {
// do nothing - overridable by subclass
}
/**
* Overridable callback method invoked to determine if a directory should be processed.
* <p/>
* This method returns a boolean to indicate if the directory should be examined or not. If you return false, the
* entire directory and any subdirectories will be skipped.
* <p/>
* This implementation does nothing and returns true.
*
* @param directory the current directory being processed
* @param depth the current directory level (starting directory = 0)
* @return true to process this directory, false to skip this directory
* @throws IOException if an I/O Error occurs
*/
protected boolean handleDirectory(OverthereFile directory, int depth) throws IOException {
// do nothing - overridable by subclass
return true; // process directory
}
/**
* Overridable callback method invoked at the start of processing each directory.
* <p/>
* This implementation does nothing.
*
* @param directory the current directory being processed
* @param depth the current directory level (starting directory = 0)
* @throws IOException if an I/O Error occurs
*/
protected void handleDirectoryStart(OverthereFile directory, int depth) throws IOException {
// do nothing - overridable by subclass
}
/**
* Overridable callback method invoked for each (non-directory) file.
* <p/>
* This implementation does nothing.
*
* @param file the current file being processed
* @param depth the current directory level (starting directory = 0)
* @throws IOException if an I/O Error occurs
*/
protected void handleFile(OverthereFile file, int depth) throws IOException {
// do nothing - overridable by subclass
}
/**
* Overridable callback method invoked for each restricted directory.
* <p/>
* This implementation does nothing.
*
* @param directory the restricted directory
* @param depth the current directory level (starting directory = 0)
* @throws IOException if an I/O Error occurs
*/
protected void handleRestricted(OverthereFile directory, int depth) throws IOException {
// do nothing - overridable by subclass
}
/**
* Overridable callback method invoked at the end of processing each directory.
* <p/>
* This implementation does nothing.
*
* @param directory the directory being processed
* @param depth the current directory level (starting directory = 0)
* @throws IOException if an I/O Error occurs
*/
protected void handleDirectoryEnd(OverthereFile directory, int depth) throws IOException {
// do nothing - overridable by subclass
}
/**
* Overridable callback method invoked at the end of processing.
* <p/>
* This implementation does nothing.
*
* @throws IOException if an I/O Error occurs
*/
protected void handleEnd() throws IOException {
// do nothing - overridable by subclass
}
}