KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > util > FileWatcher


1 /*
2
3 This software is OSI Certified Open Source Software.
4 OSI Certified is a certification mark of the Open Source Initiative.
5
6 The license (Mozilla version 1.0) can be read at the MMBase site.
7 See http://www.MMBase.org/license
8
9 */

10
11 package org.mmbase.util;
12
13 import java.io.File JavaDoc;
14 import java.util.*;
15 import org.mmbase.util.logging.*;
16 import org.mmbase.util.xml.UtilReader;
17 import edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArraySet;
18
19 /**
20  * Original javadoc.
21
22  * This will run as a thread after it has been started.
23  * It will check every interval if one of it's files has been changed.
24  * When one of them has been changed, the onChange method will be called, with the file that
25  * was changed. After that the thread will stop.
26  * To stop a running thread, call the method exit();
27  *
28  * Example:
29  * <code style="white-space:pre;">
30  * class FooFileWatcher extends FileWatcher {
31  *
32  * public FooFileWatcher() {
33  * super(true); // true: keep reading.
34  * }
35  *
36  * public void onChange(File file) {
37  * System.out.println(file.getAbsolutePath());
38  * }
39  * }
40  *
41  * // create new instance
42  * FooFileWatcher watcher = new FooFileWatcher();
43  * // set inteval
44  * watcher.setDelay(10 * 1000);
45  * watcher.add(new File("/tmp/foo.txt"));
46  * watcher.start();
47  * watcher.add(new File("/tmp/foo.txt"));
48  * wait(100*1000);
49  * watcher.exit();
50  * </code>
51  *
52  * Thanks to contributions by Mathias Bogaert.
53  * Licence was changed from apache 1.1 to Mozilla.
54  *
55  * MMBase javadoc
56  *
57  * This code was originally borrowed from the log4j project (as can still be seen from the authors),
58  * it was however quite heavily adapted. You are probably better of using a {@link ResourceWatcher}
59  * (since MMBase 1.8), because that does not watch only files. Its implementation does of course use
60  * FileWatcher, for the 'file' part of the watching.
61  *
62  * @author Ceki G&uuml;lc&uuml;
63  * @author Eduard Witteveen
64  * @author Michiel Meeuwissen
65  * @since MMBase-1.4
66  * @version $Id: FileWatcher.java,v 1.38 2006/04/19 21:10:58 michiel Exp $
67  */

