Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions src/main/checkerframework/nio-file.astub
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ public interface WatchService {
public interface WatchEvent<T> {
@Nullable T context();
}

public class NoSuchFileException extends FileSystemException {
public NoSuchFileException(String file, @Nullable String other, String reason);
}

52 changes: 32 additions & 20 deletions src/main/java/engineering/swat/watch/Watch.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,14 @@
*/
package engineering.swat.watch;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.FileSystemException;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
Expand Down Expand Up @@ -76,28 +81,13 @@ private Watch(Path path, WatchScope scope) {
* Watch a path for updates, optionally also get events for its children/descendants
* @param path which absolute path to monitor, can be a file or a directory, but has to be absolute
* @param scope for directories you can also choose to monitor it's direct children or all it's descendants
* @throws IllegalArgumentException in case a path is not supported (in relation to the scope)
* @throws IllegalArgumentException in case a path is not supported
* @return watch builder that can be further configured and then started
*/
public static Watch build(Path path, WatchScope scope) {
if (!path.isAbsolute()) {
throw new IllegalArgumentException("We can only watch absolute paths");
}
switch (scope) {
case PATH_AND_CHILDREN: // intended fallthrough
case PATH_AND_ALL_DESCENDANTS:
if (!Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS)) {
throw new IllegalArgumentException("Only directories are supported for this scope: " + scope);
}
break;
case PATH_ONLY:
if (Files.isSymbolicLink(path)) {
throw new IllegalArgumentException("Symlinks are not supported");
}
break;
default:
throw new IllegalArgumentException("Unsupported scope: " + scope);
}
return new Watch(path, scope);
}

Expand Down Expand Up @@ -192,16 +182,38 @@ public Watch onOverflow(Approximation whichFiles) {
return this;
}

private void validateOptions() throws IOException {
if (this.eventHandler == EMPTY_HANDLER) {
throw new IllegalStateException("There is no `on` handler defined");
}
if (!Files.exists(path)) {
throw new NoSuchFileException(path.toString(), null, "Cannot open a watch on a non-existing path");
}
switch (scope) {
case PATH_AND_CHILDREN: // intended fallthrough
case PATH_AND_ALL_DESCENDANTS:
if (!Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS)) {
throw new FileSystemException(path.toString(), null, "Only directories are supported for this scope: " + scope);
}
break;
case PATH_ONLY:
if (Files.isSymbolicLink(path)) {
throw new FileSystemException(path.toString(), null, "Symlinks are not supported");
}
break;
default:
throw new IllegalArgumentException("Unsupported scope: " + scope);
}
}

/**
* Start watch the path for events.
* @return a subscription for the watch, when closed, new events will stop being registered to the worker pool.
* @throws IOException in case the starting of the watcher caused an underlying IO exception
* @throws IOException in case the starting of the watcher caused an underlying IO exception or we detect it is an invalid watch
* @throws IllegalStateException the watchers is not configured correctly (for example, missing {@link #on(Consumer)}, or a watcher is started twice)
*/
public ActiveWatch start() throws IOException {
if (this.eventHandler == EMPTY_HANDLER) {
throw new IllegalStateException("There is no onEvent handler defined");
}
validateOptions();
var executor = this.executor;
if (executor == null) {
executor = FALLBACK_EXECUTOR;
Expand Down
16 changes: 8 additions & 8 deletions src/test/java/engineering/swat/watch/APIErrorsTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.FileSystemException;
import java.nio.file.NoSuchFileException;

import org.awaitility.Awaitility;
import org.junit.jupiter.api.AfterEach;
Expand Down Expand Up @@ -70,9 +71,11 @@ void noDuplicateEvents() {

@Test
void onlyDirectoryWatchingOnDirectories() {
assertThrowsExactly(IllegalArgumentException.class, () ->
assertThrowsExactly(FileSystemException.class, () ->
Watch
.build(testDir.getTestFiles().get(0), WatchScope.PATH_AND_CHILDREN)
.on(e -> {})
.start()
);
}

Expand All @@ -92,17 +95,14 @@ void noRelativePaths() {
assertThrowsExactly(IllegalArgumentException.class, () ->
Watch
.build(relativePath, WatchScope.PATH_AND_CHILDREN)
.start()
);
}

@Test
void nonExistingDirectory() throws IOException {
var nonExistingDir = testDir.getTestDirectory().resolve("testd1");
Files.createDirectory(nonExistingDir);
var w = Watch.build(nonExistingDir, WatchScope.PATH_AND_CHILDREN);
Files.delete(nonExistingDir);
assertThrowsExactly(IllegalStateException.class, w::start);
var nonExistingDir = testDir.getTestDirectory().resolve("test-not-existing");
var w = Watch.build(nonExistingDir, WatchScope.PATH_AND_CHILDREN).on(e -> {});
assertThrowsExactly(NoSuchFileException.class, w::start);
}


Expand Down
Loading