com.swoval.files.NioPathWatcherService Maven / Gradle / Ivy
package com.swoval.files;
import static com.swoval.files.Entries.UNKNOWN;
import static com.swoval.files.PathWatchers.Event.Kind.Create;
import static com.swoval.files.PathWatchers.Event.Kind.Delete;
import static com.swoval.files.PathWatchers.Event.Kind.Modify;
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 static java.nio.file.StandardWatchEventKinds.OVERFLOW;
import com.swoval.files.PathWatchers.Event;
import com.swoval.files.PathWatchers.Overflow;
import com.swoval.functional.Consumer;
import com.swoval.functional.Either;
import com.swoval.runtime.ShutdownHooks;
import java.io.IOException;
import java.nio.file.ClosedWatchServiceException;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
class WatchedDirectoriesByPath extends LockableMap {}
class NioPathWatcherService implements AutoCloseable {
private final Thread loopThread;
private final AtomicBoolean isStopped = new AtomicBoolean(false);
private static final AtomicInteger threadId = new AtomicInteger(0);
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
private final RegisterableWatchService watchService;
private final WatchedDirectoriesByPath watchedDirectoriesByPath = new WatchedDirectoriesByPath();
private final int shutdownHookId;
NioPathWatcherService(
final Consumer> eventConsumer,
final RegisterableWatchService watchService)
throws InterruptedException {
this.watchService = watchService;
this.shutdownHookId =
ShutdownHooks.addHook(
1,
new Runnable() {
@Override
public void run() {
close();
}
});
final CountDownLatch latch = new CountDownLatch(1);
loopThread =
new Thread("NioPathWatcher-loop-thread-" + threadId.incrementAndGet()) {
@Override
public void run() {
latch.countDown();
boolean stop = false;
while (!isStopped.get() && !stop && !Thread.currentThread().isInterrupted()) {
try {
final WatchKey key = watchService.take();
final List> events = key.pollEvents();
if (!key.reset()) {
key.cancel();
}
final Iterator> it = events.iterator();
while (it.hasNext()) {
final WatchEvent> e = it.next();
final WatchEvent.Kind> k = e.kind();
if (OVERFLOW.equals(k)) {
final Either result =
Either.left(new Overflow((Path) key.watchable()));
eventConsumer.accept(result);
} else if (k != null) {
final Event.Kind kind =
k.equals(ENTRY_DELETE) ? Delete : k.equals(ENTRY_CREATE) ? Create : Modify;
final Path watchKey = (Path) key.watchable();
final Path path =
e.context() == null ? watchKey : watchKey.resolve((Path) e.context());
final Either result =
Either.right(new Event(TypedPaths.get(path, UNKNOWN), kind));
eventConsumer.accept(result);
}
}
} catch (final ClosedWatchServiceException | InterruptedException e) {
stop = true;
}
}
shutdownLatch.countDown();
}
};
loopThread.setDaemon(true);
loopThread.start();
latch.await(5, TimeUnit.SECONDS);
}
private final class CachedWatchDirectory implements WatchedDirectory {
private final Path path;
private final WatchKey key;
private final AtomicBoolean closed = new AtomicBoolean(false);
CachedWatchDirectory(final Path path) throws IOException {
this.path = path;
this.key = watchService.register(path, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
}
@Override
public void close() {
if (closed.compareAndSet(false, true)) {
watchedDirectoriesByPath.remove(path);
key.reset();
key.cancel();
}
}
@Override
public String toString() {
return "WatchedDirectory(" + path + ")";
}
}
Either register(final Path path) {
Either result;
try {
if (watchedDirectoriesByPath.lock()) {
try {
final WatchedDirectory previousWatchedDirectory = watchedDirectoriesByPath.get(path);
if (previousWatchedDirectory == null) {
final WatchedDirectory watchedDirectory = new CachedWatchDirectory(path);
watchedDirectoriesByPath.put(path, watchedDirectory);
result = Either.right(watchedDirectory);
} else {
result = Either.right(previousWatchedDirectory);
}
} finally {
watchedDirectoriesByPath.unlock();
}
} else {
result = Either.right(null);
}
} catch (final ClosedWatchServiceException e) {
result = Either.left(new IOException(e));
} catch (final IOException e) {
result = Either.left(e);
}
return result;
}
@SuppressWarnings("EmptyCatchBlock")
@Override
public void close() {
if (isStopped.compareAndSet(false, true)) {
ShutdownHooks.removeHook(shutdownHookId);
loopThread.interrupt();
try {
watchedDirectoriesByPath.clear();
watchService.close();
shutdownLatch.await(5, TimeUnit.SECONDS);
loopThread.join(5000);
} catch (final InterruptedException | IOException e) {
throw new RuntimeException(e);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy