diff --git a/src/main/java/engineering/swat/watch/ActiveWatch.java b/src/main/java/engineering/swat/watch/ActiveWatch.java index 36f0a4b9..4be79e54 100644 --- a/src/main/java/engineering/swat/watch/ActiveWatch.java +++ b/src/main/java/engineering/swat/watch/ActiveWatch.java @@ -27,12 +27,17 @@ 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(); } 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 d0dc6c67..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; @@ -115,4 +115,16 @@ private WatchEvent.Kind translate(java.nio.file.WatchEvent.Kind jdkKind) { throw new IllegalArgumentException("Unexpected watch kind: " + jdkKind); } + + // -- EventHandlingWatch -- + + @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..8b555191 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 event) { + processEvents(event); + } + @Override public void close() throws IOException { IOException firstFail = null; 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()); + } +}