Skip to content

Commit f93b732

Browse files
authored
Merge pull request #628 from jooby-project/filewatcher
File watcher module
2 parents af250ed + 13b8b4a commit f93b732

22 files changed

Lines changed: 2214 additions & 19 deletions

coverage-report/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
<source>${project.parent.basedir}/jooby-csl/src/main/java</source>
8686
<source>${project.parent.basedir}/jooby-unbescape/src/main/java</source>
8787
<source>${project.parent.basedir}/jooby-thymeleaf/src/main/java</source>
88+
<source>${project.parent.basedir}/jooby-filewatcher/src/main/java</source>
8889
</sources>
8990
</configuration>
9091
</execution>
@@ -145,6 +146,7 @@
145146
<source>${project.parent.basedir}/jooby-csl/src/test/java</source>
146147
<source>${project.parent.basedir}/jooby-unbescape/src/test/java</source>
147148
<source>${project.parent.basedir}/jooby-thymeleaf/src/test/java</source>
149+
<source>${project.parent.basedir}/jooby-filewatcher/src/test/java</source>
148150
</sources>
149151
</configuration>
150152
</execution>

jooby-filewatcher/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
workdir

jooby-filewatcher/pom.xml

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
4+
5+
<parent>
6+
<groupId>org.jooby</groupId>
7+
<artifactId>jooby-project</artifactId>
8+
<version>1.1.0-SNAPSHOT</version>
9+
</parent>
10+
11+
<modelVersion>4.0.0</modelVersion>
12+
<artifactId>jooby-filewatcher</artifactId>
13+
14+
<name>file watcher module</name>
15+
<build>
16+
<plugins>
17+
<!-- sure-fire -->
18+
<plugin>
19+
<groupId>org.apache.maven.plugins</groupId>
20+
<artifactId>maven-surefire-plugin</artifactId>
21+
<configuration>
22+
<includes>
23+
<include>**/*Test.java</include>
24+
<include>**/*Feature.java</include>
25+
<include>**/Issue*.java</include>
26+
</includes>
27+
</configuration>
28+
</plugin>
29+
30+
</plugins>
31+
</build>
32+
33+
<dependencies>
34+
<!-- Jooby -->
35+
<dependency>
36+
<groupId>org.jooby</groupId>
37+
<artifactId>jooby</artifactId>
38+
<version>${project.version}</version>
39+
</dependency>
40+
41+
<!-- Test dependencies -->
42+
<dependency>
43+
<groupId>org.jooby</groupId>
44+
<artifactId>jooby</artifactId>
45+
<version>${project.version}</version>
46+
<scope>test</scope>
47+
<classifier>tests</classifier>
48+
</dependency>
49+
50+
<dependency>
51+
<groupId>org.jooby</groupId>
52+
<artifactId>jooby-netty</artifactId>
53+
<version>${project.version}</version>
54+
<scope>test</scope>
55+
</dependency>
56+
57+
<dependency>
58+
<groupId>junit</groupId>
59+
<artifactId>junit</artifactId>
60+
<scope>test</scope>
61+
</dependency>
62+
63+
<dependency>
64+
<groupId>org.easymock</groupId>
65+
<artifactId>easymock</artifactId>
66+
<scope>test</scope>
67+
</dependency>
68+
69+
<dependency>
70+
<groupId>org.powermock</groupId>
71+
<artifactId>powermock-api-easymock</artifactId>
72+
<scope>test</scope>
73+
</dependency>
74+
75+
<dependency>
76+
<groupId>org.powermock</groupId>
77+
<artifactId>powermock-module-junit4</artifactId>
78+
<scope>test</scope>
79+
</dependency>
80+
81+
<dependency>
82+
<groupId>org.jacoco</groupId>
83+
<artifactId>org.jacoco.agent</artifactId>
84+
<classifier>runtime</classifier>
85+
<scope>test</scope>
86+
</dependency>
87+
88+
</dependencies>
89+
90+
</project>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.jooby.filewatcher;
20+
21+
import java.io.IOException;
22+
import java.nio.file.Path;
23+
import java.nio.file.WatchEvent;
24+
25+
/**
26+
* Callback call when a file or folder has been created, modified or deleted.
27+
*
28+
* @author edgar
29+
* @since 1.1.0
30+
*/
31+
public interface FileEventHandler {
32+
33+
/**
34+
* File handler callback.
35+
*
36+
* @param kind Event type.
37+
* @param path Path created, modified or deleted.
38+
* @throws IOException If something goes wrong.
39+
*/
40+
void handle(WatchEvent.Kind<Path> kind, Path path) throws IOException;
41+
42+
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
* <p>
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
* <p>
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.jooby.filewatcher;
20+
21+
import com.sun.nio.file.SensitivityWatchEventModifier;
22+
23+
import java.io.IOException;
24+
import java.nio.file.*;
25+
import java.nio.file.StandardWatchEventKinds;
26+
import java.nio.file.WatchEvent.Modifier;
27+
import java.util.ArrayList;
28+
import java.util.Arrays;
29+
import java.util.List;
30+
import java.util.Optional;
31+
import java.util.function.Function;
32+
33+
import static java.nio.file.StandardWatchEventKinds.*;
34+
import static java.util.Objects.requireNonNull;
35+
36+
/**
37+
* Allow to customize a file watch handler. You can listen for particular event kinds, apply a glob
38+
* filter, recursive listening for changes and set watcher priority or modifier.
39+
* <p>
40+
* Default filter watch recursively listen for all the available kinds using a <code>HIGH</code>
41+
* modifier.
42+
* <p>
43+
* This class can't be instantiated by client code. Intances of this class are provided via
44+
* configuration callbacks. See
45+
* {@link FileWatcher#register(Path, Class, java.util.function.Consumer)}
46+
*
47+
* @author edgar
48+
* @since 1.1.0
49+
*/
50+
public class FileEventOptions {
51+
52+
private List<WatchEvent.Kind<Path>> kinds = new ArrayList<>();
53+
54+
static final PathMatcher TRUE = new PathMatcher() {
55+
56+
@Override
57+
public boolean matches(final Path path) {
58+
return true;
59+
}
60+
61+
@Override
62+
public String toString() {
63+
return "**/*";
64+
}
65+
};
66+
67+
private static final WatchEvent.Kind<?>[] KINDS = {ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY};
68+
69+
private final List<PathMatcher> matchers = new ArrayList<>();
70+
71+
private Modifier modifier = SensitivityWatchEventModifier.HIGH;
72+
73+
private boolean recursive = true;
74+
75+
private final Class<? extends FileEventHandler> handler;
76+
77+
private final Path path;
78+
79+
private FileEventHandler handlerInstance;
80+
81+
FileEventOptions(final Path path, final Class<? extends FileEventHandler> handler)
82+
throws IOException {
83+
if (!Files.exists(path)) {
84+
Files.createDirectories(path);
85+
}
86+
this.path = path;
87+
this.handler = handler;
88+
this.handlerInstance = null;
89+
}
90+
91+
FileEventOptions(final Path path, final FileEventHandler handler) throws IOException {
92+
this(path, handler.getClass());
93+
this.handlerInstance = handler;
94+
}
95+
96+
/**
97+
* Append a kind filter.
98+
* <p>
99+
* The default filter is: {@link StandardWatchEventKinds#ENTRY_CREATE},
100+
* {@link StandardWatchEventKinds#ENTRY_DELETE} and {@link StandardWatchEventKinds#ENTRY_MODIFY}.
101+
*
102+
* @param kind Filter type.
103+
* @return This options.
104+
*/
105+
public FileEventOptions kind(final WatchEvent.Kind<Path> kind) {
106+
requireNonNull(kind, "WatchEvent.Kind required.");
107+
kinds.add(kind);
108+
return this;
109+
}
110+
111+
/**
112+
* Turn on/off watching of subfolder.
113+
*
114+
* @param recursive True to watch on subfolder.
115+
* @return This options.
116+
*/
117+
public FileEventOptions recursive(final boolean recursive) {
118+
this.recursive = recursive;
119+
return this;
120+
}
121+
122+
/**
123+
* Add a path filter using <code>glob</code> expression, like <code><pre>** / *.java</pre><code>,
124+
* etc...
125+
*
126+
* @param expression Glob expression.
127+
* @return This options.
128+
*/
129+
public FileEventOptions includes(final String expression) {
130+
requireNonNull(expression, "Glob expression required.");
131+
this.matchers.add(new GlobPathMatcher(expression));
132+
return this;
133+
}
134+
135+
/**
136+
* Set a watch modifier. Default is: <code>HIGH</code>.
137+
*
138+
* @param modifier Watch modifier.
139+
* @return This options.
140+
*/
141+
public FileEventOptions modifier(final WatchEvent.Modifier modifier) {
142+
requireNonNull(modifier, "Modifier required.");
143+
this.modifier = modifier;
144+
return this;
145+
}
146+
147+
Modifier modifier() {
148+
return modifier;
149+
}
150+
151+
WatchEvent.Kind<?>[] kinds() {
152+
return kinds.size() == 0 ? KINDS : kinds.toArray(new WatchEvent.Kind[0]);
153+
}
154+
155+
PathMatcher filter() {
156+
return matchers.size() == 0 ? TRUE : new FirstOfPathMatcher(matchers);
157+
}
158+
159+
boolean recursive() {
160+
return recursive;
161+
}
162+
163+
Path path() {
164+
return path;
165+
}
166+
167+
FileEventHandler handler(
168+
final Function<Class<? extends FileEventHandler>, FileEventHandler> factory) {
169+
return Optional.ofNullable(handlerInstance).orElseGet(() -> factory.apply(handler));
170+
}
171+
172+
@Override
173+
public String toString() {
174+
return path + " {kinds: " + Arrays.toString(kinds()) + ", filter: " + filter()
175+
+ ", recursive: " + recursive + ", modifier: " + modifier + "}";
176+
}
177+
}

0 commit comments

Comments
 (0)