EmailLinkedInGoogle+TwitterFacebook

Source code discussed in this post can be freely downloaded and used in whatever way or form you want.  SVN URL is, svn checkout http://whiteboardjunkie.googlecode.com/svn/trunk/parallelly parallelly-read-only

JDK7 provides a library solution through java.nio.file.WatchService  to the classic folder watching dilemma Java developers went through for a decade.  It defines an interface closely matching the Observable.  JDK7 also provides concrete implementation classes implementing this interface to watch for folder changes.  The key performance boosts from this implementation is drawn from it using the OS interrupts to watch a folder (where ever those are available for consumption).  This approach frees up unnecessary cycle time and complexity in code for a ‘Watcher’ thread.  I liked it.  A good discussion of how to go about implementing a FolderWatcher is discussed here by Venish Joe in his post titled Monitor a Directory for Changes using Java.

When experimenting with this feature for one of the products we are building it annoyed the hell out of me that there is no listener paradigm out of the box for this feature.  I would have loved to register a  listener for particular events on a folder and then execute code only when these events occur.  That way my code will be free from the boilerplate instructions of polling for events and more annoyingly that infinite loop to poll for these events.   How I wished something like this was available.

		FolderWatchers.getInstance().addFolderListener(Paths.get("c:/boni/"),
				new ChangeListener(ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY) {
					@Override
					public void onEvent(WatchEvent<Path> anEvent) {
						System.out.println("LISTENER 1 " + anEvent.kind().name().toString()
								+ " " + anEvent.context());
					}
				});

Here I am registering a ChangeListener to be notified whenever there is any CREATE, DELETE or modify operations on path “c:/boni” (or it’s subfolders).  The meat of execution I am interested in is coded inside the ‘onEvent’.   This is a working code. What is done here is to take away all the mundane tasks of creating a watch service for a ‘Path’ and then polling through the events away.  The underlying constructs are completely thread safe.  Furthermore, ‘addFolderListener’ returns a ‘FolderWatcherFuture’ through which you can cancel the task.   Simple and elegant.  Without much further ado I present to you FolderWatchers (and its test case)

FolderWatchers.java

package org.boni.parallely.service;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class FolderWatchers {

	private static FolderWatchers watchers = null;
	private static WatchService watchService = null;
	private static final ExecutorService executor = Executors.newCachedThreadPool();
	private FolderWatchers() throws IOException{
		watchService = FileSystems.getDefault().newWatchService();
	}
	public static FolderWatchers getInstance() throws IOException {
		if (null == watchers){
			watchers = new FolderWatchers();
		}
		return watchers;
	}
	public FolderWatcherFuture addFolderListener(Path path, ChangeListener changeListener) throws IOException {
		FolderWatcher aWatcher = new FolderWatcher(path,changeListener,watchService);
		Future f = executor.submit(aWatcher);
		FolderWatcherFuture future = new FolderWatcherFuture(aWatcher, f);
		return future;
	}

	public void cancelFolderWatching(FolderWatcherFuture folderWatcherFuture) {

	}
}
class FolderWatcher implements Runnable{
	private WatchKey watchKey = null;
	private ChangeListener listener = null;
	protected FolderWatcher(Path path, ChangeListener changeListener, WatchService watchService) throws IOException {
		watchKey = path.register(watchService, changeListener.getEventTypes());
		listener = changeListener;
	}
	@Override
	public void run() {
		while(!cancel){
			for (WatchEvent anEvent : watchKey.pollEvents()){
				listener.onEvent((WatchEvent<Path>)anEvent);
			}
		}
	}
	private boolean cancel = false;
	protected void cancel(){
		this.cancel = true;
	}
}

