Skip to Main Content

Java SE (Java Platform, Standard Edition)

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

WatchService and SwingWorker: how to do it correctly?

kleopatra-JavaNetOct 16 2011 — edited Oct 21 2011
cross-posted to SOF:

http://stackoverflow.com/questions/7784909/watchservice-and-swingworker-how-to-do-it-correctly

For maximum feedback (though many regulars roam everywhere :-), here's a copy

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 (or I'm missing something obvious, a not-zero probability ;-)

Taking the code from WatchDir (simplyfied to handle a single directory only), I basically ended up

extend SwingWorker
do the registration stuff in the constructor
put the endless loop waiting for a key in doInBackground
publish each WatchEvent when retrieved via key.pollEvents()
process the chunks by firing propertyChangeEvents with the deleted/created files as newValue
@SuppressWarnings("unchecked")
public class FileWorker extends SwingWorker<Void, WatchEvent<Path>> {
    
    public static final String DELETED = "deletedFile";
    public static final String CREATED = "createdFile";
    
    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((WatchEvent<Path>) event);
            }

            // reset key return if directory no longer accessible
            boolean valid = key.reset();
            if (!valid) {
                break;
            }
        }
        return null;
    }
    
    @Override
    protected void process(List<WatchEvent<Path>> chunks) {
        super.process(chunks);
        for (WatchEvent<Path> event : chunks) {
            WatchEvent.Kind<?> kind = event.kind();
            Path name = event.context();
            Path child = directory.resolve(name);
            File file = child.toFile();
            if (StandardWatchEventKinds.ENTRY_DELETE == kind) {
                firePropertyChange(DELETED, null, file);
            } else if (StandardWatchEventKinds.ENTRY_CREATE == kind) {
                firePropertyChange(CREATED, null, file);
            }
        }
    }
    
}
The basic idea is to make using code blissfully un-aware of the slimy details: it listens to the property changes and f.i. updates arbitrary models as appropriate:
    String testDir = "D:\\scans\\library";
    File directory = new File(testDir);
    final DefaultListModel<File> model = new DefaultListModel<File>();
    for (File file : directory.listFiles()) {
        model.addElement(file);
    }
    final FileWorker worker = new FileWorker(directory);
    PropertyChangeListener l = new PropertyChangeListener() {
        
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if (FileWorker.DELETED == evt.getPropertyName()) {
                model.removeElement(evt.getNewValue());
            } else if (FileWorker.CREATED == evt.getPropertyName()) {
                model.addElement((File) evt.getNewValue());
            }
        }
    };
    worker.addPropertyChangeListener(l);
    JXList list = new JXList(model);
Seems to work, but I feel uncomfortable

Outing myself as the thread agnostic I am: all example snippets I have seen so far do block the waiting thread by using watcher.take(). Why do they do it? Would expect at least some use watcher.poll() and sleep a bit.
the SwingWorker publish method doesn't quite seem to fit: for now it's okay, as I'm watching one directory only (didn't want to galopp too far into the wrong direction :) When trying to watch several directories (as in the original WatchDir example) there are several keys and the WatchEvent relative to one of those. To resolve the path, I would need both the event and the key - but can pass on only one. Most probably got the distribution of logic wrong, though

Feedback (here or there, will take all :-) highly welcome!

Cheers
Jeanette
Comments
Locked Post
New comments cannot be posted to this locked post.
Post Details
Locked on Nov 18 2011
Added on Oct 16 2011
3 comments
819 views