package chatty.util; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.Paths; import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; import static java.nio.file.StandardWatchEventKinds.OVERFLOW; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.util.logging.Logger; /** * Watch for modifications to a single file. * * @author tduva */ public class FileWatcher implements Runnable { private static final Logger LOGGER = Logger.getLogger(FileWatcher.class.getName()); private final WatchService watcher; private final Path file; private final Path directory; private final FileChangedListener listener; /** * Create a watcher that informs the given listener that the given file was * modified. The watcher is run in it's own thread. The watcher accumulates * events so it takes a few seconds after a modification for the listener to * be informed. * * @param file The file to watch * @param listener The FileChangedListener to be informed * @return true if the watcher was created successfully, false otherwise */ public static boolean createFileWatcher(Path file, FileChangedListener listener) { if (listener == null || file == null) { return false; } try { FileWatcher watcher = new FileWatcher(file, listener); Thread thread = new Thread(watcher); thread.start(); LOGGER.info("Added file watcher for: "+file.toAbsolutePath()); return true; } catch (IOException ex) { LOGGER.warning("Error adding file watcher for: "+file.toAbsolutePath()+" ["+ex+"]"); return false; } } private FileWatcher(Path file, FileChangedListener listener) throws IOException { directory = file.getParent(); this.listener = listener; this.file = file; watcher = FileSystems.getDefault().newWatchService(); directory.register(watcher, ENTRY_MODIFY); } @Override public void run() { for (;;) { WatchKey key; /** * Wait for a modification to occur. */ try { key = watcher.take(); } catch (InterruptedException ex) { return; } /** * Wait a bit for events to accumulate. This prevents several * consecutive modifications (e.g. several writes or change of file * modification date) from all triggering the listener. */ try { Thread.sleep(4000); } catch (InterruptedException ex) { return; } for (WatchEvent<?> event : key.pollEvents()) { WatchEvent.Kind<?> kind = event.kind(); if (kind == OVERFLOW) { continue; } WatchEvent<Path> ev = (WatchEvent<Path>)event; Path fileName = ev.context(); Path changedFile = directory.resolve(fileName); if (changedFile.equals(file)) { listener.fileChanged(); } } boolean valid = key.reset(); if (!valid) { break; } } } public interface FileChangedListener { /** * Called by the watcher when one or more modifiactions are detected. */ public void fileChanged(); } /** * Test/Example. * * @param args */ public static final void main(String[] args) { final Path file = Paths.get("test/test.txt"); createFileWatcher(file, new FileChangedListener() { @Override public void fileChanged() { System.out.println(file+" changed"); } }); } }