Skip to content

Instantly share code, notes, and snippets.

@hacklschorsch
Last active July 15, 2019 22:12
Show Gist options
  • Save hacklschorsch/b22d5e750b18143fe9b2 to your computer and use it in GitHub Desktop.
Save hacklschorsch/b22d5e750b18143fe9b2 to your computer and use it in GitHub Desktop.
Java observable that notifies on changes to a File using Java 7 NIO and a separate thread.
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.util.Observable;
import static java.nio.file.StandardWatchEventKinds.*;
/**
* FileChangeNotifier notifies its observers when @file has changed.
* It uses Java 7 NIO and runs in a separate thread (since the NIO call blocks).
*
* Thanks to https://docs.oracle.com/javase/tutorial/essential/io/notification.html and
* the example code at http://docs.oracle.com/javase/tutorial/essential/io/examples/WatchDir.java
* from where this code has been distilled.
*
* Usage:
* FileChangeNotifier fileReloader = new FileChangeNotifier(file);
* fileReloader.addObserver((Observable obj, Object arg) -> {
* System.out.println("File changed for the " + arg + " time.");
* });
*
* @author Florian Sesser <[email protected]>
* @date 2015-07-31, 2015-08-18
*
* I hereby put this work under the MIT license. Use as you like,
* but you make me happy if you provide attribution (for when I vanity
* google myself ;). Except if you're military. In that case, don't use,
* don't provide attribution, and flat out fuck you.
* @see http://choosealicense.com/licenses/mit/
*/
public class FileChangeNotifier extends Observable implements Runnable, Closeable {
private final WatchService watcher = FileSystems.getDefault().newWatchService();
private final Path path;
private final File toWatch;
private Integer count = 0;
private long lastModi = 0; // timestamp to merge multiple events into one.
// (I got up to 20 events when saving a 800 KB
// xml file in vim on linux - FS 2015-07-30)
private boolean ignoreNext = false; // if we plan to modify the file ourselves, stay cool
public FileChangeNotifier(File file) throws IOException {
toWatch = file.getAbsoluteFile();
path = toWatch.toPath().getParent();
try {
path.register(watcher, ENTRY_MODIFY);
} catch (IOException x) {
System.err.println("Failed to watch " + file.getAbsolutePath());
System.err.println(x);
return;
}
new Thread(this).start();
}
public void close() throws IOException {
deleteObservers();
watcher.close();
}
public void run() {
for (;;) {
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
System.err.println(x);
return;
} catch (ClosedWatchServiceException x) {
// This is expected to happen when we stop watching for a file.
// With the return statement, the thread will stop.
return;
}
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind kind = event.kind();
if (kind == OVERFLOW) {
// TBD - how to handle the overflow event?
continue;
}
// Context for directory entry event is the file name of entry
WatchEvent<Path> ev = cast(event);
Path name = ev.context();
Path child = path.resolve(name);
if (child.equals(toWatch.toPath())) { // TODO: Is there no better way to watch only a single file? ~ FS 2015-07-30
if (lastModi < toWatch.lastModified()) {
lastModi = toWatch.lastModified();
if (!doIgnoreNext()) {
setChanged();
notifyObservers(++count);
}
}
}
boolean valid = key.reset();
if (!valid) {
continue;
}
}
}
}
/**
* Ignore the next event (for when we modify the file ourselves).
*/
public void ignoreNext() {
ignoreNext = true;
}
/* Returns whether to ignore the next event and toggle the ignore flag. */
private boolean doIgnoreNext() {
if (ignoreNext) {
ignoreNext = false;
return true;
} else {
return false;
}
}
// Comes straight from the Oracle, must be good
// see http://docs.oracle.com/javase/tutorial/essential/io/examples/WatchDir.java
@SuppressWarnings("unchecked")
static <T> WatchEvent<T> cast(WatchEvent<?> event) {
return (WatchEvent<T>)event;
}
public final File getFile() {
return toWatch;
}
}
@rahogata
Copy link

Hi can I use this code for my project?

@hacklschorsch
Copy link
Author

Hi can I use this code for my project?

Yes of course, you're most welcome. I think the MIT license is nice (see the code comment above), if that works for you. Also see the discussion about this code on StackOverflow, especially if you intend to watch many files instead of one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment