Recursive File Tree Traversing in Java using NIO.2

In my previous article about NIO.2, we have seen how to implement a service which monitors a directory recursively for any changes. In this article we will look at another improvement in JDK7 (NIO.2) called FileWatcher. This will allow us to implement a search or index. For example, we can find all the $some_pattern$ files in a given directory recursively and (or) delete / copy all the all the $some_pattern$ files in a file system. In a nutshell FileWatcher will get us a list of files from a file system based on a pattern which can be processed based on our requirement.

The FileVistor is an interface and our class should implement it. We have two methods before the traversal starts at the directory level & file level, and one method after the traversal is complete, which can be used for clean up or post processing. The important points from the interface is given in the below diagram.

While I think FileVistor is the best way to handle this, JDK7 NIO.2 has given another option to achieve the same, a class named SimpleFileVistor (which implements FileVisitor). It should be self explanatory, a simplified version of FileVisitor. We can extend the SimpileFileVisitor into our class and then traverse the directory with overriding only the methods we need, and if any step fails we will get an IOException.

According to me, FileVisitor is better because it forces you to implement the methods (sure, you can leave them blank) since these methods are really important if you plan to implement recursive delete / copy or work with symbolic links. For example, if you are copying some files to a directory you should make sure that the directory should be created first before copying which can be done in the preVisitDirectory().

The other area of concern is symbolic links and how this will be handled by FileVisitor. This can be achieved using FileVisitOption enums. By default, the symbolic links are not followed so that we are not accidentally deleting any directories in a recursive delete. If you want to handle manually, there are two options FOLLOW_LINKS (follow the links) & DETECT_CYCLES (catch circular references).

If you want to exclude some directory from FileVisitor or if you are looking for a directory or a file in the file system and once you find it you want to stop searching that can be implemented by using the return type of FileVisitor, called FileVisitResult. SKIP_SUBTREE allows us to skip directories & subdirectories. TERMINATE stops the traversing.

The search can be initiated by the walkFileTree() method in Files class. This will take the starting directory (or root directory in your search) as a parameter. You can also define Integer.MAX_VALUE if you want to manually specify the depth. And as mentioned in the above diagram, define FileVisitOption for symbolic links if needed.

Enough with the API description, let’s write some sample code to implement what we discussed. We will be using the SimpleFileVisitor so that in our demo we don’t need to implement all the methods.

Let’s start with defining the pattern which needs to be searched for. In this example, we will search for all the *txt file / directory names recursively in any given directory. This can be done with getPathMatcher() in FileSystems

PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + "*txt*");

Now, let’s initiate the search by calling walkFileTree() as mentioned below. We are not defining anything specific for symbolic links so, by default its NO_FOLLOW.

Files.walkFileTree(Paths.get("D://Search"), fileVisitor);

Let’s go through the implementations of class SimpleFileVisitor, we will be overriding only visitFile() & preVisitDirectory() in this example, but its a good practice to override all the five methods so that we have more control over the search. The implementation is pretty simple, based on the pattern the below methods will search for a directory or file and print the path.

