commit
e2e509210c
@ -1,9 +1,11 @@
|
||||
package net.sf.openrocket.startup;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.logging.LogHelper;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import net.sf.openrocket.util.watcher.DirectoryChangeReactor;
|
||||
import net.sf.openrocket.util.watcher.DirectoryChangeReactorImpl;
|
||||
|
||||
public class ApplicationModule extends AbstractModule {
|
||||
|
||||
@ -12,6 +14,7 @@ public class ApplicationModule extends AbstractModule {
|
||||
bind(LogHelper.class).toInstance(Application.getLogger());
|
||||
bind(Preferences.class).toInstance(Application.getPreferences());
|
||||
bind(Translator.class).toInstance(Application.getTranslator());
|
||||
bind(DirectoryChangeReactor.class).to(DirectoryChangeReactorImpl.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
87
core/src/net/sf/openrocket/util/watcher/Directory.java
Normal file
87
core/src/net/sf/openrocket/util/watcher/Directory.java
Normal file
@ -0,0 +1,87 @@
|
||||
package net.sf.openrocket.util.watcher;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* A kind of watched file that is a directory.
|
||||
*/
|
||||
public class Directory extends WatchedFile {
|
||||
|
||||
/**
|
||||
* The contents.
|
||||
*/
|
||||
private final HashMap<String, WatchedFile> contents = new HashMap<String, WatchedFile>();
|
||||
|
||||
/**
|
||||
* Internal lock object.
|
||||
*/
|
||||
private final Object lock = new Object();
|
||||
|
||||
/**
|
||||
* Construct a directory to be watched.
|
||||
*
|
||||
* @param dir the directory to be watched
|
||||
*
|
||||
* @throws IllegalArgumentException if dir is null, does not exist, or is not a directory
|
||||
*/
|
||||
public Directory(File dir) throws IllegalArgumentException {
|
||||
super(dir);
|
||||
if (dir == null || !dir.isDirectory() || !dir.exists()) {
|
||||
throw new IllegalArgumentException("Invalid directory.");
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the directory handling.
|
||||
*/
|
||||
private void init() {
|
||||
String[] result = list();
|
||||
for (String s : result) {
|
||||
File t = new File(getTarget(), s);
|
||||
if (t.exists()) {
|
||||
contents.put(s, new WatchedFile(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of the directory's immediate contents (the number of files or subdirectories).
|
||||
*
|
||||
* @return the number of files and directories in this directory (not deep)
|
||||
*/
|
||||
public int size() {
|
||||
return list().length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of filenames within this directory.
|
||||
*
|
||||
* @return an array of filenames
|
||||
*/
|
||||
String[] list() {
|
||||
synchronized (lock) {
|
||||
return getTarget().list();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the watched file contents.
|
||||
*
|
||||
* @return a map of watched files
|
||||
*/
|
||||
protected final HashMap<String, WatchedFile> getContents() {
|
||||
return contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared lock.
|
||||
*
|
||||
* @return a lock
|
||||
*/
|
||||
protected final Object getLock() {
|
||||
return lock;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package net.sf.openrocket.util.watcher;
|
||||
|
||||
/**
|
||||
* This interface abstracts the public API for a directory change reactor. In order to use the watcher subsystem, clients may use the default
|
||||
* change reactor (that implements this interface), or use the WatchService directly. This interface is more of a convenience abstraction.
|
||||
* <p/>
|
||||
* This only monitors directories. If you want to monitor an individual file, it is recommended that you monitor the directory that the file resides
|
||||
* within, then filter the WatchEvents accordingly.
|
||||
*/
|
||||
public interface DirectoryChangeReactor {
|
||||
|
||||
/**
|
||||
* Register an event handler with the reactor. The event handler will be called when either a creation, modification, or deletion event is detected
|
||||
* in the watched directory. Or a modification or a deletion event detected upon a watched file.
|
||||
*
|
||||
* @param theEventHandler the handler to be called when an event is detected
|
||||
*/
|
||||
void registerHandler(WatchedEventHandler<Directory> theEventHandler);
|
||||
|
||||
/**
|
||||
* Unregister an event handler with the reactor.
|
||||
*
|
||||
* @param theEventHandler the handler
|
||||
*/
|
||||
void unregisterHandler(WatchedEventHandler<Directory> theEventHandler);
|
||||
}
|
@ -0,0 +1,229 @@
|
||||
package net.sf.openrocket.util.watcher;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import net.sf.openrocket.logging.LogHelper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* This class is responsible for monitoring changes to files and directories and dispatching handle events appropriately. In order to use the watcher
|
||||
* subsystem, clients may use the default change reactor (this class), or use the WatchService directly. This class is more of a convenience
|
||||
* abstraction and doesn't necessarily represent the most efficient mechanism in all situations, but is sufficient for general purpose file watching
|
||||
* (short of using JDK 7).
|
||||
* <p/>
|
||||
* This reactor creates a new WatchService for each handler. A handler owns the business logic to be performed whenever a state change is detected in a
|
||||
* monitored directory. Each handler can monitor one directory (and optionally that directory's subdirectories). It's possible to implement a
|
||||
* different reactor that allows one handler to monitor many different directories or files.
|
||||
* <p/>
|
||||
* The default polling interval is 5 seconds. To override the polling interval a system property (openrocket.watcher.poll) can be specified. The time
|
||||
* value must be specified in milliseconds.
|
||||
* <p/>
|
||||
* For example, to change the interval to 10 seconds: -Dopenrocket.watcher.poll=10000
|
||||
* <p/>
|
||||
* <p/>
|
||||
* Example Usage of this class:
|
||||
* <pre>
|
||||
* <code>
|
||||
*
|
||||
* class MyHandler extends WatchedEventHandler<Directory> {
|
||||
*
|
||||
* private Directory dirToWatch = new Directory(new File("/tmp"));
|
||||
*
|
||||
* public Directory watchTarget() {
|
||||
* return dirToWatch;
|
||||
* }
|
||||
*
|
||||
* public boolean watchRecursively() {
|
||||
* return true;
|
||||
* }
|
||||
*
|
||||
* public void handleEvents(List<WatchEvent<?>> theEvents) {
|
||||
* for (int i = 0; i < events.size(); i++) {
|
||||
* WatchEvent<?> watchEvent = events.get(i);
|
||||
* // process the event
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* Guice (assuming that the binding is performed in an appropriate module):
|
||||
* {@literal @}Inject
|
||||
* DirectoryChangeReactor reactor;
|
||||
* reactor.registerHandler(new MyHandler());
|
||||
*
|
||||
* Programmatically:
|
||||
* DirectoryChangeReactor reactor = new DirectoryChangeReactorImpl();
|
||||
* reactor.registerHandler(new MyHandler());
|
||||
* </pre>
|
||||
*/
|
||||
public class DirectoryChangeReactorImpl implements DirectoryChangeReactor {
|
||||
|
||||
/**
|
||||
* Property for polling frequency.
|
||||
*/
|
||||
public static final String WATCHER_POLLING_INTERVAL_PROPERTY = "openrocket.watcher.poll";
|
||||
/**
|
||||
* The polling delay. Defaults to 5 seconds.
|
||||
*/
|
||||
public static final long DEFAULT_POLLING_DELAY = 5000;
|
||||
/**
|
||||
* The polling delay. Defaults to 5 seconds.
|
||||
*/
|
||||
private static long pollingDelay = DEFAULT_POLLING_DELAY;
|
||||
|
||||
@Inject
|
||||
private LogHelper log;
|
||||
|
||||
/**
|
||||
* Scheduler.
|
||||
*/
|
||||
private ScheduledExecutorService scheduler;
|
||||
/**
|
||||
* The ScheduledFuture.
|
||||
*/
|
||||
private ScheduledFuture directoryPollerFuture;
|
||||
/**
|
||||
* The runnable that does most of the work.
|
||||
*/
|
||||
private final DirectoryPoller directoryPoller = new DirectoryPoller();
|
||||
/**
|
||||
* The collection of registered handlers. A new watch service is created for each handler.
|
||||
*/
|
||||
private final Map<WatchedEventHandler, WatchService> activeWatchers = new ConcurrentHashMap<WatchedEventHandler, WatchService>();
|
||||
/**
|
||||
* Synchronization object.
|
||||
*/
|
||||
private final Object lock = new Object();
|
||||
|
||||
static {
|
||||
try {
|
||||
if (System.getProperty(WATCHER_POLLING_INTERVAL_PROPERTY) != null) {
|
||||
pollingDelay = Long.parseLong(System.getProperty(WATCHER_POLLING_INTERVAL_PROPERTY));
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
pollingDelay = DEFAULT_POLLING_DELAY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public DirectoryChangeReactorImpl() {
|
||||
this(LogHelper.getInstance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Injected constructor.
|
||||
*/
|
||||
@Inject
|
||||
public DirectoryChangeReactorImpl(LogHelper theLogger) {
|
||||
log = theLogger;
|
||||
startup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Idempotent initialization.
|
||||
*/
|
||||
private void startup() {
|
||||
|
||||
synchronized (lock) {
|
||||
if (scheduler != null) {
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
scheduler = Executors.newScheduledThreadPool(1);
|
||||
|
||||
scheduleFuture();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cause all watch services to close. This method is idempotent.
|
||||
*/
|
||||
public void shutdown() {
|
||||
synchronized (lock) {
|
||||
|
||||
if (scheduler != null) {
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
for (Iterator<WatchedEventHandler> iterator = activeWatchers.keySet().iterator(); iterator.hasNext(); ) {
|
||||
WatchedEventHandler next = iterator.next();
|
||||
activeWatchers.get(next).close();
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerHandler(WatchedEventHandler<Directory> theEventHandler) {
|
||||
synchronized (lock) {
|
||||
unregisterHandler(theEventHandler);
|
||||
final WatchService watchService = new WatchService(theEventHandler.watchRecursively());
|
||||
watchService.register(theEventHandler.watchTarget());
|
||||
activeWatchers.put(theEventHandler, watchService);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterHandler(WatchedEventHandler<Directory> theEventHandler) {
|
||||
synchronized (lock) {
|
||||
if (activeWatchers.containsKey(theEventHandler)) {
|
||||
activeWatchers.get(theEventHandler).close();
|
||||
activeWatchers.remove(theEventHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the periodic poll.
|
||||
*/
|
||||
private void scheduleFuture() {
|
||||
if (directoryPollerFuture != null
|
||||
&& !directoryPollerFuture.isDone()
|
||||
&& !directoryPollerFuture.isCancelled()) {
|
||||
directoryPollerFuture.cancel(false);
|
||||
}
|
||||
directoryPollerFuture = scheduler.schedule(directoryPoller, pollingDelay, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* This runnable is responsible for checking all watch services for new events.
|
||||
*/
|
||||
private final class DirectoryPoller implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (!activeWatchers.isEmpty()) {
|
||||
for (Iterator<WatchedEventHandler> iterator = activeWatchers.keySet().iterator(); iterator.hasNext(); ) {
|
||||
WatchedEventHandler next = iterator.next();
|
||||
try {
|
||||
WatchService watchService = activeWatchers.get(next);
|
||||
Collection<? extends WatchService.WatchKey> watchKeyCollection = watchService.poll();
|
||||
if (!watchKeyCollection.isEmpty()) {
|
||||
for (WatchService.WatchKey watchKey : watchKeyCollection) {
|
||||
next.handleEvents(watchKey.pollEvents());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("Error notifying handler of watch event. Removing registration.", e);
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
scheduleFuture();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
231
core/src/net/sf/openrocket/util/watcher/DirectoryMonitor.java
Normal file
231
core/src/net/sf/openrocket/util/watcher/DirectoryMonitor.java
Normal file
@ -0,0 +1,231 @@
|
||||
package net.sf.openrocket.util.watcher;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This class checks for changes in directories. Supported events: file creation, subdirectory creation, file modification, file and subdirectory
|
||||
* deletion.
|
||||
* <p/>
|
||||
* Synchronize calls to this class externally to ensure thread safety.
|
||||
*/
|
||||
final class DirectoryMonitor {
|
||||
|
||||
/**
|
||||
* The directories being monitored.
|
||||
*/
|
||||
private Set<Directory> monitored = new HashSet<Directory>();
|
||||
|
||||
/**
|
||||
* Flag that indicates if subdirectories should automatically be monitored when they are created.
|
||||
*/
|
||||
private boolean monitorSubdirectories = false;
|
||||
|
||||
/**
|
||||
* Constructor. Self registration is set to false.
|
||||
*/
|
||||
DirectoryMonitor() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param monitorOnCreate if true auto-monitor new subdirectories
|
||||
*/
|
||||
DirectoryMonitor(boolean monitorOnCreate) {
|
||||
monitorSubdirectories = monitorOnCreate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a directory to be monitored for changes.
|
||||
*
|
||||
* @param dir directory to monitor
|
||||
*/
|
||||
void register(final Directory dir) {
|
||||
if (dir != null && !monitored.contains(dir)) {
|
||||
monitored.add(dir);
|
||||
if (monitorSubdirectories) {
|
||||
recurse(dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void recurse(final Directory dir) {
|
||||
synchronized (dir.getLock()) {
|
||||
String[] list = dir.list();
|
||||
for (String file : list) {
|
||||
final File f = new File(dir.getTarget(), file);
|
||||
if (f.isDirectory()) {
|
||||
register(new Directory(f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear/close and resources being monitored.
|
||||
*/
|
||||
void close() {
|
||||
monitored.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a directory.
|
||||
*
|
||||
* @param dir
|
||||
*/
|
||||
private void unregister(Directory dir) {
|
||||
if (dir != null) {
|
||||
monitored.remove(dir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* The main business logic of this directory monitor.
|
||||
*
|
||||
* @return a WatchEvent instance or null
|
||||
*/
|
||||
Collection<? extends WatchService.WatchKey> check() {
|
||||
Map<Directory, WatchKeyImpl> result = new HashMap<Directory, WatchKeyImpl>();
|
||||
|
||||
for (Directory directory : monitored) {
|
||||
synchronized (directory.getLock()) {
|
||||
if (directory.exists()) {
|
||||
Map<String, WatchedFile> watchedFiles = (Map<String, WatchedFile>) directory.getContents().clone();
|
||||
String[] filesCurrentlyInDirectory = directory.list();
|
||||
for (String s : filesCurrentlyInDirectory) {
|
||||
if (directory.getContents().containsKey(s)) {
|
||||
WatchEvent we = directory.getContents().get(s).check();
|
||||
if (!we.equals(WatchEvent.NO_EVENT)) {
|
||||
add(result, directory, we);
|
||||
}
|
||||
}
|
||||
else {
|
||||
final File f = new File(directory.getTarget(), s);
|
||||
WatchedFile nf = new WatchedFile(f);
|
||||
add(result, directory, nf.createEvent());
|
||||
directory.getContents().put(s, nf);
|
||||
if (f.isDirectory() && monitorSubdirectories) {
|
||||
register(new Directory(f));
|
||||
}
|
||||
}
|
||||
watchedFiles.remove(s);
|
||||
}
|
||||
|
||||
for (String file : watchedFiles.keySet()) {
|
||||
WatchEvent we = watchedFiles.get(file).check();
|
||||
if (!we.equals(WatchEvent.NO_EVENT)) {
|
||||
add(result, directory, we);
|
||||
if (we.kind().equals(WatchEventKind.ENTRY_DELETE)) {
|
||||
directory.getContents().remove(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
add(result, directory, new DirectoryWatchEvent(WatchEventKind.ENTRY_DELETE, directory.getTarget()));
|
||||
unregister(directory);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.values();
|
||||
}
|
||||
|
||||
private void add(Map<Directory, WatchKeyImpl> keyList, Directory dir, WatchEvent we) {
|
||||
WatchKeyImpl key = keyList.get(dir);
|
||||
if (key == null) {
|
||||
key = new WatchKeyImpl(dir);
|
||||
keyList.put(dir, key);
|
||||
}
|
||||
key.add(we);
|
||||
}
|
||||
|
||||
/**
|
||||
* WatchKey impl.
|
||||
*/
|
||||
private class WatchKeyImpl implements WatchService.WatchKey {
|
||||
|
||||
/**
|
||||
* The target.
|
||||
*/
|
||||
private Directory watchKey;
|
||||
|
||||
/**
|
||||
* The list of events.
|
||||
*/
|
||||
private List<WatchEvent<?>> list = new ArrayList<WatchEvent<?>>();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param theKey
|
||||
*/
|
||||
WatchKeyImpl(Directory theKey) {
|
||||
watchKey = theKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
unregister(watchKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WatchEvent<?>> pollEvents() {
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event.
|
||||
*
|
||||
* @param event an event
|
||||
*/
|
||||
void add(WatchEvent<?> event) {
|
||||
list.add(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class that depicts an event upon a directory.
|
||||
*/
|
||||
private class DirectoryWatchEvent implements WatchEvent<File> {
|
||||
|
||||
/**
|
||||
* The type of the event.
|
||||
*/
|
||||
private final Kind<File> type;
|
||||
|
||||
/**
|
||||
* The resource target.
|
||||
*/
|
||||
private final File target;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param theType the kind of the event
|
||||
* @param theTarget the target directory file
|
||||
*/
|
||||
DirectoryWatchEvent(Kind<File> theType, File theTarget) {
|
||||
type = theType;
|
||||
target = theTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File context() {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind<File> kind() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
49
core/src/net/sf/openrocket/util/watcher/WatchEvent.java
Normal file
49
core/src/net/sf/openrocket/util/watcher/WatchEvent.java
Normal file
@ -0,0 +1,49 @@
|
||||
package net.sf.openrocket.util.watcher;
|
||||
|
||||
/**
|
||||
* Mimics the JDK 7 implementation.
|
||||
* <p/>
|
||||
* <T> the type of the context object of the event
|
||||
*/
|
||||
public interface WatchEvent<T> {
|
||||
|
||||
/**
|
||||
* Defines the target of the event.
|
||||
*
|
||||
* @return the entity for which the event was generated
|
||||
*/
|
||||
T context();
|
||||
|
||||
/**
|
||||
* Defines the type of event.
|
||||
*
|
||||
* @return the kind of event
|
||||
*/
|
||||
WatchEvent.Kind<T> kind();
|
||||
|
||||
/**
|
||||
* Defines an API for a kind of event.
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
static interface Kind<T> {
|
||||
String name();
|
||||
|
||||
Class<T> type();
|
||||
}
|
||||
|
||||
/**
|
||||
* A null-object idiom event.
|
||||
*/
|
||||
public static final WatchEvent<Void> NO_EVENT = new WatchEvent<Void>() {
|
||||
@Override
|
||||
public Void context() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind<Void> kind() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
75
core/src/net/sf/openrocket/util/watcher/WatchEventKind.java
Normal file
75
core/src/net/sf/openrocket/util/watcher/WatchEventKind.java
Normal file
@ -0,0 +1,75 @@
|
||||
package net.sf.openrocket.util.watcher;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Mimics the kind of watch event in JDK 7.
|
||||
*/
|
||||
public final class WatchEventKind {
|
||||
|
||||
/**
|
||||
* An entry was created.
|
||||
*/
|
||||
public static final WatchEvent.Kind<File> ENTRY_CREATE = new WatchEvent.Kind<File>() {
|
||||
@Override
|
||||
public String name() {
|
||||
return "ENTRY_CREATE";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<File> type() {
|
||||
return File.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An existing entry was deleted.
|
||||
*/
|
||||
public static final WatchEvent.Kind<File> ENTRY_DELETE = new WatchEvent.Kind<File>() {
|
||||
@Override
|
||||
public String name() {
|
||||
return "ENTRY_DELETE";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<File> type() {
|
||||
return File.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An existing entry was modified.
|
||||
*/
|
||||
public static final WatchEvent.Kind<File> ENTRY_MODIFY = new WatchEvent.Kind<File>() {
|
||||
@Override
|
||||
public String name() {
|
||||
return "ENTRY_MODIFY";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<File> type() {
|
||||
return File.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Disallow instantiation.
|
||||
*/
|
||||
private WatchEventKind() {
|
||||
}
|
||||
}
|
160
core/src/net/sf/openrocket/util/watcher/WatchService.java
Normal file
160
core/src/net/sf/openrocket/util/watcher/WatchService.java
Normal file
@ -0,0 +1,160 @@
|
||||
package net.sf.openrocket.util.watcher;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A service that allows consumers to watch for changes to a directory or directories. This class manages the checking
|
||||
* of state changes for one or more directories. Directories are the primary entity being monitored.
|
||||
* Events detected include the creation of a file, creation of a subdirectory, the modification
|
||||
* of a file, and the deletion of a file or subdirectory.
|
||||
* <p/>
|
||||
* JDK 7 includes a WatchService, upon which this is loosely based. This implementation is JDK 6 compatible.
|
||||
* <p/>
|
||||
* A limitation of this implementation is that it is polling based, not event driven. Also note, that this only monitors directories. If you want
|
||||
* to monitor an individual file, it is recommended that you monitor the directory that the file resides within,
|
||||
* then filter the WatchEvents accordingly.
|
||||
*
|
||||
* Example usage:
|
||||
* <p>
|
||||
* <pre>
|
||||
* WatchService watcher = new WatchService();
|
||||
* watcher.register(new File("/tmp"));
|
||||
* ...
|
||||
* Collection<? extends WatchKey> changed = watcher.poll();
|
||||
* for (Iterator<WatchKey> iterator = co.iterator(); iterator.hasNext(); ) {
|
||||
* WatchKey key = iterator.next();
|
||||
* //Do something with the WatchEvents in the key
|
||||
* }
|
||||
*
|
||||
* </pre>
|
||||
* </p>
|
||||
*/
|
||||
public class WatchService {
|
||||
|
||||
/**
|
||||
* An interface that defines keys of events.
|
||||
*/
|
||||
public static interface WatchKey {
|
||||
/**
|
||||
* Cancel the registration of the directory for which this key relates.
|
||||
*/
|
||||
void cancel();
|
||||
|
||||
/**
|
||||
* Get a list of events detected for this key.
|
||||
*
|
||||
* @return a list, not null, of events
|
||||
*/
|
||||
List<WatchEvent<?>> pollEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* The manager of all directory monitoring.
|
||||
*/
|
||||
private final DirectoryMonitor monitor;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public WatchService() {
|
||||
monitor = new DirectoryMonitor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param watchRecursively if true, directories will be watched recursively
|
||||
*/
|
||||
public WatchService(boolean watchRecursively) {
|
||||
monitor = new DirectoryMonitor(watchRecursively);
|
||||
}
|
||||
|
||||
/**
|
||||
* Polling method to get a collection of keys that indicate events detected upon one or more files within a
|
||||
* monitored directory. Each key represents one directory. The list of events represents changes to one or more
|
||||
* files within that directory. Will not block and return immediately, even if there are no keys ready.
|
||||
*
|
||||
* @return a collection of keys; guaranteed not to be null but may be empty
|
||||
*/
|
||||
public Collection<? extends WatchKey> poll() {
|
||||
return monitor.check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Polling method to get a collection of keys that indicate events detected upon one or more files within a
|
||||
* monitored directory. Each key represents one directory. The list of events represents changes to one or more
|
||||
* files within that directory. Will block for a defined length of time if there are no keys ready.
|
||||
*
|
||||
* @param time the amount of time before the method returns
|
||||
* @param unit the unit of time
|
||||
*
|
||||
* @return a collection of keys; guaranteed not to be null but may be empty
|
||||
*/
|
||||
public Collection<? extends WatchKey> poll(long time, TimeUnit unit) {
|
||||
Collection<? extends WatchKey> result = monitor.check();
|
||||
if (result != null && !result.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(unit.toMillis(time));
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
return monitor.check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocking method to get a collection of keys that indicate events detected upon one or more files within a
|
||||
* monitored directory. Each key represents one directory. The list of events represents changes to one or more
|
||||
* files within that directory.
|
||||
*
|
||||
* @return a collection of keys; guaranteed not to be null but may be empty
|
||||
*/
|
||||
public Collection<? extends WatchKey> take() {
|
||||
long wait = 60000;
|
||||
Collection<? extends WatchKey> result = null;
|
||||
do {
|
||||
result = poll(wait, TimeUnit.MILLISECONDS);
|
||||
} while (result == null || result.isEmpty());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the service and release any resources currently being used.
|
||||
*/
|
||||
public void close() {
|
||||
monitor.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a directory to be watched by this service. The file f must not be null and must refer to a directory
|
||||
* that exists. Registration is idempotent - it can be called multiple times for the same directory with no ill
|
||||
* effect.
|
||||
*
|
||||
* @param f the target directory to watch
|
||||
*
|
||||
* @throws IllegalArgumentException thrown if f is null, does not exist, or is not a directory
|
||||
*/
|
||||
public void register(File f) throws IllegalArgumentException {
|
||||
monitor.register(new Directory(f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a directory to be watched by this service. The file f must not be null and must refer to a directory
|
||||
* that exists. Registration is idempotent - it can be called multiple times for the same directory with no ill
|
||||
* effect.
|
||||
*
|
||||
* @param dir the target directory to watch
|
||||
*
|
||||
* @throws IllegalArgumentException thrown if dir is null
|
||||
*/
|
||||
public void register(Directory dir) throws IllegalArgumentException {
|
||||
if (dir == null) {
|
||||
throw new IllegalArgumentException("The directory may not be null.");
|
||||
}
|
||||
monitor.register(dir);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package net.sf.openrocket.util.watcher;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The public contract that must be implemented by clients wanting to register an interest in, and receive notification of,
|
||||
* changes to a directory or file.
|
||||
*/
|
||||
public interface WatchedEventHandler<W extends WatchedFile> {
|
||||
|
||||
/**
|
||||
* Get the target being watched.
|
||||
*
|
||||
* @return a instance of a watched file
|
||||
*/
|
||||
W watchTarget();
|
||||
|
||||
/**
|
||||
* If the target is a directory, then answer if subdirectories should also be watched for state changes. The watched target is a file, this has no
|
||||
* meaning.
|
||||
*
|
||||
* @return true if directories are to be watched recursively (watch all subdirectories et. al.)
|
||||
*/
|
||||
boolean watchRecursively();
|
||||
|
||||
/**
|
||||
* Callback method. This is invoked by the reactor whenever events are detected upon the target.
|
||||
*
|
||||
* @param theEvents a list of detected events; it's a list because if the target is a directory, potentially many files within the directory were
|
||||
* affected
|
||||
*/
|
||||
void handleEvents(List<WatchEvent<?>> theEvents);
|
||||
}
|
148
core/src/net/sf/openrocket/util/watcher/WatchedFile.java
Normal file
148
core/src/net/sf/openrocket/util/watcher/WatchedFile.java
Normal file
@ -0,0 +1,148 @@
|
||||
package net.sf.openrocket.util.watcher;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Models a file on the filesystem that is being watched for state changes (other than creation).
|
||||
*/
|
||||
class WatchedFile {
|
||||
|
||||
/**
|
||||
* The last timestamp of the file.
|
||||
*/
|
||||
private long timeStamp;
|
||||
|
||||
/**
|
||||
* The file to watch.
|
||||
*/
|
||||
private final File target;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param aFile the file to watch
|
||||
*
|
||||
* @throws IllegalArgumentException thrown if aFile is null
|
||||
*/
|
||||
WatchedFile(File aFile) throws IllegalArgumentException {
|
||||
if (aFile == null) {
|
||||
throw new IllegalArgumentException("The file may not be null.");
|
||||
}
|
||||
target = aFile;
|
||||
timeStamp = target.lastModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a 'create' event.
|
||||
*
|
||||
* @return a watch event indicating the file was created
|
||||
*/
|
||||
WatchEvent<File> createEvent() {
|
||||
return new FileWatchEvent(WatchEventKind.ENTRY_CREATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the watched target.
|
||||
*
|
||||
* @return the file being monitored
|
||||
*/
|
||||
File getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if any changes have been made to the file. This is a 'destructive' read in the sense that it is not
|
||||
* idempotent. The act of checking for changes resets the internal state and subsequent checks will indicate
|
||||
* no changes until the next physical change.
|
||||
*
|
||||
* @return a WatchEvent instance or null if no event
|
||||
*/
|
||||
public final WatchEvent check() {
|
||||
|
||||
if (!target.exists()) {
|
||||
return new FileWatchEvent(WatchEventKind.ENTRY_DELETE);
|
||||
}
|
||||
|
||||
long latest = target.lastModified();
|
||||
|
||||
if (timeStamp != latest) {
|
||||
timeStamp = latest;
|
||||
return new FileWatchEvent(WatchEventKind.ENTRY_MODIFY);
|
||||
}
|
||||
return WatchEvent.NO_EVENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates existence check to the target.
|
||||
*
|
||||
* @return true if exists
|
||||
*/
|
||||
public boolean exists() {
|
||||
return target.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine equivalence to a given object.
|
||||
*
|
||||
* @param o another watched file
|
||||
*
|
||||
* @return true if the underlying file is the same
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof WatchedFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final WatchedFile that = (WatchedFile) o;
|
||||
|
||||
if (!target.equals(that.target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute hash code.
|
||||
*
|
||||
* @return a hash value
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return target.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* A class that depicts events that occur upon a file.
|
||||
*/
|
||||
protected class FileWatchEvent implements WatchEvent<File> {
|
||||
|
||||
/**
|
||||
* The kind of event.
|
||||
*/
|
||||
private Kind<File> type;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param theType the
|
||||
*/
|
||||
FileWatchEvent(Kind<File> theType) {
|
||||
type = theType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File context() {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind<File> kind() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
package net.sf.openrocket.util.watcher;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class DirectoryChangeReactorImplTest {
|
||||
|
||||
@Test
|
||||
public void testRegisterHandler() throws Exception {
|
||||
DirectoryChangeReactorImpl impl = new DirectoryChangeReactorImpl();
|
||||
|
||||
final File tempDir = DirectoryTest.createTempDir();
|
||||
tempDir.setWritable(true);
|
||||
Directory directory = new Directory(tempDir);
|
||||
File f1 = null;
|
||||
File f2 = null;
|
||||
|
||||
File sub = null;
|
||||
Directory subdir;
|
||||
|
||||
try {
|
||||
WatchedEventHandlerImpl testHandler = new WatchedEventHandlerImpl(directory);
|
||||
impl.registerHandler(testHandler);
|
||||
|
||||
String baseName = System.currentTimeMillis() + "--";
|
||||
|
||||
f1 = new File(tempDir.getAbsolutePath(), baseName + "1");
|
||||
f1.createNewFile();
|
||||
long totalSleepTime = 0;
|
||||
WatchEvent.Kind<?> kind = null;
|
||||
while (totalSleepTime < DirectoryChangeReactorImpl.DEFAULT_POLLING_DELAY + 1000) {
|
||||
kind = testHandler.getKind();
|
||||
if (kind != null) {
|
||||
break;
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
totalSleepTime += 1000;
|
||||
}
|
||||
Assert.assertEquals(WatchEventKind.ENTRY_CREATE, kind);
|
||||
Assert.assertEquals(testHandler.eventTarget, f1);
|
||||
|
||||
f1.setLastModified(System.currentTimeMillis() + 10000);
|
||||
totalSleepTime = 0;
|
||||
kind = null;
|
||||
while (totalSleepTime < DirectoryChangeReactorImpl.DEFAULT_POLLING_DELAY + 400) {
|
||||
kind = testHandler.getKind();
|
||||
if (kind != null) {
|
||||
break;
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
totalSleepTime += 1000;
|
||||
}
|
||||
Assert.assertEquals(WatchEventKind.ENTRY_MODIFY, kind);
|
||||
Assert.assertEquals(testHandler.eventTarget, f1);
|
||||
|
||||
f1.delete();
|
||||
totalSleepTime = 0;
|
||||
kind = null;
|
||||
while (totalSleepTime < DirectoryChangeReactorImpl.DEFAULT_POLLING_DELAY + 400) {
|
||||
kind = testHandler.getKind();
|
||||
if (kind != null) {
|
||||
break;
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
totalSleepTime += 1000;
|
||||
}
|
||||
Assert.assertEquals(WatchEventKind.ENTRY_DELETE, kind);
|
||||
Assert.assertEquals(testHandler.eventTarget, f1);
|
||||
|
||||
//test recursive nature of monitoring subdirectories
|
||||
sub = DirectoryTest.createTempDir(tempDir);
|
||||
subdir = new Directory(sub);
|
||||
|
||||
totalSleepTime = 0;
|
||||
kind = null;
|
||||
while (totalSleepTime < DirectoryChangeReactorImpl.DEFAULT_POLLING_DELAY + 400) {
|
||||
kind = testHandler.getKind();
|
||||
if (kind != null) {
|
||||
break;
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
totalSleepTime += 1000;
|
||||
}
|
||||
Assert.assertEquals(WatchEventKind.ENTRY_CREATE, kind);
|
||||
Assert.assertEquals(testHandler.eventTarget, sub);
|
||||
|
||||
f2 = new File(sub.getAbsolutePath(), baseName + "2");
|
||||
f2.createNewFile();
|
||||
totalSleepTime = 0;
|
||||
kind = null;
|
||||
while (totalSleepTime < DirectoryChangeReactorImpl.DEFAULT_POLLING_DELAY + 400) {
|
||||
kind = testHandler.getKind();
|
||||
if (kind != null) {
|
||||
break;
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
totalSleepTime += 1000;
|
||||
}
|
||||
//Eventually, both events will show up, but it's system dependent which one we get first, and if
|
||||
//they both arrive together or on different polling cycles. This could be embellished, but for now
|
||||
//just see if at least one arrives.
|
||||
if (kind.equals(WatchEventKind.ENTRY_CREATE)) {
|
||||
Assert.assertEquals(f2, testHandler.eventTarget);
|
||||
}
|
||||
else if (kind.equals(WatchEventKind.ENTRY_MODIFY)) {
|
||||
Assert.assertEquals(sub, testHandler.eventTarget);
|
||||
}
|
||||
|
||||
}
|
||||
finally {
|
||||
if (f1 != null) {
|
||||
f1.delete();
|
||||
}
|
||||
if (sub != null) {
|
||||
sub.delete();
|
||||
}
|
||||
directory.getTarget().delete();
|
||||
impl.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
static class WatchedEventHandlerImpl implements WatchedEventHandler<Directory> {
|
||||
Directory file = null;
|
||||
Object eventTarget = null;
|
||||
|
||||
WatchEvent.Kind<?> kind = null;
|
||||
|
||||
WatchedEventHandlerImpl(final Directory theFile) {
|
||||
file = theFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Directory watchTarget() {
|
||||
return file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean watchRecursively() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public WatchEvent.Kind<?> getKind() {
|
||||
WatchEvent.Kind<?> tmp = kind;
|
||||
kind = null;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleEvents(final List<WatchEvent<?>> theEvents) {
|
||||
for (int i = 0; i < theEvents.size(); i++) {
|
||||
WatchEvent<?> watchEvent = theEvents.get(i);
|
||||
kind = watchEvent.kind();
|
||||
eventTarget = watchEvent.context();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package net.sf.openrocket.util.watcher;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class DirectoryMonitorTest {
|
||||
|
||||
@Test
|
||||
public void testCheck() throws Exception {
|
||||
final File tempDir = DirectoryTest.createTempDir();
|
||||
tempDir.setWritable(true);
|
||||
Directory directory = new Directory(tempDir);
|
||||
File f1 = null;
|
||||
File f2 = null;
|
||||
File f3 = null;
|
||||
try {
|
||||
|
||||
DirectoryMonitor monitor = new DirectoryMonitor();
|
||||
monitor.register(directory);
|
||||
|
||||
String baseName = System.currentTimeMillis() + "--";
|
||||
|
||||
f1 = new File(tempDir.getAbsolutePath(), baseName + "1");
|
||||
f1.createNewFile();
|
||||
f2 = new File(tempDir.getAbsolutePath(), baseName + "2");
|
||||
f2.createNewFile();
|
||||
f3 = new File(tempDir.getAbsolutePath(), baseName + "3");
|
||||
f3.createNewFile();
|
||||
|
||||
Collection<? extends WatchService.WatchKey> keys = monitor.check();
|
||||
Assert.assertEquals(1, keys.size());
|
||||
WatchService.WatchKey wk = keys.iterator().next();
|
||||
List<WatchEvent<?>> events = wk.pollEvents();
|
||||
|
||||
Assert.assertEquals(3, events.size());
|
||||
for (int i = 0; i < events.size(); i++) {
|
||||
WatchEvent<?> watchEvent = events.get(i);
|
||||
if (watchEvent.context().equals(f1)) {
|
||||
Assert.assertEquals(WatchEventKind.ENTRY_CREATE, watchEvent.kind());
|
||||
}
|
||||
else if (watchEvent.context().equals(f2)) {
|
||||
Assert.assertEquals(WatchEventKind.ENTRY_CREATE, watchEvent.kind());
|
||||
}
|
||||
else if (watchEvent.context().equals(f3)) {
|
||||
Assert.assertEquals(WatchEventKind.ENTRY_CREATE, watchEvent.kind());
|
||||
}
|
||||
else {
|
||||
System.err.println(watchEvent.context().toString());
|
||||
Assert.fail("Unknown target file.");
|
||||
}
|
||||
}
|
||||
|
||||
f1.setLastModified(System.currentTimeMillis() + 10007);
|
||||
f1.setReadable(true);
|
||||
Thread.sleep(1000);
|
||||
keys = monitor.check();
|
||||
Assert.assertEquals(1, keys.size());
|
||||
WatchService.WatchKey watchEvent = keys.iterator().next();
|
||||
Assert.assertEquals(1, watchEvent.pollEvents().size());
|
||||
Assert.assertEquals(f1, watchEvent.pollEvents().get(0).context());
|
||||
Assert.assertEquals(WatchEventKind.ENTRY_MODIFY, watchEvent.pollEvents().get(0).kind());
|
||||
}
|
||||
finally {
|
||||
if (f1 != null) {
|
||||
f1.delete();
|
||||
}
|
||||
if (f2 != null) {
|
||||
f2.delete();
|
||||
}
|
||||
if (f3 != null) {
|
||||
f3.delete();
|
||||
}
|
||||
directory.getTarget().delete();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
177
core/test/net/sf/openrocket/util/watcher/DirectoryTest.java
Normal file
177
core/test/net/sf/openrocket/util/watcher/DirectoryTest.java
Normal file
@ -0,0 +1,177 @@
|
||||
package net.sf.openrocket.util.watcher;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class DirectoryTest {
|
||||
|
||||
@Test
|
||||
public void testConstructor() throws Exception {
|
||||
try {
|
||||
new Directory(null);
|
||||
Assert.fail();
|
||||
}
|
||||
catch (IllegalArgumentException iae) {
|
||||
//success
|
||||
}
|
||||
try {
|
||||
new Directory(new File("foo"));
|
||||
Assert.fail();
|
||||
}
|
||||
catch (IllegalArgumentException iae) {
|
||||
//success
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSize() throws Exception {
|
||||
final File tempDir = createTempDir();
|
||||
tempDir.setWritable(true);
|
||||
Directory directory = new Directory(tempDir);
|
||||
File f1 = null;
|
||||
File f2 = null;
|
||||
File f3 = null;
|
||||
try {
|
||||
Assert.assertTrue(directory.exists());
|
||||
Assert.assertEquals(0, directory.size());
|
||||
|
||||
String baseName = System.currentTimeMillis() + "--";
|
||||
|
||||
f1 = new File(tempDir.getAbsolutePath(), baseName + "1");
|
||||
f1.createNewFile();
|
||||
f2 = new File(tempDir.getAbsolutePath(), baseName + "2");
|
||||
f2.createNewFile();
|
||||
f3 = new File(tempDir.getAbsolutePath(), baseName + "3");
|
||||
f3.createNewFile();
|
||||
|
||||
Assert.assertEquals(3, directory.size());
|
||||
}
|
||||
finally {
|
||||
if (f1 != null) {
|
||||
f1.delete();
|
||||
}
|
||||
if (f2 != null) {
|
||||
f2.delete();
|
||||
}
|
||||
if (f3 != null) {
|
||||
f3.delete();
|
||||
}
|
||||
directory.getTarget().delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testList() throws Exception {
|
||||
final File tempDir = createTempDir();
|
||||
tempDir.setWritable(true);
|
||||
Directory directory = new Directory(tempDir);
|
||||
File f1 = null;
|
||||
File f2 = null;
|
||||
File f3 = null;
|
||||
try {
|
||||
Assert.assertTrue(directory.exists());
|
||||
Assert.assertEquals(0, directory.size());
|
||||
|
||||
String baseName = System.currentTimeMillis() + "--";
|
||||
|
||||
f1 = new File(tempDir.getAbsolutePath(), baseName + "1");
|
||||
f1.createNewFile();
|
||||
f2 = new File(tempDir.getAbsolutePath(), baseName + "2");
|
||||
f2.createNewFile();
|
||||
f3 = new File(tempDir.getAbsolutePath(), baseName + "3");
|
||||
f3.createNewFile();
|
||||
|
||||
String[] files = directory.list();
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
String file = files[i];
|
||||
if (file.endsWith("1")) {
|
||||
Assert.assertEquals(baseName + "1", file);
|
||||
}
|
||||
else if (file.endsWith("2")) {
|
||||
Assert.assertEquals(baseName + "2", file);
|
||||
}
|
||||
else if (file.endsWith("3")) {
|
||||
Assert.assertEquals(baseName + "3", file);
|
||||
}
|
||||
else {
|
||||
Assert.fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (f1 != null) {
|
||||
f1.delete();
|
||||
}
|
||||
if (f2 != null) {
|
||||
f2.delete();
|
||||
}
|
||||
if (f3 != null) {
|
||||
f3.delete();
|
||||
}
|
||||
directory.getTarget().delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContents() throws Exception {
|
||||
final File tempDir = createTempDir();
|
||||
tempDir.setWritable(true);
|
||||
Directory directory = new Directory(tempDir);
|
||||
File f1 = null;
|
||||
File f2 = null;
|
||||
File f3 = null;
|
||||
try {
|
||||
String baseName = System.currentTimeMillis() + "--";
|
||||
|
||||
f1 = new File(tempDir.getAbsolutePath(), baseName + "1");
|
||||
f1.createNewFile();
|
||||
f2 = new File(tempDir.getAbsolutePath(), baseName + "2");
|
||||
f2.createNewFile();
|
||||
f3 = new File(tempDir.getAbsolutePath(), baseName + "3");
|
||||
f3.createNewFile();
|
||||
|
||||
Assert.assertEquals(0, directory.getContents().size());
|
||||
|
||||
//Contents is initialized at the time Directory is created. Since we had to create it for the test,
|
||||
//we need a second Directory instance.
|
||||
Directory directory1 = new Directory(tempDir);
|
||||
Assert.assertEquals(3, directory1.getContents().size());
|
||||
}
|
||||
finally {
|
||||
if (f1 != null) {
|
||||
f1.delete();
|
||||
}
|
||||
if (f2 != null) {
|
||||
f2.delete();
|
||||
}
|
||||
if (f3 != null) {
|
||||
f3.delete();
|
||||
}
|
||||
directory.getTarget().delete();
|
||||
}
|
||||
}
|
||||
|
||||
//Borrowed from Google's Guava.
|
||||
public static File createTempDir() {
|
||||
File baseDir = new File(System.getProperty("java.io.tmpdir"));
|
||||
return createTempDir(baseDir);
|
||||
}
|
||||
|
||||
public static File createTempDir(File parent) {
|
||||
String baseName = System.currentTimeMillis() + "-";
|
||||
|
||||
for (int counter = 0; counter < 2; counter++) {
|
||||
File tempDir = new File(parent, baseName + counter);
|
||||
if (tempDir.mkdir()) {
|
||||
return tempDir;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Failed to create directory within "
|
||||
+ 2 + " attempts (tried "
|
||||
+ baseName + "0 to " + baseName + (2 - 1) + ')');
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package net.sf.openrocket.util.watcher;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class WatchedFileTest {
|
||||
|
||||
@Test
|
||||
public void testConstructor() throws Exception {
|
||||
try {
|
||||
new WatchedFile(null);
|
||||
Assert.fail();
|
||||
}
|
||||
catch (IllegalArgumentException iae) {
|
||||
//success
|
||||
}
|
||||
|
||||
final File blah = new File("blah");
|
||||
WatchedFile wf = new WatchedFile(blah);
|
||||
Assert.assertEquals(blah, wf.getTarget());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateEvent() throws Exception {
|
||||
final File blah = new File("blah");
|
||||
WatchedFile wf = new WatchedFile(blah);
|
||||
Assert.assertEquals(blah, wf.createEvent().context());
|
||||
Assert.assertEquals(WatchEventKind.ENTRY_CREATE, wf.createEvent().kind());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExists() throws Exception {
|
||||
final File blah = new File("blah");
|
||||
WatchedFile wf = new WatchedFile(blah);
|
||||
Assert.assertFalse(wf.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheck() throws Exception {
|
||||
final File blah = new File("blah");
|
||||
WatchedFile wf = new WatchedFile(blah);
|
||||
|
||||
WatchEvent check = wf.check();
|
||||
Assert.assertEquals(WatchEventKind.ENTRY_DELETE, check.kind());
|
||||
|
||||
File f = File.createTempFile("tmp", "tmp");
|
||||
wf = new WatchedFile(f);
|
||||
|
||||
check = wf.check();
|
||||
Assert.assertEquals(WatchEvent.NO_EVENT, check);
|
||||
|
||||
f.setLastModified(System.currentTimeMillis() - 60000);
|
||||
check = wf.check();
|
||||
Assert.assertEquals(WatchEventKind.ENTRY_MODIFY, check.kind());
|
||||
Assert.assertEquals(f, check.context());
|
||||
|
||||
//Check for reset of state
|
||||
check = wf.check();
|
||||
Assert.assertEquals(WatchEvent.NO_EVENT, check);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals() throws Exception {
|
||||
final File blah = new File("blah");
|
||||
final File blech = new File("blech");
|
||||
WatchedFile wf1 = new WatchedFile(blah);
|
||||
WatchedFile wf2 = new WatchedFile(blah);
|
||||
WatchedFile wf3 = new WatchedFile(blech);
|
||||
|
||||
Assert.assertEquals(wf1, wf1);
|
||||
Assert.assertEquals(wf1, wf2);
|
||||
Assert.assertFalse(wf1.equals(wf3));
|
||||
Assert.assertFalse(wf1.equals(null));
|
||||
Assert.assertFalse(wf1.equals(new Object()));
|
||||
|
||||
Assert.assertEquals(wf1.hashCode(), wf2.hashCode());
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user