WatchService で再帰的にディレクトリ監視で難あり の続き

単なるメモで-す。

import java.nio.file.Path;
import java.nio.file.WatchEvent;

public interface WatcherCallback {
  void onCall(Path path, WatchEvent<Path> event);
}
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

public class WatchKeys {

    private final WatchService watcher;
    private final Map<WatchKey, Path> watchKeys = new HashMap<>();

    public WatchKeys(Path basedir, final WatchService watcher) throws IOException {
        this.watcher = watcher;
        
        Files.walkFileTree(basedir, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                register(dir);
                return FileVisitResult.CONTINUE;
            }
        });

    }

    public void register(final Path dir) throws IOException {
        if (dir == null) return;
        WatchKey key = dir.register(this.watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        this.watchKeys.put(key, dir);
    }

    public void sweep() {
        for (Iterator<Entry<WatchKey, Path>> it = watchKeys.entrySet().iterator(); it.hasNext();) {
            Entry<WatchKey, Path> entry = it.next();
            if (Files.notExists(entry.getValue(), NOFOLLOW_LINKS)) {
                entry.getKey().cancel();
                it.remove();
            }
        }
    }

    public boolean isEmpty() {
        return watchKeys.isEmpty();
    }

    public void remove(WatchKey key) {
        watchKeys.remove(key);
    }

    public Path get(WatchKey key) {
        return watchKeys.get(key);
    }
}
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class FileChangeWatcher {
    
    private static final Logger log = Logger.getLogger(FileChangeWatcher.class.getName());

    private final WatchService watcher;
    private final WatchKeys watchKeys;
    private final List<WatcherCallback> callbacks = new ArrayList<>();

    public FileChangeWatcher(Path basedir, WatcherCallback...cb) {
        try {
            watcher = FileSystems.getDefault().newWatchService();
            watchKeys = new WatchKeys(basedir, watcher);
            callbacks.addAll(Arrays.asList(cb));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void start() { 
        try (WatchService watchService = this.watcher;) {
            while (true) {
                
                WatchKey key;
                try {
                    key = watchService.take(); // wait
                } catch (InterruptedException e) {
                    log.log(Level.WARNING, "interrupted watch service.", e);
                    return;
                }
                
                hundleEvent(key);
                
                boolean valid = key.reset();
                if (!valid) this.watchKeys.remove(key);
                watchKeys.sweep();
                if (watchKeys.isEmpty()) break;
                
            }
        } catch(IOException e) { throw new RuntimeException(e); }
    }
    
    private void hundleEvent(WatchKey key) throws IOException {
        for (WatchEvent<?> event: key.pollEvents()) {
            if (event.kind() == OVERFLOW) continue;

            WatchEvent<Path> watchEvent = cast(event);
            Path path = watchKeys.get(key).resolve(watchEvent.context());
            
            if (Files.isDirectory(path, NOFOLLOW_LINKS)) {
                if (event.kind() == ENTRY_CREATE)
                    watchKeys.register(path);
                
            } else if (Files.isRegularFile(path, NOFOLLOW_LINKS)) {
                for (WatcherCallback cb : this.callbacks) {
                    cb.onCall(path, watchEvent);
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    private static <T> WatchEvent<T> cast(WatchEvent<?> event) {
        return (WatchEvent<T>)event;
    }
}