class FolderWatcherFuture implements Future{
	private FolderWatcher watcher = null;
	private Future future = null;
	public FolderWatcherFuture(FolderWatcher watcher, Future future){
		this.watcher = watcher;
		this.future = future;
	}
	@Override
	public boolean cancel(boolean mayInterruptIfRunning) {
		watcher.cancel();
		return future.cancel(mayInterruptIfRunning);
	}
	@Override
	public Object get() throws InterruptedException, ExecutionException {
		return future.get();
	}
	@Override
	public Object get(long timeout, TimeUnit unit) throws InterruptedException,
			ExecutionException, TimeoutException {
		return future.get(timeout, unit);
	}
	@Override
	public boolean isCancelled() {
		return future.isCancelled();
	}
	@Override
	public boolean isDone() {
		return future.isDone();
	}
}

ChangeListener.java

package org.boni.parallely.service;

import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchEvent.Kind;

public abstract class ChangeListener {

	Kind[] eventTypes = null;
	public ChangeListener(Kind...eventTypes) {
		this.eventTypes = eventTypes;
	}
	public Kind[] getEventTypes() {
		return eventTypes;
	}
	public abstract void onEvent(WatchEvent<Path> anEvent);

}

TestFolderWatcher.java

package org.boni.parallely.service;




import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;




import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.WatchEvent;




import org.junit.Test;




public class TestFolderWatcher {
	@Test
	public void testFutureWatchService() throws IOException {
		FolderWatchers.getInstance().addFolderListener(Paths.get("c:/boni/"),
				new ChangeListener(ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY) {
					@Override
					public void onEvent(WatchEvent<Path> anEvent) {
						System.out.println("LISTENER 1 " + anEvent.kind().name().toString()
								+ " " + anEvent.context());
					}
				});
		FolderWatchers.getInstance().addFolderListener(Paths.get("C:/boni/installs"),
				new ChangeListener(ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY) {
					@Override
					public void onEvent(WatchEvent<Path> anEvent) {
						System.out.println("LISTENER 2 " + anEvent.kind().name().toString()
								+ " " + anEvent.context());
					}
				});
		while (true) {
			;
		}
	}
}

8 Thoughts on “FolderListener for JDK7 – Watch folder events more elegantly.

  1. Anonymous on June 8, 2012 at 10:43 pm said:

    Hey Boni, Thank you for this very interesting and interesting piece of code.

    Maybe you could make it explicit that it’s also required the ChangeListener.java file to make the code run correctly. It can cause misunderstandings since Swing also has a class called ChangeListener);

    In your test, lines 45 and 46, why did you use a while(true) with just a ; and no code in body? It looks useless for me, but maybe it’s used for the Unit test (I’m totally unfamiliar with Test).

  2. while(true){;} is an infinite loop. I was buying time to test the features. Basically I’d run the test and then copy/remove files into the watch folder and observe the system behavior. As of shutting down the thread – it will shutdown with the JVM. If you want to shutdown a specific watch future before the JVM shutdown, then just call method cancel() on the FolderWatcherFuture.

    Thanks for suggesting to include ChangeListener.java. I have modified the post include the source.

  3. Anonymous on October 14, 2012 at 1:05 pm said:

    hello i have a problem how i can know if the folder is opened?

    • I might be able to help if you can detail your problem. There is no notion of an ‘open’ or ‘closed’ folder at the operating system level, It only matters whether the folder exists or not.

  4. Anonymous on November 18, 2012 at 12:18 am said:

    Nice code but I must point out that in the run method of the class FolderWatchers should be added a sleep…

    @Override
    public void run() {
    while(!cancel){
    for (WatchEvent anEvent : watchKey.pollEvents()){
    listener.onEvent((WatchEvent)anEvent);
    }
    Thread.sleep(1000L);
    }
    }

    otherwise an entire processor core will be used for this cycle !

    • Anonymous on April 10, 2013 at 3:26 pm said:

      The problem with this code is that is not using the watcher,take() or pull() methods. Also the key.reset() method should be invoked after event completition.

      In the tests I suggest to use Mockito (or something similiar…EasyMock etc) and a CountDownLatch to wait for notifications.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Post Navigation