package net.sourceforge.javautil.common.io;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.regex.Pattern;
import net.sourceforge.javautil.common.ThreadUtil;
import net.sourceforge.javautil.common.api.IWrapper;
import net.sourceforge.javautil.common.context.Context;
import net.sourceforge.javautil.common.io.IVirtualArtifactWatcherListener.VirtualArtifactWatcherEvent;
import net.sourceforge.javautil.common.io.IVirtualArtifactWatcherListener.VirtualArtifactWatcherEvent.Type;
import net.sourceforge.javautil.common.proxy.CollectionTargetProxy;
import net.sourceforge.javautil.common.shutdown.IShutdownHook;
/**
* A service that will watch for changes to specified {@link IVirtualArtifact}'s and can be ran inside a thread.
*
* @author elponderador
* @author $Author$
* @version $Id$
*/
public class VirtualArtifactWatcher extends Context implements Runnable, IShutdownHook {
/**
* @return The current watcher for this thread, or null if none have been set
*/
public static VirtualArtifactWatcher getInstance () { return Context.get(VirtualArtifactWatcher.class); }
protected boolean running = false;
protected int cycleSleep = 2000;
protected int interSleep = 20;
protected long last = -1;
protected final String name;
protected final Map<String, VirtualArtifactWatchable> artifacts = Collections.synchronizedMap(new LinkedHashMap<String, VirtualArtifactWatchable>());
protected final Set<IVirtualArtifactWatcherListener> listeners = new CopyOnWriteArraySet<IVirtualArtifactWatcherListener>();
protected final IVirtualArtifactWatcherListener propogator = CollectionTargetProxy.create(
Thread.currentThread().getContextClassLoader(), IVirtualArtifactWatcherListener.class, listeners, true
);
public VirtualArtifactWatcher(String name) { this.name = name; }
/**
* @param listener The listener to recieve filtered events
* @param paths The paths to be matched on and for which events will be propagated for the listener
*/
public void addListener (IVirtualArtifactWatcherListener listener, IVirtualPath... paths) {
this.listeners.add(new FilteredWatcher(listener, paths));
}
/**
* @param listener The listener to receive filtered events
* @param pattern The pattern to filter which events will be propagated to the listener
*/
public void addListener (IVirtualArtifactWatcherListener listener, String pattern) {
this.listeners.add(new PatternWatcher(Pattern.compile(pattern), listener));
}
/**
* @param listener The listener who will receive {@link VirtualArtifactWatcherEvent}'s from this watcher
*/
public void addListener (IVirtualArtifactWatcherListener listener) {
this.listeners.add(listener);
}
/**
* @param listener The listener who will no longer receive {@link VirtualArtifactWatcherEvent}'s from this watcher
*/
public void removeListener (IVirtualArtifactWatcherListener listener) {
if (!this.listeners.remove(listener)) {
IVirtualArtifactWatcherListener wrapper = null;
for (IVirtualArtifactWatcherListener l : listeners) {
if (l instanceof IWrapper && ((IVirtualArtifactWatcherListener)((IWrapper)l).getWrapped()) == listener) {
wrapper = l; break;
}
}
if (wrapper != null) this.removeListener(wrapper);
}
}
/**
* @param watchable The watchable to add to the list of watched artifacts
*/
public synchronized void watch (VirtualArtifactWatchable watchable) { this.artifacts.put(watchable.getArtifact().getPath().toString("/"), watchable); }
/**
* @param directory The directory to watch
* @param recursive True if all the artifacts recursively should be watched, or if just the artifacts in the root of the directory
*/
public synchronized void watch (IVirtualDirectory directory, boolean recursive) {
this.watch(new VirtualDirectoryWatched(directory, recursive));
}
/**
* @param artifact The artifact to stop watching
*/
public synchronized void stopWatching (VirtualArtifactWatchable watched) {
this.artifacts.remove(watched.getArtifact().getPath().toString("/"));
}
/**
* The higher this setting is the slower the watcher will be in responding to events.
*
* @return The time in milliseconds that the watcher will sleep between full cycle checks on the registered artifacts
*/
public int getCycleSleep() { return cycleSleep; }
public void setCycleSleep(int cycleSleep) { this.cycleSleep = cycleSleep; }
/**
* @return The time in milliseconds that the watcher will sleep between each {@link IVirtualArtifact} check
*/
public int getInterSleep() { return interSleep; }
public void setInterSleep(int interSleep) { this.interSleep = interSleep; }
public void run() {
this.running = true;
this.last = System.currentTimeMillis();
while (this.running) {
this.check(interSleep);
ThreadUtil.sleep(cycleSleep);
}
}
/**
* This will do a single check on the resources.
*
* @param changes The array to utilize for storing changes
* @param interSleep The time in milliseconds to sleep between each artifact check
*/
public void check (int interSleep) {
List<VirtualArtifactChange> changes = null;
for (String path : this.artifacts.keySet()) {
changes = this.artifacts.get(path).getArtifactChangeList(this.last);
if (changes != null) {
for (int c=0; c<changes.size(); c++) {
VirtualArtifactChange change = changes.get(c);
if (change.getTimestamp() > last) last = change.getTimestamp();
this.propogator.handle(new VirtualArtifactWatcherEvent(this, change));
}
}
ThreadUtil.sleep(interSleep);
}
}
/**
* Start the watcher in another thread
*/
public Thread start () {
if (this.running) throw new IllegalStateException("This watcher is already running");
Thread thread = new Thread(this, "Watcher - " + name);
thread.setDaemon(true);
thread.start();
return thread;
}
/**
* Stop the watcher from running
*/
public void stop () {
if (!this.running) throw new IllegalStateException("This watcher is not running");
this.running = false;
}
public void shutdown() { if (this.running) this.stop(); }
/**
* This will allow a list of {@link IVirtualPath}'s to be alerted about.
*
* @author elponderador
* @author $Author$
* @version $Id$
*/
public class FilteredWatcher implements IVirtualArtifactWatcherListener, IWrapper<IVirtualArtifactWatcherListener> {
protected final Set<IVirtualPath> paths;
protected final IVirtualArtifactWatcherListener original;
public FilteredWatcher(IVirtualArtifactWatcherListener original, IVirtualPath... paths) {
this.paths = new LinkedHashSet<IVirtualPath>();
Collections.addAll(this.paths, paths);
this.original = original;
}
public IVirtualArtifactWatcherListener getWrapped() {
return this.original;
}
public void handle(VirtualArtifactWatcherEvent evt) {
if (paths.contains(evt.getChange().getPath())) {
this.original.handle(evt);
}
}
}
/**
* An implementation using a {@link Pattern} to filter event's handled by a target listener.
*
* @author elponderador
* @author $Author$
* @version $Id$
*/
public class PatternWatcher implements IVirtualArtifactWatcherListener, IWrapper<IVirtualArtifactWatcherListener> {
protected final Pattern pattern;
protected final IVirtualArtifactWatcherListener original;
public PatternWatcher(Pattern pattern, IVirtualArtifactWatcherListener original) {
this.pattern = pattern;
this.original = original;
}
public void handle(VirtualArtifactWatcherEvent evt) {
if (pattern.matcher(evt.getChange().getPath().toString("/")).matches()) {
this.original.handle(evt);
}
}
public IVirtualArtifactWatcherListener getWrapped() {
return original;
}
}
/**
* The base interface for implementations that can generate a list of changed artifacts.
*
* @author elponderador
* @author $Author$
* @version $Id$
*/
public static interface VirtualArtifactWatchable<A extends IVirtualArtifact> {
/**
* @return The artifact being watched
*/
A getArtifact ();
/**
* @param since The time stamp from which changes should be detected
*
* @return A list of changes that have taken place since the time stamp or null if no changes have taken place
*/
List<VirtualArtifactChange> getArtifactChangeList (long since);
}
/**
* This represents a change to a {@link IVirtualArtifact}.
*
* @author elponderador
* @author $Author$
* @version $Id$
*/
public static class VirtualArtifactChange {
protected final IVirtualArtifact owner;
protected final IVirtualPath path;
protected final Type type;
protected final long timestamp;
public VirtualArtifactChange(IVirtualArtifact owner, IVirtualPath path, Type type, long timestamp) {
this.owner = owner;
this.path = path;
this.type = type;
this.timestamp = timestamp;
}
/**
* @return A time stamp reflecting when the change took place if known, otherwise when it was detected
*/
public long getTimestamp() { return timestamp; }
/**
* @return The artifact that generated the change, which may or may not be the same as referred to by the path
*/
public IVirtualArtifact getOwner() { return owner; }
/**
* @return The path that is related to the change
*/
public IVirtualPath getPath () { return path; }
/**
* @return The type of change
*/
public Type getType() { return type; }
}
}