Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
136 commits
Select commit Hold shift + click to select a range
c1d3622
Simplify `JDKFileWatch` by moving the creation (but not start) of its…
sungshik Feb 18, 2025
27cad30
Update workflow trigger for target branch `improved-overflow-support-…
sungshik Feb 21, 2025
9fe5be0
Simplify class `WatchEvent` by adding a 2-arg constructor (instead of…
sungshik Feb 21, 2025
f559c1c
Rename method to more clearly convey what it does
sungshik Feb 21, 2025
a00cc9a
Reduce diff of `JDKFileWatch` simplifications
sungshik Feb 21, 2025
bead33e
Reduce diff of `JDKFileWatch` simplifications
sungshik Feb 21, 2025
6b92693
Fix Checker Framework issue in `JDKFileWatch`
sungshik Feb 21, 2025
eab398f
Fix Checker Framework issue in `JDKBaseWatch`
sungshik Feb 21, 2025
ff91702
Remove unused fields in `JDKFileWatch`
sungshik Feb 21, 2025
0185e2d
Add method `relativize` to `ActiveWatch` interface
sungshik Feb 21, 2025
a78409f
Add method `handleEvent` to `ActiveWatch` interface
sungshik Feb 21, 2025
9607285
Add `ActiveWatch` as event handler arg to `JDK...Watch` classes
sungshik Feb 21, 2025
4fb69dc
Add `ActiveWatch` as event handler arg to `Watcher`
sungshik Feb 21, 2025
c365b4a
Move null preprocessing of relative path back to the constructor of `…
sungshik Feb 24, 2025
61c82c1
Merge branch 'improved-overflow-support/scaffolding' into improved-ov…
sungshik Feb 24, 2025
53f3038
Merge pull request #18 from SWAT-engineering/improved-overflow-suppor…
sungshik Feb 24, 2025
6cd7a59
Merge branch 'improved-overflow-support-main' into improved-overflow-…
sungshik Feb 24, 2025
2fbabb8
Rename local variable for consistency
sungshik Feb 24, 2025
340a39b
Extract internal API out of `ActiveWatch` into new `impl.EventHandlin…
sungshik Feb 24, 2025
9807fdb
Add test for `relativize`
sungshik Feb 24, 2025
8a43b87
Merge branch 'improved-overflow-support/richer-active-watch' into imp…
sungshik Feb 24, 2025
9ef704f
Use `EventHandlingWatch` instead of `ActiveWatch` internally
sungshik Feb 24, 2025
8d4dd06
Merge pull request #19 from SWAT-engineering/improved-overflow-suppor…
sungshik Feb 24, 2025
d52cfeb
Merge branch 'improved-overflow-support-main' into improved-overflow-…
sungshik Feb 24, 2025
a712a3a
Merge pull request #21 from SWAT-engineering/improved-overflow-suppor…
sungshik Feb 24, 2025
04bfe11
Add `getScope` method to `ActiveWatch` interface
sungshik Feb 26, 2025
bcfa0dd
Add `getScope` method to `ActiveWatch` interface
sungshik Feb 26, 2025
c8ed7a8
Add `OverflowPolicy` enum to configure how overflow events should be …
sungshik Feb 26, 2025
89e1b5f
Add overflow policy to watcher constructor
sungshik Feb 26, 2025
2ff52a7
Add license
sungshik Feb 26, 2025
15f8313
Add implementation of the `MEMORYLESS_RESCANS` overflow policy
sungshik Feb 26, 2025
bab08c0
Add auto-handling using the `MEMORYLESS_RESCANS` overflow policy to (…
sungshik Feb 26, 2025
a28ac94
Add function to ignore overflow events in the user-defined event hand…
sungshik Feb 26, 2025
8d59be4
Fix comment
sungshik Feb 26, 2025
df81ecb
Improve test
sungshik Feb 26, 2025
d77f7b5
Add support for propagation of overflow events from a parent dir to a…
sungshik Feb 26, 2025
5d6e32f
Move configuration of overflow policy to separate method
sungshik Feb 26, 2025
ac2e212
Remove auto-gobbling of overflow events for all overflow policies
sungshik Feb 26, 2025
ec79d61
Update test
sungshik Feb 26, 2025
96f4f5b
Use `walkFileTree` instead of `walk` to avoid redundant retrieval of …
sungshik Feb 28, 2025
67ca923
Update test
sungshik Feb 28, 2025
9744874
Fix test
sungshik Feb 28, 2025
4beb950
Fix issue that `MODIFIED` events were issued for directories when aut…
sungshik Feb 28, 2025
f7a7a34
Rename overflow auto-handling approach constants
sungshik Mar 5, 2025
b874834
Merge branch 'improved-overflow-support/first-overflow-policy' into i…
sungshik Mar 5, 2025
1f7e366
Update test after merge
sungshik Mar 5, 2025
9437eec
Add `IndexingRescanner`
sungshik Mar 5, 2025
b85b7bb
Add test for `IndexingRescanner`
sungshik Mar 5, 2025
8ffde3b
Fix typo
sungshik Mar 5, 2025
9e71ad7
Switch order of overflow auto-handler and user-defined event handler
sungshik Mar 7, 2025
546fbe8
Merge pull request #24 from SWAT-engineering/improved-overflow-suppor…
sungshik Mar 7, 2025
698767e
Merge branch 'improved-overflow-support-main' into improved-overflow-…
sungshik Mar 7, 2025
fd70359
Switch order of overflow auto-handler and user-defined event handler
sungshik Mar 7, 2025
b673d2a
Merge branch 'improved-overflow-support/overflow-policies-for-file-wa…
sungshik Mar 7, 2025
767972d
Improve test code
sungshik Mar 7, 2025
c92d17f
Remove debug println
sungshik Mar 7, 2025
a826e44
Improve comments
sungshik Mar 7, 2025
d832746
Improve comments
sungshik Mar 7, 2025
2aa7db2
Merge pull request #25 from SWAT-engineering/improved-overflow-suppor…
sungshik Mar 7, 2025
696c3a0
Merge branch 'improved-overflow-support-main' into improved-overflow-…
sungshik Mar 7, 2025
d5192e7
Improve comments and rename method
sungshik Mar 7, 2025
eb13d91
Extract a base file visitor from the visitor in `MemorylessRescanner`
sungshik Mar 7, 2025
285d92a
Integrate the usage of the base file visitor in `IndexingRescanner`
sungshik Mar 7, 2025
3581cb6
Simplify generation of DELETED events
sungshik Mar 7, 2025
5b94225
Extend `indexingRescanOnOverflow` test
sungshik Mar 7, 2025
94c3aee
Increase sleep time in test
sungshik Mar 7, 2025
d51d88f
Update torture test to ignore overflows (they're auto-handled)
sungshik Mar 10, 2025
671d5eb
Add `JDKFileTreeWatch`
sungshik Mar 10, 2025
62f4b1c
Move `updateChildWatches` to inner class
sungshik Mar 10, 2025
25d5eb7
Add comment to explain why `events` is thread-safe
sungshik Mar 10, 2025
b19cdaf
Improve event handler of `IndexingRescanner` (avoid double map lookup)
sungshik Mar 10, 2025
924492d
Fix test: don't rely on exact numbers of events
sungshik Mar 10, 2025
493165d
Fix `indexingRescanOnOverflow` test
sungshik Mar 11, 2025
d569c50
Merge pull request #26 from SWAT-engineering/improved-overflow-suppor…
sungshik Mar 11, 2025
36c4299
Merge branch 'improved-overflow-support-main' into improved-overflow-…
sungshik Mar 11, 2025
47b8a10
Use `computeIfAbsent` instead of `putIfAbsent`
sungshik Mar 11, 2025
500e238
Update `JDKRecursiveDirectoryWatch`
sungshik Mar 11, 2025
6a328bf
Add import
sungshik Mar 11, 2025
4aed091
Improve code quality of `JDKFileTreeWatch`
sungshik Mar 12, 2025
71ac833
Improve code quality of `JDKFileTreeWatch`
sungshik Mar 12, 2025
fa65b30
Add mechanism to avoid relativization in `JDKFileTreeWatch`
sungshik Mar 12, 2025
fdd24f8
Simplify relativization of paths in `JDKFileTreeWatch`
sungshik Mar 14, 2025
3bdafe6
Change order of closing internal/child watches in `JDKFileTreeWatch`
sungshik Mar 14, 2025
4582d23
Simplify relativization of paths in `JDKFileTreeWatch`
sungshik Mar 14, 2025
6a7df86
Use file names to store child watches (instead of full paths)
sungshik Mar 14, 2025
e676b5f
Use `JDKFileTreeWatch`
sungshik Mar 14, 2025
387e7c3
Add asynchronous bookkeeping of `CREATED` and `OVERFLOW` events
sungshik Mar 14, 2025
748e8ac
Fix issue that `JDKFileTreeWatch` relied on overflow handling to pres…
sungshik Mar 18, 2025
4a1423b
Add license
sungshik Mar 18, 2025
1ab8f29
Make the child watches updater asynchronous
sungshik Mar 18, 2025
385db76
Add code to close child watches when their directories no longer exis…
sungshik Mar 18, 2025
58d9561
Remove `JDKRecursiveDirectoryWatch` (replaced by `JDKFileTreeWatch`)
sungshik Mar 25, 2025
c96c943
Add filtering mechanism to `Watcher` and `JDK...` classes
sungshik Mar 25, 2025
eca305b
Move method implementation from base class to subclass (was already o…
sungshik Mar 25, 2025
408c9d7
Improve logic to close `JDK...Watch` classes (avoid event handling o…
sungshik Mar 25, 2025
e0f039d
Fix a few relativization issues in `JDKFileTreeWatch` and `IndexingRe…
sungshik Mar 25, 2025
b8adb45
Add event filter to test
sungshik Mar 25, 2025
bbd1d39
Add test to check if overflows are recoverd from
sungshik Mar 25, 2025
02b10b7
Fix JavaDoc
sungshik Mar 25, 2025
76ec380
Remove old test
sungshik Mar 25, 2025
2cc3c66
Remove `trySleep` helpers
sungshik Mar 26, 2025
b760db9
Rename method to better convey intent
sungshik Mar 26, 2025
9b58bc4
Revert change to `relativize` in `JDKFileTreeWatch` (and add comment …
sungshik Mar 26, 2025
84b627b
Move closed check to `handleEvent`
sungshik Mar 26, 2025
e53569a
Add general `handleEvent` implementation back to the base watch
sungshik Mar 26, 2025
6feac60
Fix race in closing child watches
sungshik Mar 26, 2025
f81df21
Add test to expose bug that `IndexingRescanner` generates events outs…
sungshik Mar 26, 2025
5f7329d
Fix bug that `IndexingRescanner` generates events outside the watch s…
sungshik Mar 26, 2025
ecc8ee6
Remove redundant initialization statement
sungshik Mar 26, 2025
214d0ac
Make a few minor improvements to improve code quality, comments, and …
sungshik Mar 26, 2025
5893890
Fix initialization of `IndexingRescanner.Generator`
sungshik Mar 26, 2025
98225e3
Fix initialization of `IndexingRescanner.Generator`
sungshik Mar 26, 2025
79312db
Make field final
sungshik Mar 26, 2025
9b0ba39
Merge pull request #28 from SWAT-engineering/improved-overflow-suppor…
sungshik Mar 26, 2025
3f80e77
Merge branch 'improved-overflow-support-main' into improved-overflow-…
sungshik Mar 26, 2025
fd68764
Merge pull request #27 from SWAT-engineering/improved-overflow-suppor…
sungshik Mar 28, 2025
f5bb9a3
Fix issue in `IndexingRescanner` that `DELETED` events were sometimes…
sungshik Mar 28, 2025
0979b15
Add parameterization to torture tests
sungshik Mar 28, 2025
634aad3
Add extra check to let `IndexingRescanner` report failures to get las…
sungshik Mar 28, 2025
4cad3cb
Replace 1-level index with 2-level index in `IndexingRescanner` to im…
sungshik Mar 31, 2025
9eea3e0
Fix Checker Framework error
sungshik Mar 31, 2025
f84f842
Remove parameterization from test that has a non-recursive watch
sungshik Mar 31, 2025
ba4b7dc
Merge branch 'improved-overflow-support/parameterized-torture-tests' …
sungshik Mar 31, 2025
532ff11
Make the index generic and thread-safe
sungshik Mar 31, 2025
5209921
Fix Checker Framework errors
sungshik Apr 1, 2025
3b9efdd
Improve comments about thread-safety of concurrent puts/removes in th…
sungshik Apr 1, 2025
86d7f5e
Reduce diff
sungshik Apr 1, 2025
23506ae
Improve a few comments
sungshik Apr 1, 2025
61ebbb8
Merge pull request #29 from SWAT-engineering/improved-overflow-suppor…
DavyLandman Apr 3, 2025
7b26f57
Update README to mention auto-handlers for overflows
sungshik Apr 7, 2025
12d3154
Clarify wording about overflows in README
sungshik Apr 7, 2025
6a2ff16
Rename OnOverflow.java to Approximation.java
sungshik Apr 7, 2025
28420e0
Rename `OnOverflow` to `Approximation`
sungshik Apr 7, 2025
58472c5
Rename `DIRTY` to `DIFF`
sungshik Apr 7, 2025
6f436a8
Merge pull request #30 from SWAT-engineering/improved-overflow-suppor…
DavyLandman Apr 7, 2025
3afc76f
Rename `DIRTY` to `DIFF` in README
sungshik Apr 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
pull_request:
branches:
- main
- improved-overflow-support-main

jobs:
test:
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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.DIFF) // optionally configure a handler for overflows
.on(watchEvent -> {
System.err.println(watchEvent);
});
Expand Down
14 changes: 12 additions & 2 deletions src/main/java/engineering/swat/watch/ActiveWatch.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,22 @@
package engineering.swat.watch;

