diff --git a/README.md b/README.md
index a51c36d7..7e5c1b9d 100644
--- a/README.md
+++ b/README.md
@@ -7,13 +7,14 @@ a java file watcher that works across platforms and supports recursion, single f
Features:
- monitor a single file (or directory) for changes
-- monitor a directory for changes to it's direct descendants
-- monitor a directory for changes for all it's descendants (aka recursive directory watch)
+- monitor a directory for changes to its direct descendants
+- monitor a directory for changes for all its descendants (aka recursive directory watch)
- edge cases dealt with:
- - in case of overflow we will still generate events for new descendants
- recursive watches will also continue in new directories
- multiple watches for the same directory are merged to avoid overloading the kernel
- events are processed in a configurable worker pool
+ - when an overflow happens, automatically approximate the events that were
+ missed using a configurable approximation policy
Planned features:
@@ -39,6 +40,7 @@ Start using java-watch:
var directory = Path.of("tmp", "test-dir");
var watcherSetup = Watcher.watch(directory, WatchScope.PATH_AND_CHILDREN)
.withExecutor(Executors.newCachedThreadPool()) // optionally configure a custom thread pool
+ .onOverflow(Approximation.DIRTY) // optionally configure a handler for overflows
.on(watchEvent -> {
System.err.println(watchEvent);
});
diff --git a/src/main/java/engineering/swat/watch/OnOverflow.java b/src/main/java/engineering/swat/watch/Approximation.java
similarity index 93%
rename from src/main/java/engineering/swat/watch/OnOverflow.java
rename to src/main/java/engineering/swat/watch/Approximation.java
index da3dab03..1bf3fd6d 100644
--- a/src/main/java/engineering/swat/watch/OnOverflow.java
+++ b/src/main/java/engineering/swat/watch/Approximation.java
@@ -38,7 +38,7 @@
* overflow issue), but it doesn't have to (e.g., it may carry out additional
* overflow bookkeeping).
*/
-public enum OnOverflow {
+public enum Approximation {
/**
* Synthetic events are issued for no regular files/directories in
@@ -66,8 +66,8 @@ public enum OnOverflow {
*
*
* This approach is relatively cheap in terms of memory usage (cf.
- * {@link #DIRTY}), but it results in a large over/underapproximation of the
- * actual events (cf. DIRTY).
+ * {@link #DIFF}), but it results in a large over/underapproximation of the
+ * actual events (cf. DIFF).
*/
ALL,
@@ -76,7 +76,8 @@ public enum OnOverflow {
*
* Synthetic events of kinds {@link WatchEvent.Kind#CREATED},
* {@link WatchEvent.Kind#MODIFIED}, and {@link WatchEvent.Kind#DELETED} are
- * issued for dirty regular files/directories in the scope of the watch, as
+ * issued for regular files/directories in the scope of the watch, when
+ * their current versions are different from their previous versions, as
* determined using last-modified-times. Specifically, when an
* overflow event happens:
*
@@ -103,5 +104,5 @@ public enum OnOverflow {
* but it is relatively expensive in terms of memory usage (cf. ALL), as the
* watch needs to keep track of last-modified-times.
*/
- DIRTY
+ DIFF
}
diff --git a/src/main/java/engineering/swat/watch/Watcher.java b/src/main/java/engineering/swat/watch/Watcher.java
index d2544f98..c6a094de 100644
--- a/src/main/java/engineering/swat/watch/Watcher.java
+++ b/src/main/java/engineering/swat/watch/Watcher.java
@@ -57,7 +57,7 @@ public class Watcher {
private final Logger logger = LogManager.getLogger();
private final Path path;
private final WatchScope scope;
- private volatile OnOverflow approximateOnOverflow = OnOverflow.ALL;
+ private volatile Approximation approximateOnOverflow = Approximation.ALL;
private volatile Executor executor = CompletableFuture::runAsync;
private static final BiConsumer EMPTY_HANDLER = (w, e) -> {};
@@ -174,12 +174,12 @@ public Watcher withExecutor(Executor callbackHandler) {
* {@link WatchEvent.Kind#CREATED}, {@link WatchEvent.Kind#MODIFIED}, and/or
* {@link WatchEvent.Kind#DELETED}) should be issued when an overflow event
* happens. If not defined before this watcher is started, the
- * {@link engineering.swat.watch.OnOverflow#ALL} approach will be used.
+ * {@link Approximation#ALL} approach will be used.
* @param whichFiles Constant to indicate for which regular
* files/directories to approximate
* @return This watcher for optional method chaining
*/
- public Watcher approximate(OnOverflow whichFiles) {
+ public Watcher onOverflow(Approximation whichFiles) {
this.approximateOnOverflow = whichFiles;
return this;
}
@@ -233,7 +233,7 @@ private BiConsumer applyApproximateOnOverflow()
return eventHandler;
case ALL:
return eventHandler.andThen(new MemorylessRescanner(executor));
- case DIRTY:
+ case DIFF:
return eventHandler.andThen(new IndexingRescanner(executor, path, scope));
default:
throw new UnsupportedOperationException("No event handler has been defined yet for this overflow policy");
diff --git a/src/test/java/engineering/swat/watch/RecursiveWatchTests.java b/src/test/java/engineering/swat/watch/RecursiveWatchTests.java
index 954de151..1f931207 100644
--- a/src/test/java/engineering/swat/watch/RecursiveWatchTests.java
+++ b/src/test/java/engineering/swat/watch/RecursiveWatchTests.java
@@ -149,8 +149,8 @@ void deleteOfFileInDirectoryShouldBeVisible() throws IOException {
}
@ParameterizedTest
- @EnumSource // Repeat test for each `OnOverflow` value
- void overflowsAreRecoveredFrom(OnOverflow whichFiles) throws IOException, InterruptedException {
+ @EnumSource // Repeat test for each `Approximation` value
+ void overflowsAreRecoveredFrom(Approximation whichFiles) throws IOException, InterruptedException {
var parent = testDir.getTestDirectory();
var descendants = new Path[] {
Path.of("foo"),
@@ -180,7 +180,7 @@ void overflowsAreRecoveredFrom(OnOverflow whichFiles) throws IOException, Interr
var dropEvents = new AtomicBoolean(false); // Toggles overflow simulation
var watchConfig = Watcher.watch(parent, WatchScope.PATH_AND_ALL_DESCENDANTS)
.withExecutor(ForkJoinPool.commonPool())
- .approximate(whichFiles)
+ .onOverflow(whichFiles)
.filter(e -> !dropEvents.get())
.on(events::add);
@@ -207,7 +207,7 @@ void overflowsAreRecoveredFrom(OnOverflow whichFiles) throws IOException, Interr
var overflow = new WatchEvent(WatchEvent.Kind.OVERFLOW, parent);
watch.handleEvent(overflow);
- if (whichFiles != OnOverflow.NONE) { // Auto-handler is configured
+ if (whichFiles != Approximation.NONE) { // Auto-handler is configured
for (var descendant : descendants) {
awaitCreation.accept(descendant);
awaitCreation.accept(descendant.resolve(file1));
diff --git a/src/test/java/engineering/swat/watch/SingleDirectoryTests.java b/src/test/java/engineering/swat/watch/SingleDirectoryTests.java
index 00159970..2738fdb4 100644
--- a/src/test/java/engineering/swat/watch/SingleDirectoryTests.java
+++ b/src/test/java/engineering/swat/watch/SingleDirectoryTests.java
@@ -134,7 +134,7 @@ void memorylessRescanOnOverflow() throws IOException, InterruptedException {
var nModified = new AtomicInteger();
var nOverflow = new AtomicInteger();
var watchConfig = Watcher.watch(directory, WatchScope.PATH_AND_CHILDREN)
- .approximate(OnOverflow.ALL)
+ .onOverflow(Approximation.ALL)
.on(e -> {
switch (e.getKind()) {
case CREATED:
@@ -179,7 +179,7 @@ void indexingRescanOnOverflow() throws IOException, InterruptedException {
var nDeleted = new AtomicInteger();
var watchConfig = Watcher.watch(directory, WatchScope.PATH_AND_CHILDREN)
- .approximate(OnOverflow.DIRTY)
+ .onOverflow(Approximation.DIFF)
.on(e -> {
var kind = e.getKind();
if (kind != OVERFLOW) {
diff --git a/src/test/java/engineering/swat/watch/SingleFileTests.java b/src/test/java/engineering/swat/watch/SingleFileTests.java
index 19def650..7e2fdb7c 100644
--- a/src/test/java/engineering/swat/watch/SingleFileTests.java
+++ b/src/test/java/engineering/swat/watch/SingleFileTests.java
@@ -135,7 +135,7 @@ void singleFileThatMonitorsOnlyADirectory() throws IOException, InterruptedExcep
@Test
void noRescanOnOverflow() throws IOException, InterruptedException {
var bookkeeper = new Bookkeeper();
- try (var watch = startWatchAndTriggerOverflow(OnOverflow.NONE, bookkeeper)) {
+ try (var watch = startWatchAndTriggerOverflow(Approximation.NONE, bookkeeper)) {
Thread.sleep(TestHelper.SHORT_WAIT.toMillis());
await("Overflow shouldn't trigger created, modified, or deleted events")
@@ -148,7 +148,7 @@ void noRescanOnOverflow() throws IOException, InterruptedException {
@Test
void memorylessRescanOnOverflow() throws IOException, InterruptedException {
var bookkeeper = new Bookkeeper();
- try (var watch = startWatchAndTriggerOverflow(OnOverflow.ALL, bookkeeper)) {
+ try (var watch = startWatchAndTriggerOverflow(Approximation.ALL, bookkeeper)) {
Thread.sleep(TestHelper.SHORT_WAIT.toMillis());
var isFile = Predicate.isEqual(watch.getPath());
@@ -167,13 +167,13 @@ void memorylessRescanOnOverflow() throws IOException, InterruptedException {
}
}
- private ActiveWatch startWatchAndTriggerOverflow(OnOverflow whichFiles, Bookkeeper bookkeeper) throws IOException {
+ private ActiveWatch startWatchAndTriggerOverflow(Approximation whichFiles, Bookkeeper bookkeeper) throws IOException {
var parent = testDir.getTestDirectory();
var file = parent.resolve("a.txt");
var watch = Watcher
.watch(file, WatchScope.PATH_ONLY)
- .approximate(whichFiles)
+ .onOverflow(whichFiles)
.on(bookkeeper)
.start();
diff --git a/src/test/java/engineering/swat/watch/TortureTests.java b/src/test/java/engineering/swat/watch/TortureTests.java
index 0826aaf7..740a76f3 100644
--- a/src/test/java/engineering/swat/watch/TortureTests.java
+++ b/src/test/java/engineering/swat/watch/TortureTests.java
@@ -145,8 +145,8 @@ Set stop() throws InterruptedException {
private static final int THREADS = 4;
@ParameterizedTest
- @EnumSource(names = { "ALL", "DIRTY" })
- void pressureOnFSShouldNotMissNewFilesAnything(OnOverflow whichFiles) throws InterruptedException, IOException {
+ @EnumSource(names = { "ALL", "DIFF" })
+ void pressureOnFSShouldNotMissNewFilesAnything(Approximation whichFiles) throws InterruptedException, IOException {
final var root = testDir.getTestDirectory();
var pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 4);
@@ -155,7 +155,7 @@ void pressureOnFSShouldNotMissNewFilesAnything(OnOverflow whichFiles) throws Int
var seenCreates = ConcurrentHashMap.newKeySet();
var watchConfig = Watcher.watch(testDir.getTestDirectory(), WatchScope.PATH_AND_ALL_DESCENDANTS)
.withExecutor(pool)
- .approximate(whichFiles)
+ .onOverflow(whichFiles)
.on(ev -> {
var fullPath = ev.calculateFullPath();
switch (ev.getKind()) {
@@ -267,14 +267,14 @@ void manyRegistrationsForSamePath() throws InterruptedException, IOException {
}
}
- static Stream manyRegisterAndUnregisterSameTimeSource() {
- OnOverflow[] values = { OnOverflow.ALL, OnOverflow.DIRTY };
+ static Stream manyRegisterAndUnregisterSameTimeSource() {
+ Approximation[] values = { Approximation.ALL, Approximation.DIFF };
return TestHelper.streamOf(values, 5);
}
@ParameterizedTest
@MethodSource("manyRegisterAndUnregisterSameTimeSource")
- void manyRegisterAndUnregisterSameTime(OnOverflow whichFiles) throws InterruptedException, IOException {
+ void manyRegisterAndUnregisterSameTime(Approximation whichFiles) throws InterruptedException, IOException {
var startRegistering = new Semaphore(0);
var startedWatching = new Semaphore(0);
var stopAll = new Semaphore(0);
@@ -296,7 +296,7 @@ void manyRegisterAndUnregisterSameTime(OnOverflow whichFiles) throws Interrupted
for (int k = 0; k < 1000; k++) {
var watcher = Watcher
.watch(testDir.getTestDirectory(), WatchScope.PATH_AND_CHILDREN)
- .approximate(whichFiles)
+ .onOverflow(whichFiles)
.on(e -> {
if (e.calculateFullPath().equals(target)) {
seen.add(id);
@@ -342,10 +342,10 @@ void manyRegisterAndUnregisterSameTime(OnOverflow whichFiles) throws Interrupted
}
@ParameterizedTest
- @EnumSource(names = { "ALL", "DIRTY" })
+ @EnumSource(names = { "ALL", "DIFF" })
//Deletes can race the filesystem, so you might miss a few files in a dir, if that dir is already deleted
@EnabledIfEnvironmentVariable(named="TORTURE_DELETE", matches="true")
- void pressureOnFSShouldNotMissDeletes(OnOverflow whichFiles) throws InterruptedException, IOException {
+ void pressureOnFSShouldNotMissDeletes(Approximation whichFiles) throws InterruptedException, IOException {
final var root = testDir.getTestDirectory();
var pool = Executors.newCachedThreadPool();
@@ -361,7 +361,7 @@ void pressureOnFSShouldNotMissDeletes(OnOverflow whichFiles) throws InterruptedE
final var happened = new Semaphore(0);
var watchConfig = Watcher.watch(testDir.getTestDirectory(), WatchScope.PATH_AND_ALL_DESCENDANTS)
.withExecutor(pool)
- .approximate(whichFiles)
+ .onOverflow(whichFiles)
.on(ev -> {
events.getAndIncrement();
happened.release();
diff --git a/src/test/java/engineering/swat/watch/impl/overflows/IndexingRescannerTests.java b/src/test/java/engineering/swat/watch/impl/overflows/IndexingRescannerTests.java
index d3913881..0915d5ce 100644
--- a/src/test/java/engineering/swat/watch/impl/overflows/IndexingRescannerTests.java
+++ b/src/test/java/engineering/swat/watch/impl/overflows/IndexingRescannerTests.java
@@ -38,7 +38,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import engineering.swat.watch.OnOverflow;
+import engineering.swat.watch.Approximation;
import engineering.swat.watch.TestDirectory;
import engineering.swat.watch.TestHelper;
import engineering.swat.watch.WatchEvent;
@@ -75,7 +75,7 @@ void onlyEventsForFilesInScopeAreIssued() throws IOException, InterruptedExcepti
// children (not all descendants) of `path`
var eventsOnlyForChildren = new AtomicBoolean(true);
var watchConfig = Watcher.watch(path, WatchScope.PATH_AND_CHILDREN)
- .approximate(OnOverflow.NONE) // Disable the auto-handler here; we'll have an explicit one below
+ .onOverflow(Approximation.NONE) // Disable the auto-handler here; we'll have an explicit one below
.on(e -> {
if (e.getRelativePath().getNameCount() > 1) {
eventsOnlyForChildren.set(false);