001: package dalma.container;
002:
003: import java.io.File;
004: import java.lang.ref.WeakReference;
005: import java.util.HashMap;
006: import java.util.HashSet;
007: import java.util.Iterator;
008: import java.util.Map;
009: import java.util.Set;
010: import java.util.logging.Logger;
011:
012: /**
013: * Periodically checks for an update to a file in a specific directory and invokes a callback.
014: *
015: * @author Kohsuke Kawaguchi
016: */
017: abstract class FileChangeMonitor {
018: private static final Logger logger = Logger
019: .getLogger(FileChangeMonitor.class.getName());
020:
021: private static final Set<WeakReference<FileChangeMonitor>> monitors = new HashSet<WeakReference<FileChangeMonitor>>();
022:
023: /**
024: * The singleton thread that does all the monitoring.
025: */
026: private static final Thread monitorThread;
027:
028: public static void add(FileChangeMonitor job) {
029: synchronized (monitors) {
030: monitors.add(new WeakReference<FileChangeMonitor>(job));
031: monitors.notify();
032: }
033: }
034:
035: /**
036: * Directory to monitor.
037: */
038: private final File dir;
039:
040: /**
041: * Files in the {@link #dir} known currently, keyed by their names.
042: */
043: private Map<String, Entry> files;
044:
045: private class Entry {
046: /**
047: * The {@link File} instance that this object represents.
048: */
049: private final File file;
050:
051: /**
052: * The last-modified timestamp known to us.
053: */
054: private long timestamp;
055:
056: public Entry(File file) {
057: this .file = file;
058: timestamp = file.lastModified();
059: }
060: }
061:
062: public FileChangeMonitor(File dir) {
063: this .dir = dir;
064:
065: if (!dir.exists())
066: throw new IllegalArgumentException("No such directory "
067: + dir);
068:
069: // fill in the initial values
070: files = new HashMap<String, Entry>();
071: for (File f : dir.listFiles()) {
072: files.put(f.getName(), new Entry(f));
073: }
074:
075: synchronized (monitors) {
076: monitors.add(new WeakReference<FileChangeMonitor>(this ));
077: }
078: }
079:
080: /**
081: * Cancels the monitor.
082: *
083: * The monitor is also implicitly cancelled when a {@link FileChangeMonitor} is
084: * garbage-collected.
085: */
086: public void cancel() {
087: synchronized (monitors) {
088: Iterator<WeakReference<FileChangeMonitor>> itr = monitors
089: .iterator();
090: while (itr.hasNext()) {
091: WeakReference<FileChangeMonitor> wr = itr.next();
092: if (wr.get() == this ) {
093: itr.remove();
094: return;
095: }
096: }
097: }
098: throw new IllegalStateException("already cancelled");
099: }
100:
101: /**
102: * Invoked when a file/directory is changed.
103: */
104: protected abstract void onUpdated(File file);
105:
106: /**
107: * Invoked when a new file/directory is added.
108: */
109: protected abstract void onAdded(File file);
110:
111: /**
112: * Invoked when a file/directory is removed.
113: */
114: protected abstract void onDeleted(File file);
115:
116: /**
117: * Checks if there's an update, and invokes callbacks if there is.
118: *
119: * @return false
120: * if the monitoring should terminate.
121: */
122: private boolean check() {
123: Map<String, Entry> newMap = new HashMap<String, Entry>();
124:
125: if (!dir.isDirectory())
126: return false; // directory itself no longer exists
127:
128: for (File f : dir.listFiles()) {
129: String name = f.getName();
130: Entry entry = files.get(name);
131: if (entry == null) {
132: // new file
133: newMap.put(name, new Entry(f));
134: onAdded(f);
135: } else {
136: long t = f.lastModified();
137: if (entry.timestamp < t) {
138: onUpdated(f);
139: entry.timestamp = t;
140: }
141: newMap.put(name, entry);
142: files.remove(name);
143: }
144: }
145:
146: // anything left in 'files' are deleted files
147: for (Entry e : files.values()) {
148: onDeleted(e.file);
149: }
150: files = newMap;
151:
152: return true;
153: }
154:
155: static {
156: monitorThread = new Thread() {
157: public void run() {
158: while (true) {
159: try {
160: Thread.sleep(3000); // every once in 3 seconds
161:
162: synchronized (monitors) {
163: Iterator<WeakReference<FileChangeMonitor>> itr = monitors
164: .iterator();
165: while (itr.hasNext()) {
166: WeakReference<FileChangeMonitor> wr = itr
167: .next();
168: FileChangeMonitor job = wr.get();
169:
170: if (job == null) {
171: itr.remove();
172: continue;
173: }
174:
175: try {
176: if (!job.check())
177: itr.remove();
178: } catch (Throwable e) {
179: e.printStackTrace();
180: itr.remove();
181: }
182: }
183:
184: while (monitors.isEmpty()) {
185: monitors.wait();
186: }
187: }
188: } catch (Throwable t) {
189: t.printStackTrace();
190: // don't let the thread die
191: }
192: }
193: }
194: };
195: monitorThread.setDaemon(true);
196: monitorThread.start();
197: }
198: }
|