WatchService sounded like an exciting idea ... unfortunately it seems to be as low-level as warned in the tutorial/api plus doesn\'t really fit into the Swing event model (o
Because your background thread is devoted entirely to watching, take() is the right choice. It effectively hides the platform dependent implementation, which may either forward or poll. One of the poll() methods would be appropriate if, for example, your background thread also needed to examine other queues in series with the WatchService
.
Addendum: Because the WatchKey has state, it should probably not be forwarded to process()
. The context() of a WatchEvent is a "relative path between the directory registered with the watch service and the entry that is created, deleted, or modified." One of the resolve() methods should work if the directories share a common root.
With regard to your second point, couldn't you create a class that holds both WatchEvent and key and have the SwingWorker's second generic parameter be this type? Sorry, I know you've already thought of this, so I guess my question is: any down-side to doing this?
Actually, @Eels's comment didn't stop knocking in the back of my head - and finally registered: it's the way to go, but there is no need for any "artificial" struct, because we already have the perfect candidate - it's the PropertyChangeEvent itself :-)
Taking the overall process description from my question, the first three bullets remain the same
Revised FileWorker
:
@SuppressWarnings("unchecked")
public class FileWorker extends SwingWorker<Void, PropertyChangeEvent> {
public static final String FILE_DELETED = StandardWatchEventKinds.ENTRY_DELETE.name();
public static final String FILE_CREATED = StandardWatchEventKinds.ENTRY_CREATE.name();
public static final String FILE_MODIFIED = StandardWatchEventKinds.ENTRY_MODIFY.name();
// final version will keep a map of keys/directories (just as in the tutorial example)
private Path directory;
private WatchService watcher;
public FileWorker(File file) throws IOException {
directory = file.toPath();
watcher = FileSystems.getDefault().newWatchService();
directory.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
}
@Override
protected Void doInBackground() throws Exception {
for (;;) {
// wait for key to be signalled
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
return null;
}
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
// TBD - provide example of how OVERFLOW event is handled
if (kind == OVERFLOW) {
continue;
}
publish(createChangeEvent((WatchEvent<Path>) event, key));
}
// reset key return if directory no longer accessible
boolean valid = key.reset();
if (!valid) {
break;
}
}
return null;
}
/**
* Creates and returns the change notification. This method is called from the
* worker thread while looping through the events as received from the Watchkey.
*
* @param event
* @param key
*/
protected PropertyChangeEvent createChangeEvent(WatchEvent<Path> event, WatchKey key) {
Path name = event.context();
// real world will lookup the directory from the key/directory map
Path child = directory.resolve(name);
PropertyChangeEvent e = new PropertyChangeEvent(this, event.kind().name(), null, child.toFile());
return e;
}
@Override
protected void process(List<PropertyChangeEvent> chunks) {
super.process(chunks);
for (PropertyChangeEvent event : chunks) {
getPropertyChangeSupport().firePropertyChange(event);
}
}
}