Skip to content

Commit 7b5ad2b

Browse files
committed
Merge branch 'main' into improved-macos-support-main
2 parents cdc0b70 + ec14259 commit 7b5ad2b

File tree

17 files changed

+168
-65
lines changed

17 files changed

+168
-65
lines changed

.github/dependabot.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "maven"
4+
directory: "/"
5+
schedule:
6+
interval: "monthly"
7+
open-pull-requests-limit: 10
8+
groups:
9+
upstream-libs:
10+
patterns:
11+
- "*"
12+
exclude-patterns:
13+
- "*checkerframework*"
14+
- "*maven.plugins*"
15+
checker-framework:
16+
patterns:
17+
- "*checkerframework*"
18+
maven-plugins:
19+
patterns:
20+
- "*maven.plugins*"
21+
22+
- package-ecosystem: "github-actions"
23+
directory: "/"
24+
schedule:
25+
interval: "monthly"
26+
groups:
27+
gh-actions:
28+
patterns:
29+
- "*" # group all GH action upgrades in a single PR

.github/workflows/build.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
DELAY_FACTOR: 3
3333

3434
- name: Upload coverage to Codecov
35-
uses: codecov/codecov-action@v4
35+
uses: codecov/codecov-action@v5
3636
env:
3737
CODECOV_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN }}
3838

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# java-watch
2+
[![Maven Central Version](https://img.shields.io/maven-central/v/engineering.swat/java-watch?style=flat-square&label=maven)](https://central.sonatype.com/artifact/engineering.swat/java-watch)
3+
[![javadoc](https://javadoc.io/badge2/engineering.swat/java-watch/docs.svg?style=flat-square)](https://javadoc.io/doc/engineering.swat/java-watch)
4+
[![Codecov](https://img.shields.io/codecov/c/github/SWAT-engineering/java-watch?style=flat-square)](https://codecov.io/gh/SWAT-engineering/java-watch)
25

36
a java file watcher that works across platforms and supports recursion, single file watches, and tries to make sure no events are missed. Where possible it uses Java's NIO WatchService.
47

@@ -22,7 +25,7 @@ Planned features:
2225
- Support single file watches natively in linux (see [#11](https://github.com/SWAT-engineering/java-watch/issues/11))
2326
- Monitor only specific events (such as only CREATE events)
2427

25-
[![codecov](https://codecov.io/gh/SWAT-engineering/java-watch/graph/badge.svg?token=XL29SDYAF0)](https://codecov.io/gh/SWAT-engineering/java-watch)
28+
2629

2730
## Usage
2831

@@ -40,7 +43,7 @@ Start using java-watch:
4043

4144
```java
4245
var directory = Path.of("tmp", "test-dir");
43-
var watcherSetup = Watcher.watch(directory, WatchScope.PATH_AND_CHILDREN)
46+
var watcherSetup = Watch.build(directory, WatchScope.PATH_AND_CHILDREN)
4447
.withExecutor(Executors.newCachedThreadPool()) // optionally configure a custom thread pool
4548
.onOverflow(Approximation.DIFF) // optionally configure a handler for overflows
4649
.on(watchEvent -> {

RELEASE-NOTES.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Release notes for java-watch
2+
3+
## 0.5.0 - 2025-04-15 - first release
4+
5+
First version of java-watch. Included features:
6+
7+
- Monitor changes at:
8+
- single entry level
9+
- directory level (only direct descendants)
10+
- all descendants level
11+
- Handle `OVERFLOW` events:
12+
- Yourself
13+
- We generate events for all current entries on disk
14+
- We keep track of changes in a map, and based on modification stamp generate a most likely events
15+
- extensive tests for Linux/Windows/OSX support

pom.xml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
<groupId>engineering.swat</groupId>
3434
<artifactId>java-watch</artifactId>
35-
<version>0.5.0-RC5-SNAPSHOT</version>
35+
<version>0.5.1-SNAPSHOT</version>
3636
<packaging>jar</packaging>
3737

3838
<name>${project.groupId}:${project.artifactId}</name>
@@ -71,9 +71,9 @@
7171

7272
<properties>
7373
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
74-
<checkerframework.version>3.42.0</checkerframework.version>
75-
<junit.version>5.10.2</junit.version>
76-
<log4j.version>2.23.0</log4j.version>
74+
<checkerframework.version>3.49.2</checkerframework.version>
75+
<junit.version>5.12.2</junit.version>
76+
<log4j.version>2.24.3</log4j.version>
7777
<maven.compiler.source>11</maven.compiler.source>
7878
<maven.compiler.target>11</maven.compiler.target>
7979
</properties>
@@ -204,7 +204,7 @@
204204
<dependency>
205205
<groupId>org.awaitility</groupId>
206206
<artifactId>awaitility</artifactId>
207-
<version>4.2.2</version>
207+
<version>4.3.0</version>
208208
<scope>test</scope>
209209
</dependency>
210210
<dependency>
@@ -221,6 +221,7 @@
221221
<groupId>org.apache.logging.log4j</groupId>
222222
<artifactId>log4j-core</artifactId>
223223
<version>${log4j.version}</version>
224+
<scope>test</scope>
224225
</dependency>
225226
</dependencies>
226227

@@ -236,6 +237,7 @@
236237
<extensions>true</extensions>
237238
<configuration>
238239
<publishingServerId>central</publishingServerId>
240+
<autoPublish>true</autoPublish>
239241
</configuration>
240242
</plugin>
241243
<plugin> <!-- sign jar for maven central-->

src/main/java/engineering/swat/watch/Watcher.java renamed to src/main/java/engineering/swat/watch/Watch.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
* <p>It will avoid common errors using the raw apis, and will try to use the most native api where possible.</p>
5454
* Note, there are differences per platform that cannot be avoided, please review the readme of the library.
5555
*/
56-
public class Watcher {
56+
public class Watch {
5757
private final Logger logger = LogManager.getLogger();
5858
private final Path path;
5959
private final WatchScope scope;
@@ -65,7 +65,7 @@ public class Watcher {
6565
private static final Predicate<WatchEvent> TRUE_FILTER = e -> true;
6666
private volatile Predicate<WatchEvent> eventFilter = TRUE_FILTER;
6767

68-
private Watcher(Path path, WatchScope scope) {
68+
private Watch(Path path, WatchScope scope) {
6969
this.path = path;
7070
this.scope = scope;
7171
}
@@ -76,7 +76,7 @@ private Watcher(Path path, WatchScope scope) {
7676
* @param scope for directories you can also choose to monitor it's direct children or all it's descendants
7777
* @throws IllegalArgumentException in case a path is not supported (in relation to the scope)
7878
*/
79-
public static Watcher watch(Path path, WatchScope scope) {
79+
public static Watch build(Path path, WatchScope scope) {
8080
if (!path.isAbsolute()) {
8181
throw new IllegalArgumentException("We can only watch absolute paths");
8282
}
@@ -95,7 +95,7 @@ public static Watcher watch(Path path, WatchScope scope) {
9595
default:
9696
throw new IllegalArgumentException("Unsupported scope: " + scope);
9797
}
98-
return new Watcher(path, scope);
98+
return new Watch(path, scope);
9999
}
100100

101101
/**
@@ -105,7 +105,7 @@ public static Watcher watch(Path path, WatchScope scope) {
105105
* @param eventHandler a callback that handles the watch event, will be called once per event.
106106
* @return this for optional method chaining
107107
*/
108-
public Watcher on(Consumer<WatchEvent> eventHandler) {
108+
public Watch on(Consumer<WatchEvent> eventHandler) {
109109
if (this.eventHandler != EMPTY_HANDLER) {
110110
throw new IllegalArgumentException("on handler cannot be set more than once");
111111
}
@@ -116,7 +116,7 @@ public Watcher on(Consumer<WatchEvent> eventHandler) {
116116
/**
117117
* Convenience variant of {@link #on(Consumer)}, which allows you to only respond to certain events
118118
*/
119-
public Watcher on(WatchEventListener listener) {
119+
public Watch on(WatchEventListener listener) {
120120
if (this.eventHandler != EMPTY_HANDLER) {
121121
throw new IllegalArgumentException("on handler cannot be set more than once");
122122
}
@@ -149,7 +149,7 @@ public Watcher on(WatchEventListener listener) {
149149
* ({@code true}) or dropped ({@code false})
150150
* @return {@code this} (to support method chaining)
151151
*/
152-
Watcher filter(Predicate<WatchEvent> predicate) {
152+
Watch filter(Predicate<WatchEvent> predicate) {
153153
if (this.eventFilter != TRUE_FILTER) {
154154
throw new IllegalArgumentException("filter cannot be set more than once");
155155
}
@@ -163,7 +163,7 @@ Watcher filter(Predicate<WatchEvent> predicate) {
163163
* @param callbackHandler worker pool to use
164164
* @return this for optional method chaining
165165
*/
166-
public Watcher withExecutor(Executor callbackHandler) {
166+
public Watch withExecutor(Executor callbackHandler) {
167167
this.executor = callbackHandler;
168168
return this;
169169
}
@@ -179,7 +179,7 @@ public Watcher withExecutor(Executor callbackHandler) {
179179
* files/directories to approximate
180180
* @return This watcher for optional method chaining
181181
*/
182-
public Watcher onOverflow(Approximation whichFiles) {
182+
public Watch onOverflow(Approximation whichFiles) {
183183
this.approximateOnOverflow = whichFiles;
184184
return this;
185185
}

src/main/java/engineering/swat/watch/impl/jdk/JDKFileWatch.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,9 @@ public JDKFileWatch(Path file, Executor exec,
6161
assert !parent.equals(file);
6262

6363
this.internal = new JDKDirectoryWatch(parent, exec, (w, e) -> {
64-
if (e.getKind() == WatchEvent.Kind.OVERFLOW) {
65-
var overflow = new WatchEvent(WatchEvent.Kind.OVERFLOW, file);
66-
eventHandler.accept(w, overflow);
67-
}
68-
if (fileName.equals(e.getRelativePath())) {
69-
eventHandler.accept(w, e);
64+
var kind = e.getKind();
65+
if (kind == WatchEvent.Kind.OVERFLOW || e.getRelativePath().equals(fileName)) {
66+
eventHandler.accept(w, new WatchEvent(kind, file));
7067
}
7168
}, eventFilter);
7269

src/main/java/engineering/swat/watch/impl/overflows/IndexingRescanner.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444

4545
import org.apache.logging.log4j.LogManager;
4646
import org.apache.logging.log4j.Logger;
47+
import org.checkerframework.checker.nullness.qual.NonNull;
4748
import org.checkerframework.checker.nullness.qual.Nullable;
4849

4950
import engineering.swat.watch.WatchEvent;
@@ -59,7 +60,7 @@ public IndexingRescanner(Executor exec, Path path, WatchScope scope) {
5960
new Indexer(path, scope).walkFileTree(); // Make an initial scan to populate the index
6061
}
6162

62-
private static class PathMap<V> {
63+
private static class PathMap<V extends @NonNull Object > {
6364
private final Map<Path, Map<Path, V>> values = new ConcurrentHashMap<>();
6465
// ^^^^ ^^^^
6566
// Parent File name (regular file or directory)
@@ -85,7 +86,7 @@ public Set<Path> getFileNames(Path parent) {
8586
return apply(this::remove, p);
8687
}
8788

88-
private static <V> @Nullable V apply(BiFunction<Path, Path, @Nullable V> action, Path p) {
89+
private static <V extends @Nullable Object> V apply(BiFunction<Path, Path, V> action, Path p) {
8990
var parent = p.getParent();
9091
var fileName = p.getFileName();
9192
if (parent != null && fileName != null) {

src/test/java/engineering/swat/watch/APIErrorsTests.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ static void setupEverything() {
6161
@Test
6262
void noDuplicateEvents() {
6363
assertThrowsExactly(IllegalArgumentException.class, () ->
64-
Watcher
65-
.watch(testDir.getTestDirectory(), WatchScope.PATH_AND_CHILDREN)
64+
Watch
65+
.build(testDir.getTestDirectory(), WatchScope.PATH_AND_CHILDREN)
6666
.on(System.out::println)
6767
.on(System.err::println)
6868
);
@@ -71,16 +71,16 @@ void noDuplicateEvents() {
7171
@Test
7272
void onlyDirectoryWatchingOnDirectories() {
7373
assertThrowsExactly(IllegalArgumentException.class, () ->
74-
Watcher
75-
.watch(testDir.getTestFiles().get(0), WatchScope.PATH_AND_CHILDREN)
74+
Watch
75+
.build(testDir.getTestFiles().get(0), WatchScope.PATH_AND_CHILDREN)
7676
);
7777
}
7878

7979
@Test
8080
void doNotStartWithoutEventHandler() {
8181
assertThrowsExactly(IllegalStateException.class, () ->
82-
Watcher
83-
.watch(testDir.getTestDirectory(), WatchScope.PATH_AND_CHILDREN)
82+
Watch
83+
.build(testDir.getTestDirectory(), WatchScope.PATH_AND_CHILDREN)
8484
.start()
8585
);
8686
}
@@ -90,8 +90,8 @@ void noRelativePaths() {
9090
var relativePath = testDir.getTestDirectory().resolve("d1").relativize(testDir.getTestDirectory());
9191

9292
assertThrowsExactly(IllegalArgumentException.class, () ->
93-
Watcher
94-
.watch(relativePath, WatchScope.PATH_AND_CHILDREN)
93+
Watch
94+
.build(relativePath, WatchScope.PATH_AND_CHILDREN)
9595
.start()
9696
);
9797
}
@@ -100,7 +100,7 @@ void noRelativePaths() {
100100
void nonExistingDirectory() throws IOException {
101101
var nonExistingDir = testDir.getTestDirectory().resolve("testd1");
102102
Files.createDirectory(nonExistingDir);
103-
var w = Watcher.watch(nonExistingDir, WatchScope.PATH_AND_CHILDREN);
103+
var w = Watch.build(nonExistingDir, WatchScope.PATH_AND_CHILDREN);
104104
Files.delete(nonExistingDir);
105105
assertThrowsExactly(IllegalStateException.class, w::start);
106106
}

src/test/java/engineering/swat/watch/DeleteLockTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ private static void recursiveDelete(Path target) throws IOException {
7676
}
7777

7878
private void deleteAndVerify(Path target, WatchScope scope) throws IOException {
79-
try (var watch = Watcher.watch(target, scope).on(ev -> {}).start()) {
79+
try (var watch = Watch.build(target, scope).on(ev -> {}).start()) {
8080
recursiveDelete(target);
8181
assertFalse(Files.exists(target), "The file/directory shouldn't exist anymore");
8282
}

0 commit comments

Comments
 (0)