import java.io.Closeable;
import java.nio.file.Path;

/**
* <p>Marker interface for an active watch, in the future might get properties you can inspect.</p>
* <p>Marker interface for an active watch, in the future might get more properties you can inspect.</p>
*
* <p>For now, make sure to close the watch when not interested in new events</p>
* <p>For now, make sure to close the watch when not interested in new events.</p>
*/
public interface ActiveWatch extends Closeable {

/**
* Gets the path watched by this watch.
*/
Path getPath();

/**
* Gets the scope of this watch.
*/
WatchScope getScope();
}
108 changes: 108 additions & 0 deletions src/main/java/engineering/swat/watch/Approximation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* BSD 2-Clause License
*
* Copyright (c) 2023, Swat.engineering
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package engineering.swat.watch;

/**
* Constants to indicate for which regular files/directories in the scope of the
* watch an <i>approximation</i> of synthetic events (of kinds
* {@link WatchEvent.Kind#CREATED}, {@link WatchEvent.Kind#MODIFIED}, and/or
* {@link WatchEvent.Kind#DELETED}) should be issued when an overflow event
* happens. These synthetic events, as well as the overflow event itself, are
* subsequently passed to the user-defined event handler of the watch.
* Typically, the user-defined event handler can ignore the original overflow
* event (i.e., handling the synthetic events is sufficient to address the
* overflow issue), but it doesn't have to (e.g., it may carry out additional
* overflow bookkeeping).
*/
public enum Approximation {

/**
* Synthetic events are issued for <b>no regular files/directories</b> in
* the scope of the watch. Thus, the user-defined event handler is fully
* responsible to handle overflow events.
*/
NONE,

/**
* <p>
* Synthetic events of kinds {@link WatchEvent.Kind#CREATED} and
* {@link WatchEvent.Kind#MODIFIED}, but not
* {@link WatchEvent.Kind#DELETED}, are issued for all regular
* files/directories in the scope of the watch. Specifically, when an
* overflow event happens:
*
* <ul>
* <li>CREATED events are issued for all regular files/directories
* (overapproximation).
* <li>MODIFIED events are issued for all non-empty, regular files
* (overapproximation) but for no directories (underapproximation).
* <li>DELETED events are issued for no regular files/directories
* (underapproximation).
* </ul>
*
* <p>
* This approach is relatively cheap in terms of memory usage (cf.
* {@link #DIFF}), but it results in a large over/underapproximation of the
* actual events (cf. DIFF).
*/
ALL,


/**
* <p>
* Synthetic events of kinds {@link WatchEvent.Kind#CREATED},
* {@link WatchEvent.Kind#MODIFIED}, and {@link WatchEvent.Kind#DELETED} are
* issued for regular files/directories in the scope of the watch, when
* their current versions are different from their previous versions, as
* determined using <i>last-modified-times</i>. Specifically, when an
* overflow event happens:
*
* <ul>
* <li>CREATED events are issued for all regular files/directories when the
* previous last-modified-time is unknown, but the current
* last-modified-time is known (i.e., the file started existing).
* <li>MODIFIED events are issued for all regular files/directories when the
* previous last-modified-time is before the current last-modified-time.
* <li>DELETED events are issued for all regular files/directories when the
* previous last-modified-time is known, but the current
* last-modified-time is unknown (i.e., the file stopped existing).
* </ul>
*
* <p>
* To keep track of last-modified-times, an internal <i>index</i> is
* populated with last-modified-times of all regular files/directories in
* the scope of the watch when the watch is started. Each time when any
* event happens, the index is updated accordingly, so when an overflow
* event happens, last-modified-times can be compared as described above.
*
* <p>
* This approach results in a small overapproximation (cf. {@link #ALL}),
* but it is relatively expensive in terms of memory usage (cf. ALL), as the
* watch needs to keep track of last-modified-times.
*/
DIFF
}
22 changes: 21 additions & 1 deletion src/main/java/engineering/swat/watch/WatchEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,16 @@ public enum Kind {
private final Path rootPath;
private final Path relativePath;

private static final Path EMPTY_PATH = Path.of("");

public WatchEvent(Kind kind, Path rootPath) {
this(kind, rootPath, null);
}

public WatchEvent(Kind kind, Path rootPath, @Nullable Path relativePath) {
this.kind = kind;
this.rootPath = rootPath;
this.relativePath = relativePath == null ? Path.of("") : relativePath;
this.relativePath = relativePath == null ? EMPTY_PATH : relativePath;
}

public Kind getKind() {
Expand Down Expand Up @@ -101,6 +107,20 @@ public Path calculateFullPath() {
return rootPath.resolve(relativePath);
}

/**
* @return The file name of the full path of this event, or {@code null} if
* it has zero elements (cf. {@link Path#getFileName()}), but without
* calculating the full path. This method is equivalent to, but more
* efficient than, {@code calculateFullPath().getFileName()}.
*/
public @Nullable Path getFileName() {
var fileName = relativePath.getFileName();
if (fileName == null || fileName.equals(EMPTY_PATH)) {
fileName = rootPath.getFileName();
}
return fileName;
}

@Override
public String toString() {
return String.format("WatchEvent[%s, %s, %s]", this.rootPath, this.kind, this.relativePath);
Expand Down
83 changes: 68 additions & 15 deletions src/main/java/engineering/swat/watch/Watcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,19 @@
import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import engineering.swat.watch.impl.EventHandlingWatch;
import engineering.swat.watch.impl.jdk.JDKDirectoryWatch;
import engineering.swat.watch.impl.jdk.JDKFileTreeWatch;
import engineering.swat.watch.impl.jdk.JDKFileWatch;
import engineering.swat.watch.impl.jdk.JDKRecursiveDirectoryWatch;
import engineering.swat.watch.impl.overflows.IndexingRescanner;
import engineering.swat.watch.impl.overflows.MemorylessRescanner;

/**
* <p>Watch a path for changes.</p>
Expand All @@ -50,17 +55,19 @@
*/
public class Watcher {
private final Logger logger = LogManager.getLogger();
private final WatchScope scope;
private final Path path;
private final WatchScope scope;
private volatile Approximation approximateOnOverflow = Approximation.ALL;
private volatile Executor executor = CompletableFuture::runAsync;

private static final Consumer<WatchEvent> EMPTY_HANDLER = p -> {};
private volatile Consumer<WatchEvent> eventHandler = EMPTY_HANDLER;

private static final BiConsumer<EventHandlingWatch, WatchEvent> EMPTY_HANDLER = (w, e) -> {};
private volatile BiConsumer<EventHandlingWatch, WatchEvent> eventHandler = EMPTY_HANDLER;
private static final Predicate<WatchEvent> TRUE_FILTER = e -> true;
private volatile Predicate<WatchEvent> eventFilter = TRUE_FILTER;

private Watcher(WatchScope scope, Path path) {
this.scope = scope;
private Watcher(Path path, WatchScope scope) {
this.path = path;
this.scope = scope;
}

/**
Expand All @@ -87,9 +94,8 @@
break;
default:
throw new IllegalArgumentException("Unsupported scope: " + scope);

}
return new Watcher(scope, path);
return new Watcher(path, scope);
}

/**
Expand All @@ -103,7 +109,7 @@
if (this.eventHandler != EMPTY_HANDLER) {
throw new IllegalArgumentException("on handler cannot be set more than once");
}
this.eventHandler = eventHandler;
this.eventHandler = (w, e) -> eventHandler.accept(e);
return this;
}

Expand All @@ -114,7 +120,7 @@
if (this.eventHandler != EMPTY_HANDLER) {
throw new IllegalArgumentException("on handler cannot be set more than once");
}
this.eventHandler = ev -> {
this.eventHandler = (w, ev) -> {
switch (ev.getKind()) {
case CREATED:
listener.onCreated(ev);
Expand All @@ -135,6 +141,22 @@
return this;
}

/**
* Configures the event filter to determine which events should be passed to
* the event handler. By default (without calling this method), all events
* are passed. This method must be called at most once.
* @param predicate The predicate to determine an event should be kept
* ({@code true}) or dropped ({@code false})
* @return {@code this} (to support method chaining)
*/
Watcher filter(Predicate<WatchEvent> predicate) {
if (this.eventFilter != TRUE_FILTER) {
throw new IllegalArgumentException("filter cannot be set more than once");

Check warning on line 154 in src/main/java/engineering/swat/watch/Watcher.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/Watcher.java#L154

Added line #L154 was not covered by tests
}
this.eventFilter = predicate;
return this;
}

/**
* Optionally configure the executor in which the {@link #on(Consumer)} callbacks are scheduled.
* If not defined, every task will be scheduled on the {@link java.util.concurrent.ForkJoinPool#commonPool()}.
Expand All @@ -146,6 +168,22 @@
return this;
}

/**
* Optionally configure which regular files/directories in the scope of the
* watch an <i>approximation</i> of synthetic events (of kinds
* {@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 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 onOverflow(Approximation whichFiles) {
this.approximateOnOverflow = whichFiles;
return this;
}

/**
* Start watch the path for events.
* @return a subscription for the watch, when closed, new events will stop being registered to the worker pool.
Expand All @@ -157,33 +195,48 @@
throw new IllegalStateException("There is no onEvent handler defined");
}

var h = applyApproximateOnOverflow();

switch (scope) {
case PATH_AND_CHILDREN: {
var result = new JDKDirectoryWatch(path, executor, eventHandler, false);
var result = new JDKDirectoryWatch(path, executor, h, eventFilter);
result.open();
return result;
}
case PATH_AND_ALL_DESCENDANTS: {
try {
var result = new JDKDirectoryWatch(path, executor, eventHandler, true);
var result = new JDKDirectoryWatch(path, executor, h, eventFilter, true);
result.open();
return result;
} catch (Throwable ex) {
// no native support, use the simulation
logger.debug("Not possible to register the native watcher, using fallback for {}", path);
logger.trace(ex);
var result = new JDKRecursiveDirectoryWatch(path, executor, eventHandler);
var result = new JDKFileTreeWatch(path, executor, h, eventFilter);
result.open();
return result;
}
}
case PATH_ONLY: {
var result = new JDKFileWatch(path, executor, eventHandler);
var result = new JDKFileWatch(path, executor, h, eventFilter);
result.open();
return result;
}
default:
throw new IllegalStateException("Not supported yet");
}
}

private BiConsumer<EventHandlingWatch, WatchEvent> applyApproximateOnOverflow() {
switch (approximateOnOverflow) {
case NONE:
return eventHandler;
case ALL:
return eventHandler.andThen(new MemorylessRescanner(executor));
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");

Check warning on line 239 in src/main/java/engineering/swat/watch/Watcher.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/engineering/swat/watch/Watcher.java#L239

Added line #L239 was not covered by tests
}
}
}
Loading