In Java 7 there were quite a lot of new things added to the File NIO package (java.nio)- there was a new java.nio.file package and java.nio.file.attribute package. Main highlights of the java.nio.file package are the following classes:
- Path: Its an object used to locate a file or directory in the file system. The value contained in the Path instance is platform dependent. For example on Windows the file separators would be “\” while in a Unix platform it would be “/”.
- Files: This class contains static methods which operate on the Path instance to create files, delete or move the files around and to even get the BufferedReader, BufferedWriter, InputStream, OutputStream among other methods.
- FileSystems: Provides static methods to create instance of FileSystem
- WatchService: A service for watching different locations in the file system for events like modification, creation, deletion. Classes which implement Watchable can be used to register with a watch service. An instance of WatchService can be obtained from the FileSystem instance.
- WatchKey: When a Watchable entity is registered with a WatchService a key which is a WatchKey is generated. Initially the key is in ready state waiting to be notified of any events on the Watchable entity. Once an event occurs the key goes into signaled state and allows to access the events using its pollEvents method. After processing the poll events the key has to be reset by invoking its reset method.
also read:
Lets look at an example of watching the “/tmp/nio” directory (Note: this example was executed on Unix platform, file separator would have to be changed for executing this in Windows platform) for Modification and Deletions happening in the directory.
We first have to create a Path instance to indicate this directory:
Path tmpPath = Paths.get("/tmp/nio/");
Obtain a WatchService from the FileSystem class:
WatchService watchService = FileSystems.getDefault().newWatchService();
Now we would have to register the Path created above with the WatchService created above and also specify the kind of events we are interested to watch. For the kind of events we make use of StandardWatchEventKinds class and its fields ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE.
/* * Watching the /tmp/nio/ directory * for MODIFY and DELETE operations */ tmpPath.register( watchService, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);
To watch the directory we place all the processing login in an infinite loop (we can also spawn a different thread to watch, but in this example lets keep it simple). And perform the following tasks:
1. Wait for any key to be signaled by invoking the take() method of the WatchService class. One can even use poll() method but the difference is that take() is a blocking call and waits until some key is signaled.
2. Once a WatchKey is obtained, poll for the events available in the key.
3. For each event find out the type of the event using event.kind().type() and also the Path which the event is acting on by using event.context() method.
The above 3 steps in code would be:
/* * Keep polling for events on the watched directory, */ for(;;){ WatchKey key = watchService.take(); //Poll all the events queued for the key for ( WatchEvent<?> event: key.pollEvents()){ WatchEvent.Kind kind = event.kind(); switch (kind.name()){ case "ENTRY_MODIFY": System.out.println("Modified: "+event.context()); break; case "ENTRY_DELETE": System.out.println("Delete: "+event.context()); break; } } //reset is invoked to put the key back to ready state boolean valid = key.reset(); //If the key is invalid, just exit. if ( !valid){ break; } }
Dont forget to reset() the key once done with handling the events. Otherwise the key would not be in Ready state to listen to more events.
Combining all the parts discussed above, the complete program would be:
import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.*; public class DirectoryWatcherDemo { public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException { Path tmpPath = Paths.get("/tmp/nio/"); WatchService watchService = FileSystems.getDefault().newWatchService(); //Watching the /tmp/nio/ directory //for MODIFY and DELETE operations tmpPath.register( watchService, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE); /* * Keep polling for events on the watched directory, */ for(;;){ WatchKey key = watchService.take(); //Poll all the events queued for the key for ( WatchEvent<?> event: key.pollEvents()){ WatchEvent.Kind kind = event.kind(); switch (kind.name()){ case "ENTRY_MODIFY": System.out.println("Modified: "+event.context()); break; case "ENTRY_DELETE": System.out.println("Delete: "+event.context()); break; } } //reset is invoked to put the key back to ready state boolean valid = key.reset(); //If the key is invalid, just exit. if ( !valid){ break; } } } }
Running the following commands in the terminal in “/tmp/nio” directory:
mohamed@mohamed:/tmp/nio$ ls mohamed@mohamed:/tmp/nio$ touch t1 mohamed@mohamed:/tmp/nio$ touch t2 mohamed@mohamed:/tmp/nio$ touch t3 mohamed@mohamed:/tmp/nio$ rm t1 mohamed@mohamed:/tmp/nio$ vim t2 mohamed@mohamed:/tmp/nio$ mkdir t4 mohamed@mohamed:/tmp/nio$ rm -r t4 mohamed@mohamed:/tmp/nio$ ls t2 t3
results in the following output in the directory watching program:
Modified: t1 Modified: t2 Modified: t3 Delete: t1 Delete: .t2.swpx Delete: .t2.swp Modified: .t2.swp Modified: .t2.swp Modified: t2 Modified: t2 Delete: .t2.swp Delete: t4
One thing to note here is that we did execute mkdir t4 to create a t4 directory but that wasn’t notified because we didn’t register the ENTRY_CREATE event. Also once the new directory is added one has to register the new directory with the WatchService. I will write about this in a separate post sometime later.
Do drop in your queries or any issues you are facing with the above example and explanation.