title: WatchService and SwingWorker: how to do it correctly?
body: <p>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 </p>
  
  <p>Taking the code <a href="http://download.oracle.com/javase/tutorial/essential/io/notification.html" rel="nofollow">from WatchDir example in the tutorial</a> (simplyfied to handle a single directory only), I basically ended up</p>
  
  <ul>
  <li>extend SwingWorker</li>
  <li>do the registration stuff in the constructor</li>
  <li>put the endless loop waiting for a key in doInBackground</li>
  <li>publish each WatchEvent when retrieved via key.pollEvents()</li>
  <li><p>process the chunks by firing propertyChangeEvents with the deleted/created files as newValue</p>
  
  <pre><code>@SuppressWarnings("unchecked")
  public class FileWorker extends SwingWorker&lt;Void, WatchEvent&lt;Path&gt;&gt; {
  
      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&lt;?&gt; event : key.pollEvents()) {
                  WatchEvent.Kind&lt;?&gt; kind = event.kind();
                  // TBD - provide example of how OVERFLOW event is handled
                  if (kind == OVERFLOW) {
                      continue;
                  }
                  publish((WatchEvent&lt;Path&gt;) event);
              }
  
              // reset key return if directory no longer accessible
              boolean valid = key.reset();
              if (!valid) {
                  break;
              }
          }
          return null;
      }
  
      @Override
      protected void process(List&lt;WatchEvent&lt;Path&gt;&gt; chunks) {
          super.process(chunks);
          for (WatchEvent&lt;Path&gt; event : chunks) {
              WatchEvent.Kind&lt;?&gt; 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);
              }
          }
      }
  
  }
  </code></pre></li>
  </ul>
  
  <p>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:</p>
  
  <pre><code>    String testDir = "D:\\scans\\library";
      File directory = new File(testDir);
      final DefaultListModel&lt;File&gt; model = new DefaultListModel&lt;File&gt;();
      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);
  </code></pre>
  
  <p>Seems to work, but I feel uncomfortable </p>
  
  <ul>
  <li>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.  </li>
  <li>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 <em>directory</em> [A] the key is watching - but can pass on only one. Most probably got the distribution of logic wrong, though</li>
  </ul>
  
  <p>[A] <strong>Edited</strong> (triggered by @trashgods's comment) - it's actually not the key I have to pass around along with the event, it's the directory it's reporting the changes on. Changed the question accordingly</p>
  
  <p>FYI, this question is cross-posted to the <a href="https://forums.oracle.com/forums/thread.jspa?threadID=2298734&amp;tstart=0" rel="nofollow">OTN swing forum</a></p>
  
  <p><strong>Addendum</strong></p>
  
  <p>Reading the api doc of WatchKey:</p>
  
  <blockquote>
    <p>Where there are several threads retrieving signalled keys from a watch
    service then care should be taken to ensure that the reset method is
    only invoked after the events for the object have been processed.</p>
  </blockquote>
  
  <p>seems to imply that the events should </p>
  
  <ol>
  <li>be processed on the same thread that retrieved the WatchKey</li>
  <li>shouldn't be touched after the key is reset</li>
  </ol>
  
  <p>Not entirely sure, but combined with the (future) requirement to recursively watching directories (more than one) decided to follow @Eels advice, kind of - will soon post the code I settled on </p>
  
  <p><strong>EDIT</strong> 
  just accepted my own answer - will humbly revert that if anybody has reasonable objections  </p>