public FileWatcher() {
      watched = new ConcurrentHashMap<>();
      thread = new Thread(this, "FileWatcher");
      Runtime.getRuntime().addShutdownHook(new Thread(this::stop));
      thread.start();
   }
@Test
   public void testFileWatcher() throws IOException, InterruptedException {
      try(FileWatcher watcher = new FileWatcher()) {
         Path tmpDir = Paths.get(CommonsTestingUtil.tmpDirectory(FileWatcherTest.class));
         Files.createDirectories(tmpDir);
         Path a = tmpDir.resolve("a");
         Files.deleteIfExists(a);
         Path b = tmpDir.resolve("b");
         Files.deleteIfExists(b);
         Files.createFile(a);
         ConcurrentMap<Path, AtomicInteger> counters = new ConcurrentHashMap<>();
         watcher.watch(a, p -> counters.computeIfAbsent(p, k -> new AtomicInteger()).incrementAndGet());
         Files.setLastModifiedTime(a, FileTime.fromMillis(System.currentTimeMillis()));
         Eventually.eventually(() -> counters.containsKey(a) && counters.get(a).get() == 1);
         Files.setLastModifiedTime(a, FileTime.fromMillis(System.currentTimeMillis()));
         Eventually.eventually(() -> counters.get(a).get() == 2);
         Files.createFile(b);
         Thread.sleep(FileWatcher.SLEEP + 500);
         assertFalse(counters.containsKey(b));
         assertEquals(2, counters.get(a).get());
         // watch "b"
         watcher.watch(b, p -> counters.computeIfAbsent(p, k -> new AtomicInteger()).incrementAndGet());
         // add a second watcher to "a"
         watcher.watch(a, p -> counters.computeIfAbsent(p, k -> new AtomicInteger()).incrementAndGet());
         Files.setLastModifiedTime(a, FileTime.fromMillis(System.currentTimeMillis()));
         Files.setLastModifiedTime(b, FileTime.fromMillis(System.currentTimeMillis()));
         Eventually.eventually(() -> counters.get(a).get() == 4);
         Eventually.eventually(() -> counters.containsKey(b) && counters.get(b).get() == 1);
      }
   }