@Override
public FileVisitResult visitFile(Path filePath, BasicFileAttributes basicFileAttributes) {
    if (filePath.getName() != null && pathMatcher.matches(filePath.getName()))
        System.out.println("FILE: " + filePath);
    return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult preVisitDirectory(Path directoryPath) {
    if (directoryPath.getName() != null && pathMatcher.matches(directoryPath.getName()))
        System.out.println("DIR: " + directoryPath);
    return FileVisitResult.CONTINUE;
}

Once this is completed, we can use the postVisitDirectory() to perform additional tasks or any cleanup if needed. A sample output from my machine is given below.

The complete source code is given below. Please note that you need JDK7 to run this code. Source is also available in my github page.

public class NIO2_FileVisitor extends SimpleFileVisitor<Path> {
    private PathMatcher pathMatcher;

    @Override
    public FileVisitResult visitFile(Path filePath, BasicFileAttributes basicFileAttributes) {
        if (filePath.getName() != null && pathMatcher.matches(filePath.getName()))
                System.out.println("FILE: " + filePath);
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult preVisitDirectory(Path directoryPath) {
        if (directoryPath.getName() != null && pathMatcher.matches(directoryPath.getName()))
                System.out.println("DIR: " + directoryPath);
        return FileVisitResult.CONTINUE;
    }

    public static void main(String[] args) throws IOException {
        NIO2_FileVisitor fileVisitor = new NIO2_FileVisitor();
        fileVisitor.pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + "*txt*");
        Files.walkFileTree(Paths.get("D://Search"), fileVisitor);
    }
}

Monitor a Directory for Changes using Java

Many applications which we use on a day to day basis like a music organizer, file editors monitor the directory for any changes in the files/directories and take appropriate action in the application if there are any changes detected on the fly. Since Java do not have direct access to the system level calls (unless we use JNI, which will make the code platform specific) the only way to monitor any directory is to use a separate thread which will be using a lot of resources (memory & disk I/O) to monitor the changes inside the directory. If we have sub-directories and need a recursive monitor, then the thread becomes more resource intensive.

There was a JSR (Java Specification Request) requested to add / rewrite more I/O APIs for Java platform. This was implemented in JDK 7 as JSR 203 with support for APIs like file system access, scalable asynchronous I/O operations, socket-channel binding and configuration, and multicast datagrams.

JSR 203 is one of the big feature for JDK 7 (Developer Preview is available in java.sun.com) and its been implemented as the second I/O package is java, called as NIO.2. I will be looking into more of these packages in future posts, but in this, I will show how to monitor a directory and its sub-directories for any changes using NIO.2 (JDK 7).

The APIs which we will be using WatchService (a watch service that watches registered objects for changes and events), WatchKey (a token representing the registration of a watchable object with a WatchService) and WatchEvent (an event or a repeated event for an object that is registered with a WatchService) to monitor a directory. So, without further explanation, let’s start working on the code.

Please note that you need JDK 7 to run this program. While writing this post, JDK 7 is available as a EA (Early Access) in Java Early Access Downloads page. Download the JDK and install it.

The first step is to get a directory to monitor. Path is one of the new I/O API as a part of NIO.2 which gives us more control over the I/O. So let’s get the directory to watch, if you want to watch the directory recursively then there should be another boolean flag defined, but in this example we will watch only the parent directory.

Path _directotyToWatch = Paths.get(args[0]);

Now let’s create a Watch service to the above directory and add a key to the service. In the watch key we can define what are all the events we need to look for. In this example we will monitor Create, Delete & Rename/Modify of the files or directories in the path.

WatchService watcherSvc = FileSystems.getDefault().newWatchService();
WatchKey watchKey = _directotyToWatch.register(
    watcherSvc,ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);

Now we have all the variables defined. Let’s start a infinite loop to monitor the directory for any changes using WatchEvent. We will poll events in the directory and once some event is triggered (based on the WatchKey definition) we will print the type of event occurred and the name of the file/directory on which the event occurred. Once done, we will reset the watch key.

while (true) {
    watchKey=watcherSvc.take();
    for (WatchEvent<?> event: watchKey.pollEvents()) {
        WatchEvent<Path> watchEvent = castEvent(event);
        System.out.println(event.kind().name().toString() + " "
            + _directotyToWatch.resolve(watchEvent.context()));
    watchKey.reset();
    }
}

Now to make the WatchEvent <Path> work, we should create a small utility as below ( this is the castEvent which is used in the above code).

static <T> WatchEvent<T> castEvent(WatchEvent<?> event) {
    return (WatchEvent<T>)event;
}

Now compile the file and give a directory as a runtime parameter while running it. Once the program starts running, start creating some directories/files or modify/rename some files in the directory which you gave as a parameter, the program will start triggering the event and you should be able to watch the modifications in the console. A sample output from my machine is below.

Full source of this application is available in my github page.

import java.nio.file.*;
import static java.nio.file.StandardWatchEventKind.*;

static <T> WatchEvent<T> castEvent(WatchEvent<?> event) {
    return (WatchEvent<T>)event;
}

public static void main (String args[]) throws Exception {
    Path _directotyToWatch = Paths.get(args[0]);
    WatchService watcherSvc = FileSystems.getDefault().newWatchService();
    WatchKey watchKey = _directotyToWatch.register(watcherSvc,
        ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);

    while (true) {
        watchKey=watcherSvc.take();
        for (WatchEvent<?> event: watchKey.pollEvents()) {
            WatchEvent<Path> watchEvent = castEvent(event);
            System.out.println(event.kind().name().toString() + " "
                + _directotyToWatch.resolve(watchEvent.context()));
            watchKey.reset();
        }
    }
}