68 public abstract class FileWatcher {
69     private static Logger log = Logging.getLoggerInstance(FileWatcher.class);
70
71     private static FileWatcherRunner fileWatchers = new FileWatcherRunner();
72     static {
73         fileWatchers.start();
74     }
75
76     /**
77      * The default delay between every file modification check, set to 60
78      * seconds.
79      */

80     static final public long DEFAULT_DELAY = 60000;
81
82     /**
83      * The one thread doing al the work also needs a delay.
84      */

85     static public long THREAD_DELAY = 10000;
86
87
88
89     private static Map props;
90
91
92     /**
93      * @since MMBase-1.8
94      */

95     static private Runnable JavaDoc watcher = new Runnable JavaDoc() {
96             public void run() {
97                 try {
98                     String JavaDoc delay = (String JavaDoc) props.get("delay");
99                     if (delay != null) {
100                         THREAD_DELAY = Integer.parseInt(delay);
101                         log.service("Set thread delay time to " + THREAD_DELAY);
102                     }
103                 } catch (Exception JavaDoc e) {
104                     log.error(e);
105                 }
106             }
107         };
108
109
110     static {
111         props = new UtilReader("resourcewatcher.xml", watcher).getProperties();
112         watcher.run();
113     }
114
115     /**
116      * @since MMBase-1.8
117      */

118     public static void shutdown() {
119         fileWatchers.run = false;
120         fileWatchers.interrupt();
121         log.service("Shut down file watcher thread");
122     }
123
124     /**
125      * The delay to observe between every check. By default set {@link
126      * #DEFAULT_DELAY}.
127      */

128     private long delay = DEFAULT_DELAY;
129
130     private Set files = new LinkedHashSet();
131     private Set fileSet = new FileSet(); // (automaticly) wraps 'files'.
132
private Set removeFiles = new HashSet();
133     private boolean stop = false;
134     private boolean continueAfterChange = false;
135     private long lastCheck = 0;
136
137     protected FileWatcher() {
138         this(true);
139     }
140
141     protected FileWatcher(boolean c) {
142         // make it end when parent treath ends..
143
continueAfterChange = c;
144     }
145
146     public void start() {
147         fileWatchers.add(this);
148     }
149     /**
150      * Put here the stuff that has to be executed, when a file has been changed.
151      * @param file The file that was changed..
152      */

153     abstract public void onChange(File JavaDoc file);
154
155     /**
156      * Set the delay to observe between each check of the file changes.
157      */

158     public void setDelay(long delay) {
159         this.delay = delay;
160         if (delay < THREAD_DELAY) {
161             log.service("Delay of " + this + " (" + delay + " ms) is smaller than the delay of the watching thread. Will not watch more often then once per " + THREAD_DELAY + " ms.");
162         }
163     }
164
165     /**
166      * Add's a file to be checked...
167      * @param file The file which has to be monitored..
168      * @throws RuntimeException If file is null
169      */

170     public void add(File JavaDoc file) {
171         FileEntry fe = new FileEntry(file);
172         synchronized (this) {
173             files.add(fe);
174             if (removeFiles.remove(fe)) {
175                 log.service("Canceling removal from filewatcher " + fe);
176             }
177         }
178     }
179
180     /**
181      * Wether the file is being watched or not.
182      * @param file the file to be checked.
183      * @since MMBase-1.6
184      */

185     public boolean contains(File JavaDoc file) {
186         return files.contains(new FileEntry(file));
187     }
188
189     /**
190      * Remove file from the watch-list
191      */

192     public void remove(File JavaDoc file) {
193         synchronized (this) {
194             removeFiles.add(file);
195         }
196     }
197
198     /**
199      * Returns a (modifiable) Set of all files (File object) of this FileWatcher. If you change it, you change the
200      * FileWatcher. The order of the Set is predictable (backed by a {@link java.util.LinkedHashSet}).
201      *
202      * @since MMBase-1.8.
203      */

204     public Set getFiles() {
205         return fileSet;
206     }
207
208     /**
209      * Removes all files, this watcher will end up watching nothing.
210      * @since MMBase-1.8
211      */

212     public void clear() {
213         fileSet.clear();
214     }
215
216     /**
217      * Stops watching.
218      */

219     public void exit() {
220         synchronized (this) {
221             stop = true;
222         }
223     }
224
225     /**
226      * Shows the 'contents' of the filewatcher. It shows a list of files/last modified timestamps.
227      */

228     public String JavaDoc toString() {
229         return files.toString();
230     }
231
232     /**
233      * Looks if a file has changed. If it is, the 'onChance' for this file is called.
234      *
235      * Before doing so, it removes the files which were requested for
236      * removal from the watchlist.
237      *
238      */

239     private boolean changed() {
240         synchronized (this) {
241             Iterator i = files.iterator();
242             while (i.hasNext()) {
243                 FileEntry fe = (FileEntry)i.next();
244                 if (fe.changed()) {
245                     log.debug("the file :" + fe.getFile().getAbsolutePath() + " has changed.");
246                     try {
247                         onChange(fe.getFile());
248                     } catch (Throwable JavaDoc e) {
249                         log.warn("onChange of " + fe.getFile().getName() + " lead to exception:");
250                         log.warn(Logging.stackTrace(e));
251                     }
252                     if (continueAfterChange) {
253                         fe.updated(); // onChange was called now, it can be marked up-to-date again
254
} else { //
255
return true; // stop watching
256
}
257                 }
258             }
259         }
260         return false;
261     }
262
263     private void removeFiles() {
264         synchronized (this) {
265             // remove files if necessary
266
Iterator ri = removeFiles.iterator();
267             while (ri.hasNext()) {
268                 File JavaDoc f = (File JavaDoc)ri.next();
269                 FileEntry found = null;
270
271                 // search the file
272
Iterator i = files.iterator();
273                 while (i.hasNext()) {
274                     FileEntry fe = (FileEntry)i.next();
275                     if (fe.getFile().equals(f)) {
276                         if (log.isDebugEnabled()) {
277                             log.debug("removing file[" + fe.getFile().getName() + "]");
278                         }
279                         found = fe;
280                         break;
281                     }
282                 }
283                 if (found != null) {
284                     files.remove(found);
285                     log.service("Removed " + found + " from watchlist");
286                 }
287             }
288             removeFiles.clear();
289         }
290     }
291
292     /**
293      * Looks if we have to stop
294      */

295     private boolean mustStop() {
296         synchronized (this) {
297             return stop;
298         }
299     }
300
301     public boolean equals(Object JavaDoc o) {
302         if (o == this) return true;
303         if (o == null) return false;
304         if (getClass().equals(o.getClass())) {
305             FileWatcher f = (FileWatcher)o;
306             return this.files.equals(f.files);
307         }
308         return false;
309     }
310
311
312     /**
313      * @see java.lang.Object#hashCode()
314      */

315     public int hashCode() {
316         return files == null ? 0 : files.hashCode();
317     }
318
319     /**
320      * @javadoc
321      */

322     public static void main(String JavaDoc[] args) {
323
324         // start some filewatchers
325
for (int i = 0; i < 100; i++) {
326             FileWatcher w = new TestFileWatcher();
327             // add some files
328
for (int j = 0; j < 4; j++) {
329                 try {
330                     w.add(File.createTempFile("filewatchertestfile", ".txt"));
331                 } catch (Exception JavaDoc e) {
332                     System.out.println(e);
333                 }
334             }
335             //set a delay and start it.
336
w.setDelay(1 * 1000); // 1 s
337
w.start();
338             //this.wait(123); // make all watchers out sync
339
}
340
341         System.out.println("Starting");
342         // ok, a lot of those are running now, let time something else, and see if it suffers.
343
long start = System.currentTimeMillis();
344         long k;
345         for (k = 0; k < 400000000;) {
346             k++;
347         }
348         System.out.println("\ntook " + (System.currentTimeMillis() - start) + " ms");
349     }
350
351     /**
352      * The one thread to handle all FileWatchers. In earlier implementation every FileWatcher had
353      * it's own thread, but that is avoided now.
354      */

355     private static class FileWatcherRunner extends Thread JavaDoc {
356
357
358         boolean run = true;
359         /**
360          * Set of file-watchers, which are currently active.
361          */

362         private Set watchers = new CopyOnWriteArraySet();
363         private Set watchersToAdd = new HashSet();
364
365         FileWatcherRunner() {
366             super("MMBase FileWatcher thread");
367             log.service("Starting the file-watcher thread");
368             setPriority(MIN_PRIORITY);
369             setDaemon(true);
370         }
371
372         void add(FileWatcher f) {
373             watchers.add(f);
374         }
375
376         /**
377          * Main loop, will check every watched file every amount of time.
378          * It will never stop, this thread is a daemon.
379          */

380         public void run() {
381             // todo: how to stop this thread except through interrupting it?
382
List removed = new ArrayList();
383             while (run) {
384                 try {
385                     long now = System.currentTimeMillis();
386                     Iterator i = watchers.iterator();
387                     while (i.hasNext()) {
388                         FileWatcher f = (FileWatcher)i.next();
389                         if (now - f.lastCheck > f.delay) {
390                             if (log.isDebugEnabled()) {
391                                 log.trace("Filewatcher will sleep for : " + f.delay / 1000 + " s. " + "Currently watching: " + f.getClass().getName() + " " + f.toString());
392                             }
393                             // System.out.print(".");
394
f.removeFiles();
395                             //changed returns true if we can stop watching
396
if (f.changed() || f.mustStop()) {
397                                 if (log.isDebugEnabled()) {
398                                     log.debug("Removing filewatcher " + f + " " + f.mustStop());
399                                 }
400                                 removed.add(f);
401                             }
402                             f.lastCheck = now;
403                         }
404                     }
405                     watchers.removeAll(removed);
406                     removed.clear();
407                     if (log.isTraceEnabled()) {
408                         log.trace("Sleeping " + THREAD_DELAY + " ms");
409                     }
410                     Thread.sleep(THREAD_DELAY);
411                 } catch (InterruptedException JavaDoc e) {
412                     Thread JavaDoc ct = Thread.currentThread();
413                     log.debug((ct != null ? ct.getName() : "MMBase")+ " was interrupted.");
414                     break; // likely interrupted due to MMBase going down - break out of loop
415
} catch (Throwable JavaDoc ex) {
416                     // unexpected exception?? This run method should never interrupt, so we catch everything.
417
log.error("Exception: " + ex.getClass().getName() + ": " + ex.getMessage() + Logging.stackTrace(ex));
418                 }
419                 // when we found a change, we exit..
420
}
421         }
422     }
423
424     /**
425      * Performance test
426      */

427     private static class TestFileWatcher extends FileWatcher {
428         int i = 0;
429         public void onChange(java.io.File JavaDoc f) {
430             // do something..
431
i++;
432         }
433         protected void finalize() {
434             System.out.println(this.toString() + ":" + i);
435         }
436     }
437
438     /**
439      * Object used in file-lists of the FileWatcher. It wraps a File object, but adminstrates
440      * lastmodified an existence seperately (to compare with the actual values of the File).
441      */

442     private class FileEntry {
443         // static final Logger log = Logging.getLoggerInstance(FileWatcher.class.getName());
444
private long lastModified = -1;
445         private boolean exists = false;
446         private File JavaDoc file;
447
448         public FileEntry(File JavaDoc file) {
449             if (file == null) {
450                 String JavaDoc msg = "file was null";
451                 // log.error(msg);
452
throw new RuntimeException JavaDoc(msg);
453             }
454             exists = file.exists();
455             if (!exists) {
456                 // file does not exist. A change will be triggered
457
// once the file comes into existence
458
log.debug("file :" + file.getAbsolutePath() + " did not exist (yet)");
459                 lastModified = -1;
460             } else {
461                 lastModified = file.lastModified();
462             }
463             this.file = file;
464         }
465
466         /**
467          * Signal a change.
468          * Returns true if the file was modified, added, or removed.
469          * @return <code>true</code> if the file was changed
470          */

471         public boolean changed() {
472             if (file.exists()) {
473                 if (!exists) {
474                     log.info("File " + file.getAbsolutePath() + " added");
475                     return true;
476                 } else {
477                     boolean result = lastModified < file.lastModified();
478                     if (result) {
479                         log.info("File " + file.getAbsolutePath() + " changed");
480                     }
481                     return result;
482                 }
483             } else {
484                 if (exists) {
485                     log.info("File " + file.getAbsolutePath() + " removed");
486                 }
487                 return exists;
488             }
489         }
490
491         /**
492          * Call if changes were treated.
493          */

494         public void updated() {
495             exists = file.exists();
496             if (exists) {
497                 lastModified = file.lastModified();
498             } else {
499                 lastModified = -1;
500             }
501         }
502
503         public File JavaDoc getFile() {
504             return file;
505         }
506
507         public String JavaDoc toString() {
508             return file.toString() + ":" + lastModified;
509         }
510
511         public boolean equals(Object JavaDoc o) {
512             if (o instanceof FileEntry) {
513                 FileEntry fe = (FileEntry)o;
514                 return file.equals(fe.file);
515             } else if (o instanceof File JavaDoc) {
516                 return file.equals(o);
517             }
518             return false;
519         }
520
521         public int hashCode() {
522             return file.hashCode();
523         }
524
525     }
526
527     /**
528      * This FileSet makes the 'files' object of the FileWatcher look like a Set of File rather then Set of FileEntry's.
529      * @since MMBase-1.8
530      */

531     private class FileSet extends AbstractSet {
532         public int size() {
533             return FileWatcher.this.files.size();
534         }
535         public Iterator iterator() {
536             return new FileIterator();
537         }
538         public boolean add(Object JavaDoc o) {
539             int s = size();
540             FileWatcher.this.add((File JavaDoc) o);
541             return s != size();
542         }
543     }
544     /**
545      * The iterator belonging to FileSet.
546      * @since MMBase-1.8
547      */

548     private class FileIterator implements Iterator {
549         Iterator it;
550         File JavaDoc lastFile;
551         FileIterator() {
552             it = FileWatcher.this.files.iterator();
553         }
554         public boolean hasNext() {
555             return it.hasNext();
556         }
557         public Object JavaDoc next() {
558             FileEntry f = (FileEntry) it.next();
559             lastFile = f.getFile();
560             return lastFile;
561         }
562         public void remove() {
563             FileWatcher.this.remove(lastFile);
564         }
565
566     }
567
568 }
569
Popular Tags