I'm trying to develop a watch service that, every time a piece of code is modifed, created or deleted, does a certain action.
I've made a Daemon that implements WatchService and that gets the path to a certain package to watch, for example "../JDV/src/randompackage/java/test/money_scenario".
This Daemon has the following code:
public Daemon(Path dir) throws IOException{
this.dir = dir;
watcher = FileSystems.getDefault().newWatchService();
WatchKey key = dir.register(watcher, ENTRY_CREATE,ENTRY_DELETE,
ENTRY_MODIFY);
}
public void processEvents() throws InitializationError {
for (;;) {
// wait for key to be signaled
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
return;
}
for (WatchEvent<?> event: key.pollEvents()) {
WatchEvent.Kind kind = event.kind();
if (kind == OVERFLOW) {
continue;
}
if(kind == ENTRY_CREATE) {
System.out.println("Creation has been detected in " + getDirName());
}
if(kind == ENTRY_DELETE) {
System.out.println("Deletion has been detected in " + getDirName());
}
if(kind == ENTRY_MODIFY) {
System.out.println("Modification has been detected in " + getDirName());
}
}
boolean valid = key.reset();
if (!valid) {
break;
}
}
}
What happens is the following: I run the Daemon and it is active. Whenever I go Create or Delete a class in in the money_scenario package (which itselves has subpackages from where I could delete/create the class), the Daemon detects it and prints "Modification has been detected", instead of creation/deletion. When I modify a class in the money_scenario package, it doesn't detect anything.
What am I doing wrong?
Edit: Resolved but another issue popped up. Multiple events:
When I delete a class for example I get:
Modification has been detected in money_scenario
Deletion has been detected in money_scenario
Modification has been detected in money_scenario
I'm really not sure what to tell you on this one, because I took your code and it ran fine on my machine.
The only thing I removed was the throws InitializationError, though I doubt that it has something to do with it.
Config: Mac OS X 10.9/Java 1.7.0_45/Intellij IDEA 12.1.6
P.S.
which itselves has subpackages from where I could delete/create the class
WatchService only watches the directory that you point to, if you want to watch the subdirectories you need to recursively walk and register each directory in the subtree.
EDIT:
public void processEvents() {
boolean finished = false;
while (!finished) {
// wait for key to be signaled
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
return;
}
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
switch (kind.name()) {
case "ENTRY_CREATE":
System.out.println("Creation has been detected in " + getDirName());
break;
case "ENTRY_DELETE":
System.out.println("Deletion has been detected in " + getDirName());
break;
case "ENTRY_MODIFY":
System.out.println("Modification has been detected in " + getDirName());
break;
default:
continue;
}
if (!key.reset()) {
finished = true;
}
}
}
}
Related
I can monitor a directory by registering cwith a WatchKey (there are tons of examples on the web) however this watcher catches every single event. E.g. On windows If am monitoring the d:/temp dir and I create a new .txt file and rename it I get the following events.
ENTRY_CREATE: d:\temp\test\New Text Document.txt
ENTRY_MODIFY: d:\temp\test
ENTRY_DELETE: d:\temp\test\New Text Document.txt
ENTRY_CREATE: d:\temp\test\test.txt
ENTRY_MODIFY: d:\temp\test
I want to perform an action when the new file is created or updated. However I don't want the action to run 5 times in the above example.
My 1st Idea: As I only need to run the action (in this case a push to a private Git server) once every now an then (e.g. check every 10 seconds only if there are changes to the monitored directory and only then perform the push) I thought of having an object with a boolean parameter that I can get and set from within separate threads.
Now this works kinda ok (unless the gurus can help educated me as to why this is a terrible idea) The problem is that if a file event is caught during the SendToGit thread's operation and this operation completes it sets the "Found" parameter to false. Immediately thereafter one of the other events are caught (as in the example above) they will set the "Found" parameter to true again. This is not ideal as I will then run the SendToGit operation immediately again which will be unnecessary.
My 2nd Idea Investigate pausing the check for changes in the MonitorFolder thread until the SendToGit operation is complete (I.e. Keep checking if the ChangesFound Found parameter has been set back to false. When this parameter is false start checking for changes again.
Questions
Is this an acceptable way to go or have I gone down a rabbit hole with no hope of return?
If I go down the road of my 2nd idea what happens if I am busy with the SendToGit operation and a change is made in the monitoring folder? I suspect that this will not be identified and I may miss changes.
The Rest of the code
ChangesFound.java
package com.acme;
public class ChangesFound {
private boolean found = false;
public boolean wereFound() {
return this.found;
}
public void setFound(boolean commitToGit) {
this.found = commitToGit;
}
}
In my main app I start 2 threads.
MonitorFolder.java Monitors the directory and when Watcher events are found set the ChangesFound variable "found" to true.
SendToGit.java Every 10 seconds checks if the ChangesFound variable found is true and if it is performs the push. (or in this case just prints a message)
Here is my App that starts the threads:
package com.acme;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
public class App {
private static ChangesFound chg;
public static void main(String[] args) throws IOException {
String dirToMonitor = "D:/Temp";
boolean recursive = true;
chg = new ChangesFound();
Runnable r = new SendToGit(chg);
new Thread(r).start();
Path dir = Paths.get(dirToMonitor);
Runnable m = new MonitorFolder(chg, dir, recursive);
new Thread(m).start();
}
}
SendToGit.java
package com.acme;
public class SendToGit implements Runnable {
private ChangesFound changes;
public SendToGit(ChangesFound chg) {
changes = chg;
}
public void run() {
while (true) {
try {
Thread.sleep(10000);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
System.out.println(java.time.LocalDateTime.now() + " [SendToGit] waking up.");
if (changes.wereFound()) {
System.out.println("\t***** CHANGES FOUND push to Git.");
changes.setFound(false);
} else {
System.out.println("\t***** Nothing changed.");
}
}
}
}
MonitorFolder.java (Apologies for the long class I only added this here in case it helps someone else.)
package com.acme;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
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 static java.nio.file.StandardWatchEventKinds.OVERFLOW;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;
public class MonitorFolder implements Runnable {
private static WatchService watcher;
private static Map<WatchKey, Path> keys;
private static boolean recursive;
private static boolean trace = false;
private static boolean commitGit = false;
private static ChangesFound changes;
#SuppressWarnings("unchecked")
static <T> WatchEvent<T> cast(WatchEvent<?> event) {
return (WatchEvent<T>) event;
}
/**
* Creates a WatchService and registers the given directory
*/
MonitorFolder(ChangesFound chg, Path dir, boolean rec) throws IOException {
changes = chg;
watcher = FileSystems.getDefault().newWatchService();
keys = new HashMap<WatchKey, Path>();
recursive = rec;
if (recursive) {
System.out.format("[MonitorFolder] Scanning %s ...\n", dir);
registerAll(dir);
System.out.println("Done.");
} else {
register(dir);
}
// enable trace after initial registration
this.trace = true;
}
/**
* Register the given directory with the WatchService
*/
private static void register(Path dir) throws IOException {
WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
if (trace) {
Path prev = keys.get(key);
if (prev == null) {
System.out.format("register: %s\n", dir);
} else {
if (!dir.equals(prev)) {
System.out.format("update: %s -> %s\n", prev, dir);
}
}
}
keys.put(key, dir);
}
/**
* Register the given directory, and all its sub-directories, with the
* WatchService.
*/
private static void registerAll(final Path start) throws IOException {
// register directory and sub-directories
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
#Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
register(dir);
return FileVisitResult.CONTINUE;
}
});
}
/**
* Process all events for keys queued to the watcher
*/
public void run() {
for (;;) {
// wait for key to be signalled
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
return;
}
Path dir = keys.get(key);
if (dir == null) {
System.err.println("WatchKey not recognized!!");
continue;
}
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind kind = event.kind();
// TBD - provide example of how OVERFLOW event is handled
if (kind == OVERFLOW) {
System.out.println("Something about Overflow");
continue;
}
// Context for directory entry event is the file name of entry
WatchEvent<Path> ev = cast(event);
Path name = ev.context();
Path child = dir.resolve(name);
// print out event and set ChangesFound object Found parameter to True
System.out.format("[MonitorFolder] " + java.time.LocalDateTime.now() + " - %s: %s\n", event.kind().name(), child);
changes.setFound(true);
// if directory is created, and watching recursively, then
// register it and its sub-directories
if (recursive && (kind == ENTRY_CREATE)) {
try {
if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
registerAll(child);
}
} catch (IOException x) {
// ignore to keep sample readbale
}
}
}
// reset key and remove from set if directory no longer accessible
boolean valid = key.reset();
if (!valid) {
keys.remove(key);
// all directories are inaccessible
if (keys.isEmpty()) {
System.out.println("keys.isEmpty");
break;
}
}
}
}
}
Both of your strategies will lead to issues because the Watch Service is very verbose and sends many messages when maybe one or two is actually needed to your downstream handling - so sometimes you may do unnecessary work or miss events.
When using WatchService you could collate multiple notifications together and pass on as ONE event listing a sets of recent deletes, creates and updates:
DELETE followed by CREATE => sent as UPDATE
CREATE followed by MODIFY => sent as CREATE
CREATE or MODIFY followed by DELETE => sent as DELETE
Instead of calling WatchService.take() and acting on each message, use WatchService.poll(timeout) and only when nothing is returned act on the union of preceeding set of events as one - not individually after each successful poll.
It is easier to decouple the problems as two components so that you don't repeat the WatchService code the next time you need it:
A watch manager which handles watch service + dir registrations and collates the duplicates to send to event listeners as ONE group
A Listener class to receive the group of changes and act on the set.
This example may help illustrate - see WatchExample which is the manager which sets up the registrations BUT passes on much fewer events to the callback defined by setListener. You could set up MonitorFolder like WatchExample to reduce the events discovered, and make your code in SendToGit as a Listener which is called on demand with the aggregated set of fileChange(deletes, creates, updates).
public static void main(String[] args) throws IOException, InterruptedException {
final List<Path> dirs = Arrays.stream(args).map(Path::of).map(Path::toAbsolutePath).collect(Collectors.toList());
Kind<?> [] kinds = { StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE};
// Should launch WatchExample PER Filesystem:
WatchExample w = new WatchExample();
w.setListener(WatchExample::fireEvents);
for(Path dir : dirs)
w.register(kinds, dir);
// For 2 or more WatchExample use: new Thread(w[n]::run).start();
w.run();
}
public class WatchExample implements Runnable {
private final Set<Path> created = new LinkedHashSet<>();
private final Set<Path> updated = new LinkedHashSet<>();
private final Set<Path> deleted = new LinkedHashSet<>();
private volatile boolean appIsRunning = true;
// Decide how sensitive the polling is:
private final int pollmillis = 100;
private WatchService ws;
private Listener listener = WatchExample::fireEvents;
#FunctionalInterface
interface Listener
{
public void fileChange(Set<Path> deleted, Set<Path> created, Set<Path> modified);
}
WatchExample() {
}
public void setListener(Listener listener) {
this.listener = listener;
}
public void shutdown() {
System.out.println("shutdown()");
this.appIsRunning = false;
}
public void run() {
System.out.println();
System.out.println("run() START watch");
System.out.println();
try(WatchService autoclose = ws) {
while(appIsRunning) {
boolean hasPending = created.size() + updated.size() + deleted.size() > 0;
System.out.println((hasPending ? "ws.poll("+pollmillis+")" : "ws.take()")+" as hasPending="+hasPending);
// Use poll if last cycle has some events, as take() may block
WatchKey wk = hasPending ? ws.poll(pollmillis,TimeUnit.MILLISECONDS) : ws.take();
if (wk != null) {
for (WatchEvent<?> event : wk.pollEvents()) {
Path parent = (Path) wk.watchable();
Path eventPath = (Path) event.context();
storeEvent(event.kind(), parent.resolve(eventPath));
}
boolean valid = wk.reset();
if (!valid) {
System.out.println("Check the path, dir may be deleted "+wk);
}
}
System.out.println("PENDING: cre="+created.size()+" mod="+updated.size()+" del="+deleted.size());
// This only sends new notifications when there was NO event this cycle:
if (wk == null && hasPending) {
listener.fileChange(deleted, created, updated);
deleted.clear();
created.clear();
updated.clear();
}
}
}
catch (InterruptedException e) {
System.out.println("Watch was interrupted, sending final updates");
fireEvents(deleted, created, updated);
}
catch (IOException e) {
throw new UncheckedIOException(e);
}
System.out.println("run() END watch");
}
public void register(Kind<?> [] kinds, Path dir) throws IOException {
System.out.println("register watch for "+dir);
// If dirs are from different filesystems WatchService will give errors later
if (this.ws == null) {
ws = dir.getFileSystem().newWatchService();
}
dir.register(ws, kinds);
}
/**
* Save event for later processing by event kind EXCEPT for:
* <li>DELETE followed by CREATE => store as MODIFY
* <li>CREATE followed by MODIFY => store as CREATE
* <li>CREATE or MODIFY followed by DELETE => store as DELETE
*/
private void
storeEvent(Kind<?> kind, Path path) {
System.out.println("STORE "+kind+" path:"+path);
boolean cre = false;
boolean mod = false;
boolean del = kind == StandardWatchEventKinds.ENTRY_DELETE;
if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
mod = deleted.contains(path);
cre = !mod;
}
else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
cre = created.contains(path);
mod = !cre;
}
addOrRemove(created, cre, path);
addOrRemove(updated, mod, path);
addOrRemove(deleted, del, path);
}
// Add or remove from the set:
private static void addOrRemove(Set<Path> set, boolean add, Path path) {
if (add) set.add(path);
else set.remove(path);
}
public static void fireEvents(Set<Path> deleted, Set<Path> created, Set<Path> modified) {
System.out.println();
System.out.println("fireEvents START");
for (Path path : deleted)
System.out.println(" DELETED: "+path);
for (Path path : created)
System.out.println(" CREATED: "+path);
for (Path path : modified)
System.out.println(" UPDATED: "+path);
System.out.println("fireEvents END");
System.out.println();
}
}
I have bellow code to monitor a directory for any file changes.
public class DirectoryWatcherExample {
public static void main(String[] args) {
WatchService watchService
= FileSystems.getDefault().newWatchService();
Path path = Paths.get(System.getProperty("user.home"));
path.register(
watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);
WatchKey key;
while ((key = watchService.take()) != null) {
for (WatchEvent<?> event : key.pollEvents()) {
System.out.println(
"Event kind:" + event.kind()
+ ". File affected: " + event.context() + ".");
}
key.reset();
}
}
}
Currently this is a stand alone and is being executed from my eclipse IDE. Now I need to automate it and I should not run this class everytime. Would you please help me as to how I can remove main() method and run it? Can I create a exeucutable jar and run it? If i run it once then will the program continuosly listen the directlroy? When will the program stops listening the directory? Is there any time limit, I mean after a few days?
Im watching a directory using Java 7 nio WatchService by using below method.
Path myDir = Paths.get("/rootDir");
try {
WatchService watcher = myDir.getFileSystem().newWatchService();
myDir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
WatchKey watckKey = watcher.take();
List<WatchEvent<?>> events = watckKey.pollEvents();
for (WatchEvent event : events) {
if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
System.out.println("Created: " + event.context().toString());
JOptionPane.showMessageDialog(null,"Created: " + event.context().toString());
}
if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
System.out.println("Delete: " + event.context().toString());
JOptionPane.showMessageDialog(null,"Delete: " + event.context().toString());
}
if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
System.out.println("Modify: " + event.context().toString());
JOptionPane.showMessageDialog(null,"Modify: " + event.context().toString());
}
}
} catch (Exception e) {
System.out.println("Error: " + e.toString());
}
But the above method only respond in one events happen in the directory after that watcher does not respond to the events happen in that folder. Is there a way that I can modify this to capture all events happen inside the folder. I also want to modify this to capture events happen in sub folders too. Can someone help me with that.
Thank you.
From the JavaDoc of WatchService:
A Watchable object is registered with a watch service by invoking its register method, returning a WatchKey to represent the registration. When an event for an object is detected the key is signalled, and if not currently signalled, it is queued to the watch service so that it can be retrieved by consumers that invoke the poll or take methods to retrieve keys and process events. Once the events have been processed the consumer invokes the key's reset method to reset the key which allows the key to be signalled and re-queued with further events.
You are only calling watcher.take() once.
To watch for further events, you must call watchKey.reset() after consuming the WatchEvents. Put all this in a loop.
while (true) {
WatchKey watckKey = watcher.take();
List<WatchEvent<?>> events = watckKey.pollEvents();
for (WatchEvent event : events) {
// process event
}
watchKey.reset();
}
Also take a look at the relevant section of the Java Tutorial.
Use Apache Commons IO File Monitoring
it will also capture events happening in sub folders too
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.monitor.FileAlterationListener;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
public class Monitor {
public Monitor() {
}
//path to a folder you are monitoring .
public static final String FOLDER = MYPATH;
public static void main(String[] args) throws Exception {
System.out.println("monitoring started");
// The monitor will perform polling on the folder every 5 seconds
final long pollingInterval = 5 * 1000;
File folder = new File(FOLDER);
if (!folder.exists()) {
// Test to see if monitored folder exists
throw new RuntimeException("Directory not found: " + FOLDER);
}
FileAlterationObserver observer = new FileAlterationObserver(folder);
FileAlterationMonitor monitor =
new FileAlterationMonitor(pollingInterval);
FileAlterationListener listener = new FileAlterationListenerAdaptor() {
// Is triggered when a file is created in the monitored folder
#Override
public void onFileCreate(File file) {
// "file" is the reference to the newly created file
System.out.println("File created: "+ file.getCanonicalPath());
}
// Is triggered when a file is deleted from the monitored folder
#Override
public void onFileDelete(File file) {
try {
// "file" is the reference to the removed file
System.out.println("File removed: "+ file.getCanonicalPath());
// "file" does not exists anymore in the location
System.out.println("File still exists in location: "+ file.exists());
} catch (IOException e) {
e.printStackTrace(System.err);
}
}
};
observer.addListener(listener);
monitor.addObserver(observer);
monitor.start();
}
}
I want to fire an event whenever a file has stopped being modified. I am polling a directory and if the filename matches to "input.csv" then i want to fire an event on that file. Time to check whether the file has been modified is 10 seconds. I am getting a null pointer exception when no event occurs.
This is my sample code:
public class MyPoller {
public static void main(String[] args) throws URISyntaxException,
IOException, InterruptedException {
Path tmpPath = Paths.get("E:/MyDirectory/DEC");
WatchService watchService = FileSystems.getDefault().newWatchService();
// Watching the E:/MyDirectory/DEC directory
// for MODIFY and DELETE operations
tmpPath.register(watcput.csvService, StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
for (;;) {
WatchKey key = watchService.poll(10000,TimeUnit.MILLISECONDS);
List<WatchEvent<?>> events = key.pollEvents();
// Poll all the events queued for the key
for (WatchEvent event : events) {
switch (event.context().toString()) {
case "input.csv":
System.out.println("Modified: " + event.context());
String filename = "" + event.context();
System.out.println(" "+filename);
break;
default:
break;
}
}
boolean valid = key.reset();
// If the key is invalid, just exit.
if (!valid) {
break;
}
}
}
}
If no event is generated, then the WatchKey may be null
WatchKey key = watchService.poll(10000,TimeUnit.MILLISECONDS);
Try to do a null check on the key and then proceed to do pollEvents() if it's indeed not null.
When I'm trying to register a file instead of a directory java.nio.file.NotDirectoryException is thrown. Can I listen for a single file change, not the whole directory?
Just filter the events for the file you want in the directory:
final Path path = FileSystems.getDefault().getPath(System.getProperty("user.home"), "Desktop");
System.out.println(path);
try (final WatchService watchService = FileSystems.getDefault().newWatchService()) {
final WatchKey watchKey = path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
while (true) {
final WatchKey wk = watchService.take();
for (WatchEvent<?> event : wk.pollEvents()) {
//we only register "ENTRY_MODIFY" so the context is always a Path.
final Path changed = (Path) event.context();
System.out.println(changed);
if (changed.endsWith("myFile.txt")) {
System.out.println("My file has changed");
}
}
// reset the key
boolean valid = wk.reset();
if (!valid) {
System.out.println("Key has been unregisterede");
}
}
}
Here we check whether the changed file is "myFile.txt", if it is then do whatever.
Other answers are right that you must watch a directory and filter for your particular file. However, you probably want a thread running in the background. The accepted answer can block indefinitely on watchService.take(); and doesn't close the WatchService. A solution suitable for a separate thread might look like:
public class FileWatcher extends Thread {
private final File file;
private AtomicBoolean stop = new AtomicBoolean(false);
public FileWatcher(File file) {
this.file = file;
}
public boolean isStopped() { return stop.get(); }
public void stopThread() { stop.set(true); }
public void doOnChange() {
// Do whatever action you want here
}
#Override
public void run() {
try (WatchService watcher = FileSystems.getDefault().newWatchService()) {
Path path = file.toPath().getParent();
path.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);
while (!isStopped()) {
WatchKey key;
try { key = watcher.poll(25, TimeUnit.MILLISECONDS); }
catch (InterruptedException e) { return; }
if (key == null) { Thread.yield(); continue; }
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
#SuppressWarnings("unchecked")
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path filename = ev.context();
if (kind == StandardWatchEventKinds.OVERFLOW) {
Thread.yield();
continue;
} else if (kind == java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY
&& filename.toString().equals(file.getName())) {
doOnChange();
}
boolean valid = key.reset();
if (!valid) { break; }
}
Thread.yield();
}
} catch (Throwable e) {
// Log or rethrow the error
}
}
}
I tried working from the accepted answer and this article. You should be able to use this thread with new FileWatcher(new File("/home/me/myfile")).start() and stop it by calling stopThread() on the thread.
No it isn't possible to register a file, the watch service doesn't work this way. But registering a directory actually watches changes on the directory children (the files and sub-directories), not the changes on the directory itself.
If you want to watch a file, then you register the containing directory with the watch service. Path.register() documentation says:
WatchKey java.nio.file.Path.register(WatchService watcher, Kind[] events, Modifier...
modifiers) throws IOException
Registers the file located by this path with a watch service.
In this release, this path locates a directory that exists. The directory is registered with the watch service so that entries in the directory can be watched
Then you need to process events on entries, and detect those related to the file you are interested in, by checking the context value of the event. The context value represents the name of the entry (actually the path of the entry relatively to the path of its parent, which is exactly the child name). You have an example here.
Apache offers a FileWatchDog class with a doOnChange method.
private class SomeWatchFile extends FileWatchdog {
protected SomeWatchFile(String filename) {
super(filename);
}
#Override
protected void doOnChange() {
fileChanged= true;
}
}
And where ever you want you can start this thread:
SomeWatchFile someWatchFile = new SomeWatchFile (path);
someWatchFile.start();
The FileWatchDog class polls a file's lastModified() timestamp. The native WatchService from Java NIO is more efficient, since notifications are immediate.
You cannot watch an individual file directly but you can filter out what you don't need.
Here is my FileWatcher class implementation:
import java.io.File;
import java.nio.file.*;
import java.nio.file.WatchEvent.Kind;
import static java.nio.file.StandardWatchEventKinds.*;
public abstract class FileWatcher
{
private Path folderPath;
private String watchFile;
public FileWatcher(String watchFile)
{
Path filePath = Paths.get(watchFile);
boolean isRegularFile = Files.isRegularFile(filePath);
if (!isRegularFile)
{
// Do not allow this to be a folder since we want to watch files
throw new IllegalArgumentException(watchFile + " is not a regular file");
}
// This is always a folder
folderPath = filePath.getParent();
// Keep this relative to the watched folder
this.watchFile = watchFile.replace(folderPath.toString() + File.separator, "");
}
public void watchFile() throws Exception
{
// We obtain the file system of the Path
FileSystem fileSystem = folderPath.getFileSystem();
// We create the new WatchService using the try-with-resources block
try (WatchService service = fileSystem.newWatchService())
{
// We watch for modification events
folderPath.register(service, ENTRY_MODIFY);
// Start the infinite polling loop
while (true)
{
// Wait for the next event
WatchKey watchKey = service.take();
for (WatchEvent<?> watchEvent : watchKey.pollEvents())
{
// Get the type of the event
Kind<?> kind = watchEvent.kind();
if (kind == ENTRY_MODIFY)
{
Path watchEventPath = (Path) watchEvent.context();
// Call this if the right file is involved
if (watchEventPath.toString().equals(watchFile))
{
onModified();
}
}
}
if (!watchKey.reset())
{
// Exit if no longer valid
break;
}
}
}
}
public abstract void onModified();
}
To use this, you just have to extend and implement the onModified() method like so:
import java.io.File;
public class MyFileWatcher extends FileWatcher
{
public MyFileWatcher(String watchFile)
{
super(watchFile);
}
#Override
public void onModified()
{
System.out.println("Modified!");
}
}
Finally, start watching the file:
String watchFile = System.getProperty("user.home") + File.separator + "Desktop" + File.separator + "Test.txt";
FileWatcher fileWatcher = new MyFileWatcher(watchFile);
fileWatcher.watchFile();
Not sure about others, but I groan at the amount of code needed to watch a single file for changes using the basic WatchService API. It has to be simpler!
Here are a couple of alternatives using third party libraries:
Using Apache Commons Configuration
Using spring-loaded package from the Spring Framework (didn't find an example implementation for this off-hand, but it looks straight-forward to use)
I have created a wrapper around Java 1.7's WatchService that allows registering a directory and any number of glob patterns. This class will take care of the filtering and only emit events you are interested in.
try {
DirectoryWatchService watchService = new SimpleDirectoryWatchService(); // May throw
watchService.register( // May throw
new DirectoryWatchService.OnFileChangeListener() {
#Override
public void onFileCreate(String filePath) {
// File created
}
#Override
public void onFileModify(String filePath) {
// File modified
}
#Override
public void onFileDelete(String filePath) {
// File deleted
}
},
<directory>, // Directory to watch
<file-glob-pattern-1>, // E.g. "*.log"
<file-glob-pattern-2>, // E.g. "input-?.txt"
<file-glob-pattern-3>, // E.g. "config.ini"
... // As many patterns as you like
);
watchService.start(); // The actual watcher runs on a new thread
} catch (IOException e) {
LOGGER.error("Unable to register file change listener for " + fileName);
}
Complete code is in this repo.
I extended the solution by BullyWiiPlaza a bit, for integration with javafx.concurrent, e.g. javafx.concurrent.Taskand javafx.concurrent.Service.
Also I added possibility to track multiple files.
Task:
import javafx.concurrent.Task;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.nio.file.*;
import java.util.*;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
#Slf4j
public abstract class FileWatcherTask extends Task<Void> {
static class Entry {
private final Path folderPath;
private final String watchFile;
Entry(Path folderPath, String watchFile) {
this.folderPath = folderPath;
this.watchFile = watchFile;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Entry entry = (Entry) o;
return Objects.equals(folderPath, entry.folderPath) && Objects.equals(watchFile, entry.watchFile);
}
#Override
public int hashCode() {
return Objects.hash(folderPath, watchFile);
}
}
private final List<Entry> entryList;
private final Map<WatchKey, Entry> watchKeyEntryMap;
public FileWatcherTask(Iterable<String> watchFiles) {
this.entryList = new ArrayList<>();
this.watchKeyEntryMap = new LinkedHashMap<>();
for (String watchFile : watchFiles) {
Path filePath = Paths.get(watchFile);
boolean isRegularFile = Files.isRegularFile(filePath);
if (!isRegularFile) {
// Do not allow this to be a folder since we want to watch files
throw new IllegalArgumentException(watchFile + " is not a regular file");
}
// This is always a folder
Path folderPath = filePath.getParent();
// Keep this relative to the watched folder
watchFile = watchFile.replace(folderPath.toString() + File.separator, "");
Entry entry = new Entry(folderPath, watchFile);
entryList.add(entry);
log.debug("Watcher initialized for {} entries. ({})", entryList.size(), entryList.stream().map(e -> e.watchFile + "-" + e.folderPath).findFirst().orElse("<>"));
}
}
public FileWatcherTask(String... watchFiles) {
this(Arrays.asList(watchFiles));
}
public void watchFile() throws Exception {
// We obtain the file system of the Path
// FileSystem fileSystem = folderPath.getFileSystem();
// TODO: use the actual file system instead of default
FileSystem fileSystem = FileSystems.getDefault();
// We create the new WatchService using the try-with-resources block
try (WatchService service = fileSystem.newWatchService()) {
log.debug("Watching filesystem {}", fileSystem);
for (Entry e : entryList) {
// We watch for modification events
WatchKey key = e.folderPath.register(service, ENTRY_MODIFY);
watchKeyEntryMap.put(key, e);
}
// Start the infinite polling loop
while (true) {
// Wait for the next event
WatchKey watchKey = service.take();
for (Entry e : entryList) {
// Call this if the right file is involved
var hans = watchKeyEntryMap.get(watchKey);
if (hans != null) {
for (WatchEvent<?> watchEvent : watchKey.pollEvents()) {
// Get the type of the event
WatchEvent.Kind<?> kind = watchEvent.kind();
if (kind == ENTRY_MODIFY) {
Path watchEventPath = (Path) watchEvent.context();
onModified(e.watchFile);
}
if (!watchKey.reset()) {
// Exit if no longer valid
log.debug("Watch key {} was reset", watchKey);
break;
}
}
}
}
}
}
}
#Override
protected Void call() throws Exception {
watchFile();
return null;
}
public abstract void onModified(String watchFile);
}
Service:
public abstract class FileWatcherService extends Service<Void> {
private final Iterable<String> files;
public FileWatcherService(Iterable<String> files) {
this.files = files;
}
#Override
protected Task<Void> createTask() {
return new FileWatcherTask(files) {
#Override
public void onModified(String watchFile) {
FileWatcherService.this.onModified(watchFile);
}
};
}
abstract void onModified(String watchFile);
}