Commit d71711f5 authored by Stefan Schuhbaeck's avatar Stefan Schuhbaeck
Browse files

Add OutputDirWatcher and builder class

The OutputDirWatcher can watch all or a subset of directories within
the output directory of a VadereProject. If The filesystem
WatchEventHandler will trigger reload of simulation output directories.
TODO: implement reload.
parent 0dc448df
package org.vadere.simulator.projects;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* Implements directory watcher to notify project if the filesystem changes.
*
* @author Stefan Schuhbäck
*/
public class OutputDirWatcher implements Runnable {
private WatchService watchService;
private ConcurrentHashMap<WatchKey, Path> keys;
private List<WatchEventHandler> handlers;
private VadereProject project;
public OutputDirWatcher(VadereProject project) throws IOException {
this.project = project;
}
@SuppressWarnings("unchecked")
static <T> WatchEvent<T> cast(WatchEvent<?> event) {
return (WatchEvent<T>) event;
}
private WatchEvent<Path>[] getWatchEvents(List<WatchEvent<?>> list) {
List<WatchEvent<Path>> ret = new ArrayList<>();
list.forEach(e -> ret.add(cast(e)));
return list.toArray(new WatchEvent[list.size()]);
}
@Override
public void run() {
System.out.println("Start Watching...");
try {
while (true) {
WatchKey key = watchService.take();
Path dir = keys.get(key);
if (dir == null) {
System.out.println("Key not found: " + key.toString());
continue;
}
WatchEvent<Path>[] events = getWatchEvents(key.pollEvents());
handlers.forEach(handler -> handler.processEvent(dir, events));
boolean valid = key.reset();
if (!valid)
keys.remove(key);
}
} catch (InterruptedException e) {
try {
watchService.close();
System.out.println("Cleanup Watcher...");
} catch (IOException e1) {
//log
e1.printStackTrace();
}
System.out.println("return from Watcher");
return;
}
}
public WatchService getWatchService() {
return watchService;
}
public void setWatchService(WatchService watchService) {
this.watchService = watchService;
}
public ConcurrentHashMap<WatchKey, Path> getKeys() {
return keys;
}
public void setKeys(ConcurrentHashMap<WatchKey, Path> keys) {
this.keys = keys;
}
public List<WatchEventHandler> getHandlers() {
return handlers;
}
public void setHandlers(List<WatchEventHandler> handlers) {
this.handlers = handlers;
}
public VadereProject getProject() {
return project;
}
public void setProject(VadereProject project) {
this.project = project;
}
}
package org.vadere.simulator.projects;
import org.vadere.util.io.IOUtils;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
/**
* Builder class for {@link OutputDirWatcher}.
*
* @author Stefan Schuhbäck
*/
public class OutputDirWatcherBuilder {
public static final WatchEvent.Kind<?>[] DEFAULT_KEYS =
new WatchEvent.Kind<?>[]{ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY};
private OutputDirWatcher outputDirWatcher;
private ConcurrentHashMap<WatchKey, Path> keys;
private WatchService watchService;
private List<WatchEventHandler> handlers;
public OutputDirWatcherBuilder() {
}
public OutputDirWatcherBuilder initOutputDirWatcher(VadereProject project) throws IOException {
this.outputDirWatcher = new OutputDirWatcher(project);
this.watchService = FileSystems.getDefault().newWatchService();
this.keys = new ConcurrentHashMap<>();
this.handlers = new ArrayList<>();
return this;
}
public OutputDirWatcherBuilder registerDefaultDir() throws IOException {
Path root = outputDirWatcher.getProject().getOutputDir();
registerAll(root);
addDefaultEventHandler();
return this;
}
public OutputDirWatcher build() {
outputDirWatcher.setKeys(this.keys);
outputDirWatcher.setHandlers(this.handlers);
outputDirWatcher.setWatchService(this.watchService);
return outputDirWatcher;
}
private void addDefaultEventHandler() {
WatchEventHandler simulationOutputDirCreated = (dir, ev) -> {
if ((ev.length == 1) && (ev[0].kind() == ENTRY_CREATE)) {
Path context = dir.resolve(ev[0].context());
if (context.toFile().isDirectory()) {
System.out.println("simulationOutputDirCreated ...");
}
}
};
addEventHandler(simulationOutputDirCreated);
WatchEventHandler simulationOutputDirModified = (dir, ev) -> {
if ((ev.length == 2) && (ev[0].kind() == ENTRY_DELETE) && (ev[1].kind() == ENTRY_CREATE)) {
Path context = dir.resolve(ev[1].context());
if (context.toFile().isDirectory()) {
System.out.print("simulationOutputDirModified new name: ");
System.out.println(context.getFileName().toString());
}
}
};
addEventHandler(simulationOutputDirModified);
WatchEventHandler simulationOutputFileNew = (dir, ev) -> {
if ((ev.length == 2) && (ev[0].kind() == ENTRY_CREATE) && (ev[1].kind() == ENTRY_MODIFY)) {
Path context = dir.resolve(ev[1].context());
if (context.toFile().isFile()) {
System.out.print("a file was created...");
System.out.format(" in %s with the name of %s as dirty!%n", dir.getFileName().toString(), context.getFileName().toString());
}
}
};
addEventHandler(simulationOutputFileNew);
WatchEventHandler simulationOutputFileModified = (dir, ev) -> {
if ((ev.length == 1) && (ev[0].kind() == ENTRY_MODIFY)) {
Path context = dir.resolve(ev[0].context());
if (context.toFile().isFile()) {
System.out.print("a file was modified...");
System.out.format(" mark %s as dirty!%n", dir.getFileName().toString());
}
}
};
addEventHandler(simulationOutputFileModified);
WatchEventHandler delete = (dir, ev) -> {
if ((ev.length == 1) && (ev[0].kind() == ENTRY_DELETE)) {
Path context = dir.resolve(ev[0].context());
if (context.toFile().isFile()) {
System.out.format("file deleted %s!%n", context.getFileName().toString());
} else {
System.out.format("dir deleted %s!%n", context.getFileName().toString());
}
}
};
addEventHandler(delete);
}
public OutputDirWatcherBuilder addEventHandler(WatchEventHandler handler) {
this.handlers.add(handler);
return this;
}
public void register(Path dir, WatchEvent.Kind<?>... events) throws IOException {
WatchEvent.Kind<?>[] selectedEvents;
if (events.length <= 0) {
selectedEvents = DEFAULT_KEYS;
} else {
selectedEvents = events;
}
if (dir.toString().contains(IOUtils.CORRUPT_DIR))
return;
WatchKey key = dir.register(watchService, selectedEvents);
keys.put(key, dir);
}
public void registerAll(Path root, WatchEvent.Kind<?>... events) throws IOException {
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
//do not watch corrupt directory or its children
if (dir.endsWith(IOUtils.CORRUPT_DIR)) {
return FileVisitResult.SKIP_SUBTREE;
}
register(dir, events);
return FileVisitResult.CONTINUE;
}
});
}
public OutputDirWatcher getOutputDirWatcher() {
return outputDirWatcher;
}
public void setOutputDirWatcher(OutputDirWatcher outputDirWatcher) {
this.outputDirWatcher = outputDirWatcher;
}
public ConcurrentHashMap<WatchKey, Path> getKeys() {
return keys;
}
public void setKeys(ConcurrentHashMap<WatchKey, Path> keys) {
this.keys = keys;
}
public WatchService getWatchService() {
return watchService;
}
public void setWatchService(WatchService watchService) {
this.watchService = watchService;
}
public List<WatchEventHandler> getHandlers() {
return handlers;
}
public void setHandlers(List<WatchEventHandler> handlers) {
this.handlers = handlers;
}
}
package org.vadere.simulator.projects;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
/**
*
* @author Stefan Schuhbäck
*/
@FunctionalInterface
public interface WatchEventHandler {
void processEvent(Path dir, WatchEvent<Path>[] events);
}
package org.vadere.simulator.projects;
import org.junit.Before;
import org.junit.Test;
import org.vadere.simulator.projects.io.IOVadere;
import org.vadere.util.io.IOUtils;
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
/**
*
* @author Stefan Schuhbäck
*/
public class OutputDirWatcherBuilderTest {
private VadereProject project;
private OutputDirWatcherBuilder builder;
@Before
public void setup() throws URISyntaxException, IOException {
Path projectPath = Paths.get(getClass().getResource("/data/simpleProject").toURI());
project = IOVadere.readProject(projectPath.toString());
builder = new OutputDirWatcherBuilder();
}
@Test
public void initOutputDirWatcher() throws Exception {
builder.initOutputDirWatcher(project);
assertNotNull(builder.getWatchService());
assertNotNull(builder.getKeys());
assertNotNull(builder.getHandlers());
}
@Test
public void registerDefaultDir() throws Exception {
builder.initOutputDirWatcher(project).registerDefaultDir();
assertEquals("There should be three default Handlers",
5,builder.getHandlers().size());
assertEquals("There should be 15 directories in the watch list",
14, builder.getKeys().size());
Path corruptedDir = project.getOutputDir().resolve(IOUtils.CORRUPT_DIR);
builder.getKeys().forEach((k,v) -> {
assertNotEquals("The corrupt directory should not be watched",
corruptedDir, v);
});
}
@Test
public void build() throws Exception {
}
@Test
public void addEventHandler() throws Exception {
WatchEventHandler handler = (dir, ev) -> {
System.out.print("XXX");
};
builder.initOutputDirWatcher(project).addEventHandler(handler);
assertEquals("There should be one Handler",
1,
builder.getHandlers().size());
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
System.setOut(new PrintStream(buffer));
builder.getHandlers().forEach( h -> h.processEvent(null, null));
System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out)));
assertEquals("WatchEventHandler output wrong", "XXX", buffer.toString());
}
@Test
public void register() throws Exception {
builder.initOutputDirWatcher(project)
.register(project.getOutputDir().resolve("test_postvis_2018-01-17_16-57-06.272"));
assertEquals("There should be one watched directory", 1,builder.getKeys().size());
builder.register(project.getOutputDir().resolve("corrupt/test_postvis_2018-01-19_13-38-01.695"));
assertEquals("There should be one watched directory", 1,builder.getKeys().size());
}
@Test
public void registerAll() throws Exception {
builder.initOutputDirWatcher(project).registerAll(project.getOutputDir());
assertEquals("There should be 15 directories in the watch list",
14, builder.getKeys().size());
}
}
\ No newline at end of file
package org.vadere.simulator.projects;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.vadere.simulator.projects.io.IOVadere;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.WatchKey;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*
* @author Stefan Schuhbäck
*/
public class OutputDirWaterTest {
public class OutputDirWatcherTest {
private Path outputDir;
OutputDirWatcher dirWatcher;
private VadereProject project;
@Before
public void setup() throws Exception {
outputDir = Paths.get(getClass().getResource("/data/simpleProject/output").toURI());
// dirWatcher = new OutputDirWatcher();
public void setup() throws URISyntaxException, IOException {
Path projectPath = Paths.get(getClass().getResource("/data/simpleProject").toURI());
project = IOVadere.readProject(projectPath.toString());
}
@Test @Ignore
public void testist(){
List<WatcherHandler> list = new ArrayList<>();
// list.add(new WatcherHandlerImpl("Hi") {
// @Override
// public void doit() {
// System.out.println(s);
// }
// });
//
// list.add(new WatcherHandlerImpl("World!") {
// @Override
// public void doit() {
// System.out.println(s);
// }
// });
// String s = "hi";
// WatcherHandler w = () -> System.out.println(s);
//
// list.add(w);
// list.add(WatcherHandler -> System.out.println(s.toUpperCase()));
// list.forEach(e -> e.);
public void OutputDirWatcherEventTest() throws IOException, InterruptedException {
}
OutputDirWatcherBuilder builder = new OutputDirWatcherBuilder();
builder.initOutputDirWatcher(project);
builder.registerDefaultDir();
OutputDirWatcher dirWatcher = builder.build();
@Test @Ignore
public void WatchTest() throws IOException, InterruptedException {
dirWatcher.registerAll(outputDir);
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<?> future = executor.submit(dirWatcher);
executor.shutdown();
......@@ -69,27 +45,17 @@ public class OutputDirWaterTest {
// Shutdown after 10 seconds
executor.awaitTermination(30, TimeUnit.SECONDS);
// abort watcher
future.cancel(true);
executor.awaitTermination(1, TimeUnit.SECONDS);
executor.shutdownNow();
}
@Test @Ignore
public void numberOfWatchedDirsTest() throws IOException {
dirWatcher.registerAll(outputDir);
ConcurrentHashMap<WatchKey, Path> map = dirWatcher.getKeys();
assertEquals("There should be 14 simulation dirs and the output dir itself!",
15 ,map.size());
}
private void addDummyEventHandler(OutputDirWatcherBuilder builder) {
@Test @Ignore
public void ignoreCourruptedDirTest() throws IOException {
dirWatcher.registerAll(outputDir);
ConcurrentHashMap<WatchKey, Path> map = dirWatcher.getKeys();
map.forEach((watchKey, path) -> {
assertFalse("Directory corrupt should not be watched! ",path.endsWith("corrupt"));
});
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment