From 0185e2d5f4889267d7009bf36c090a36b77f60ef Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Fri, 21 Feb 2025 16:54:14 +0100 Subject: [PATCH 1/5] Add method `relativize` to `ActiveWatch` interface --- .../engineering/swat/watch/ActiveWatch.java | 24 +++++++++++++++++-- .../swat/watch/impl/jdk/JDKBaseWatch.java | 7 ++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/main/java/engineering/swat/watch/ActiveWatch.java b/src/main/java/engineering/swat/watch/ActiveWatch.java index 36f0a4b9..2e36b617 100644 --- a/src/main/java/engineering/swat/watch/ActiveWatch.java +++ b/src/main/java/engineering/swat/watch/ActiveWatch.java @@ -27,12 +27,32 @@ package engineering.swat.watch; import java.io.Closeable; +import java.nio.file.Path; /** - *

Marker interface for an active watch, in the future might get properties you can inspect.

+ *

Marker interface for an active watch, in the future might get more properties you can inspect.

* - *

For now, make sure to close the watch when not interested in new events

+ *

For now, make sure to close the watch when not interested in new events.

*/ public interface ActiveWatch extends Closeable { + + /** + * Gets the path watched by this watch. + */ + Path getPath(); + + /** + * Relativizes the full path of `event` against the path watched by this + * watch (as per `getPath()`). Returns a new event whose root path and + * relative path are set in accordance with the relativization. + */ + default WatchEvent relativize(WatchEvent event) { + var fullPath = event.calculateFullPath(); + + var kind = event.getKind(); + var rootPath = getPath(); + var relativePath = rootPath.relativize(fullPath); + return new WatchEvent(kind, rootPath, relativePath); + } } diff --git a/src/main/java/engineering/swat/watch/impl/jdk/JDKBaseWatch.java b/src/main/java/engineering/swat/watch/impl/jdk/JDKBaseWatch.java index febe5917..be5ec8f4 100644 --- a/src/main/java/engineering/swat/watch/impl/jdk/JDKBaseWatch.java +++ b/src/main/java/engineering/swat/watch/impl/jdk/JDKBaseWatch.java @@ -118,4 +118,11 @@ private WatchEvent.Kind translate(java.nio.file.WatchEvent.Kind jdkKind) { throw new IllegalArgumentException("Unexpected watch kind: " + jdkKind); } + + // -- ActiveWatch -- + + @Override + public Path getPath() { + return path; + } } From a78409fcb9d020d56f699de22de02af76eef32c4 Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Fri, 21 Feb 2025 17:06:11 +0100 Subject: [PATCH 2/5] Add method `handleEvent` to `ActiveWatch` interface --- src/main/java/engineering/swat/watch/ActiveWatch.java | 7 +++++++ .../java/engineering/swat/watch/impl/jdk/JDKBaseWatch.java | 5 +++++ .../engineering/swat/watch/impl/jdk/JDKDirectoryWatch.java | 2 +- .../java/engineering/swat/watch/impl/jdk/JDKFileWatch.java | 5 +++++ .../swat/watch/impl/jdk/JDKRecursiveDirectoryWatch.java | 6 +++++- 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/java/engineering/swat/watch/ActiveWatch.java b/src/main/java/engineering/swat/watch/ActiveWatch.java index 2e36b617..1aefae17 100644 --- a/src/main/java/engineering/swat/watch/ActiveWatch.java +++ b/src/main/java/engineering/swat/watch/ActiveWatch.java @@ -36,6 +36,13 @@ */ public interface ActiveWatch extends Closeable { + /** + * Handles `event`. The purpose of this method is to trigger the event + * handler of this watch "from the outside" (in addition to having native + * file system libraries trigger the event handler "from the inside"). This + * is useful to report synthetic events (e.g., while handling overflows). + */ + void handleEvent(WatchEvent event); /** * Gets the path watched by this watch. diff --git a/src/main/java/engineering/swat/watch/impl/jdk/JDKBaseWatch.java b/src/main/java/engineering/swat/watch/impl/jdk/JDKBaseWatch.java index be5ec8f4..fd5cfc32 100644 --- a/src/main/java/engineering/swat/watch/impl/jdk/JDKBaseWatch.java +++ b/src/main/java/engineering/swat/watch/impl/jdk/JDKBaseWatch.java @@ -121,6 +121,11 @@ private WatchEvent.Kind translate(java.nio.file.WatchEvent.Kind jdkKind) { // -- ActiveWatch -- + @Override + public void handleEvent(WatchEvent e) { + eventHandler.accept(e); + } + @Override public Path getPath() { return path; diff --git a/src/main/java/engineering/swat/watch/impl/jdk/JDKDirectoryWatch.java b/src/main/java/engineering/swat/watch/impl/jdk/JDKDirectoryWatch.java index 82a8d097..79cd65c2 100644 --- a/src/main/java/engineering/swat/watch/impl/jdk/JDKDirectoryWatch.java +++ b/src/main/java/engineering/swat/watch/impl/jdk/JDKDirectoryWatch.java @@ -62,7 +62,7 @@ private void handleJDKEvents(List> events) { exec.execute(() -> { for (var ev : events) { try { - eventHandler.accept(translate(ev)); + handleEvent(translate(ev)); } catch (Throwable ignored) { logger.error("Ignoring downstream exception:", ignored); diff --git a/src/main/java/engineering/swat/watch/impl/jdk/JDKFileWatch.java b/src/main/java/engineering/swat/watch/impl/jdk/JDKFileWatch.java index ae572d00..c74e99cb 100644 --- a/src/main/java/engineering/swat/watch/impl/jdk/JDKFileWatch.java +++ b/src/main/java/engineering/swat/watch/impl/jdk/JDKFileWatch.java @@ -72,6 +72,11 @@ private static Path requireNonNull(@Nullable Path p, String message) { // -- JDKBaseWatch -- + @Override + public void handleEvent(WatchEvent event) { + internal.handleEvent(event); + } + @Override public synchronized void close() throws IOException { internal.close(); diff --git a/src/main/java/engineering/swat/watch/impl/jdk/JDKRecursiveDirectoryWatch.java b/src/main/java/engineering/swat/watch/impl/jdk/JDKRecursiveDirectoryWatch.java index ec8b16b7..2ea15483 100644 --- a/src/main/java/engineering/swat/watch/impl/jdk/JDKRecursiveDirectoryWatch.java +++ b/src/main/java/engineering/swat/watch/impl/jdk/JDKRecursiveDirectoryWatch.java @@ -270,7 +270,6 @@ private List registerForNewDirectory(Path dir) { } } - private List syncAfterOverflow(Path dir) { var events = new ArrayList(); var seenFiles = new HashSet(); @@ -299,6 +298,11 @@ private void detectedMissingEntries(Path dir, ArrayList events, Hash // -- JDKBaseWatch -- + @Override + public void handleEvent(WatchEvent ev) { + processEvents(ev); + } + @Override public void close() throws IOException { IOException firstFail = null; From 2fbabb89b12c872a339b52ed93988f1e6e8db99d Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Mon, 24 Feb 2025 11:30:07 +0100 Subject: [PATCH 3/5] Rename local variable for consistency --- .../swat/watch/impl/jdk/JDKRecursiveDirectoryWatch.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/engineering/swat/watch/impl/jdk/JDKRecursiveDirectoryWatch.java b/src/main/java/engineering/swat/watch/impl/jdk/JDKRecursiveDirectoryWatch.java index 2ea15483..8b555191 100644 --- a/src/main/java/engineering/swat/watch/impl/jdk/JDKRecursiveDirectoryWatch.java +++ b/src/main/java/engineering/swat/watch/impl/jdk/JDKRecursiveDirectoryWatch.java @@ -299,8 +299,8 @@ private void detectedMissingEntries(Path dir, ArrayList events, Hash // -- JDKBaseWatch -- @Override - public void handleEvent(WatchEvent ev) { - processEvents(ev); + public void handleEvent(WatchEvent event) { + processEvents(event); } @Override From 340a39b26e75d972b2d23957da643792d370391a Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Mon, 24 Feb 2025 11:50:35 +0100 Subject: [PATCH 4/5] Extract internal API out of `ActiveWatch` into new `impl.EventHandlingWatch` --- .../engineering/swat/watch/ActiveWatch.java | 22 -------- .../swat/watch/impl/EventHandlingWatch.java | 55 +++++++++++++++++++ .../swat/watch/impl/jdk/JDKBaseWatch.java | 6 +- 3 files changed, 58 insertions(+), 25 deletions(-) create mode 100644 src/main/java/engineering/swat/watch/impl/EventHandlingWatch.java diff --git a/src/main/java/engineering/swat/watch/ActiveWatch.java b/src/main/java/engineering/swat/watch/ActiveWatch.java index 1aefae17..4be79e54 100644 --- a/src/main/java/engineering/swat/watch/ActiveWatch.java +++ b/src/main/java/engineering/swat/watch/ActiveWatch.java @@ -36,30 +36,8 @@ */ public interface ActiveWatch extends Closeable { - /** - * Handles `event`. The purpose of this method is to trigger the event - * handler of this watch "from the outside" (in addition to having native - * file system libraries trigger the event handler "from the inside"). This - * is useful to report synthetic events (e.g., while handling overflows). - */ - void handleEvent(WatchEvent event); - /** * Gets the path watched by this watch. */ Path getPath(); - - /** - * Relativizes the full path of `event` against the path watched by this - * watch (as per `getPath()`). Returns a new event whose root path and - * relative path are set in accordance with the relativization. - */ - default WatchEvent relativize(WatchEvent event) { - var fullPath = event.calculateFullPath(); - - var kind = event.getKind(); - var rootPath = getPath(); - var relativePath = rootPath.relativize(fullPath); - return new WatchEvent(kind, rootPath, relativePath); - } } diff --git a/src/main/java/engineering/swat/watch/impl/EventHandlingWatch.java b/src/main/java/engineering/swat/watch/impl/EventHandlingWatch.java new file mode 100644 index 00000000..525a09eb --- /dev/null +++ b/src/main/java/engineering/swat/watch/impl/EventHandlingWatch.java @@ -0,0 +1,55 @@ +/* + * 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.impl; + +import engineering.swat.watch.ActiveWatch; +import engineering.swat.watch.WatchEvent; + +public interface EventHandlingWatch extends ActiveWatch { + + /** + * Handles `event`. The purpose of this method is to trigger the event + * handler of this watch "from the outside" (in addition to having native + * file system libraries trigger the event handler "from the inside"). This + * is useful to report synthetic events (e.g., while handling overflows). + */ + void handleEvent(WatchEvent event); + + /** + * Relativizes the full path of `event` against the path watched by this + * watch (as per `getPath()`). Returns a new event whose root path and + * relative path are set in accordance with the relativization. + */ + default WatchEvent relativize(WatchEvent event) { + var fullPath = event.calculateFullPath(); + + var kind = event.getKind(); + var rootPath = getPath(); + var relativePath = rootPath.relativize(fullPath); + return new WatchEvent(kind, rootPath, relativePath); + } +} diff --git a/src/main/java/engineering/swat/watch/impl/jdk/JDKBaseWatch.java b/src/main/java/engineering/swat/watch/impl/jdk/JDKBaseWatch.java index f48ac527..292499be 100644 --- a/src/main/java/engineering/swat/watch/impl/jdk/JDKBaseWatch.java +++ b/src/main/java/engineering/swat/watch/impl/jdk/JDKBaseWatch.java @@ -37,10 +37,10 @@ import org.apache.logging.log4j.Logger; import org.checkerframework.checker.nullness.qual.Nullable; -import engineering.swat.watch.ActiveWatch; import engineering.swat.watch.WatchEvent; +import engineering.swat.watch.impl.EventHandlingWatch; -public abstract class JDKBaseWatch implements ActiveWatch { +public abstract class JDKBaseWatch implements EventHandlingWatch { private final Logger logger = LogManager.getLogger(); protected final Path path; @@ -116,7 +116,7 @@ private WatchEvent.Kind translate(java.nio.file.WatchEvent.Kind jdkKind) { throw new IllegalArgumentException("Unexpected watch kind: " + jdkKind); } - // -- ActiveWatch -- + // -- EventHandlingWatch -- @Override public void handleEvent(WatchEvent e) { From 9807fdb4e2b91612e65ca80bc4c6d2861361b20c Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Mon, 24 Feb 2025 11:50:46 +0100 Subject: [PATCH 5/5] Add test for `relativize` --- .../watch/impl/EventHandlingWatchTests.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/test/java/engineering/swat/watch/impl/EventHandlingWatchTests.java diff --git a/src/test/java/engineering/swat/watch/impl/EventHandlingWatchTests.java b/src/test/java/engineering/swat/watch/impl/EventHandlingWatchTests.java new file mode 100644 index 00000000..0eeac52b --- /dev/null +++ b/src/test/java/engineering/swat/watch/impl/EventHandlingWatchTests.java @@ -0,0 +1,67 @@ +/* + * 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.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; + +import engineering.swat.watch.WatchEvent; + +class EventHandlingWatchTests { + + private static EventHandlingWatch emptyWatch(Path path) { + return new EventHandlingWatch() { + @Override + public void handleEvent(WatchEvent event) { + // Nothing to handle + } + + @Override + public void close() throws IOException { + // Nothing to close + } + + @Override + public Path getPath() { + return path; + } + }; + } + + @Test + void relativizeTest() { + var e1 = new WatchEvent(WatchEvent.Kind.OVERFLOW, Path.of("foo"), Path.of("bar", "baz.txt")); + var e2 = new WatchEvent(WatchEvent.Kind.OVERFLOW, Path.of("foo", "bar", "baz.txt")); + var e3 = emptyWatch(Path.of("foo")).relativize(e2); + assertEquals(e1.getRootPath(), e3.getRootPath()); + assertEquals(e1.getRelativePath(), e3.getRelativePath()); + } +}