KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > vfs > impl > DefaultFileMonitor


1 /*
2  * Copyright 2002-2005 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.commons.vfs.impl;
17
18 import org.apache.commons.logging.Log;
19 import org.apache.commons.logging.LogFactory;
20 import org.apache.commons.vfs.FileListener;
21 import org.apache.commons.vfs.FileMonitor;
22 import org.apache.commons.vfs.FileName;
23 import org.apache.commons.vfs.FileObject;
24 import org.apache.commons.vfs.FileSystemException;
25 import org.apache.commons.vfs.FileType;
26 import org.apache.commons.vfs.provider.AbstractFileSystem;
27
28 import java.util.HashMap JavaDoc;
29 import java.util.Map JavaDoc;
30 import java.util.Stack JavaDoc;
31
32 /**
33  * A polling {@link FileMonitor} implementation.<br />
34  * <br />
35  * The DefaultFileMonitor is a Thread based polling file system monitor with a 1
36  * second delay.<br />
37  * <br />
38  * <b>Design:</b>
39  * <p/>
40  * There is a Map of monitors known as FileMonitorAgents. With the thread running,
41  * each FileMonitorAgent object is asked to "check" on the file it is
42  * responsible for.
43  * To do this check, the cache is cleared.
44  * </p>
45  * <ul>
46  * <li>If the file existed before the refresh and it no longer exists, a delete
47  * event is fired.</li>
48  * <li>If the file existed before the refresh and it still exists, check the
49  * last modified timestamp to see if that has changed.</li>
50  * <li>If it has, fire a change event.</li>
51  * </ul>
52  * <p/>
53  * With each file delete, the FileMonitorAgent of the parent is asked to
54  * re-build its
55  * list of children, so that they can be accurately checked when there are new
56  * children.<br/>
57  * New files are detected during each "check" as each file does a check for new
58  * children.
59  * If new children are found, create events are fired recursively if recursive
60  * descent is
61  * enabled.
62  * </p>
63  * <p/>
64  * For performance reasons, added a delay that increases as the number of files
65  * monitored
66  * increases. The default is a delay of 1 second for every 1000 files processed.
67  * </p>
68  * <p/>
69  * <br /><b>Example usage:</b><pre>
70  * FileSystemManager fsManager = VFS.getManager();
71  * FileObject listendir = fsManager.resolveFile("/home/username/monitored/");
72  * <p/>
73  * DefaultFileMonitor fm = new DefaultFileMonitor(new CustomFileListener());
74  * fm.setRecursive(true);
75  * fm.addFile(listendir);
76  * fm.start();
77  * </pre>
78  * <i>(where CustomFileListener is a class that implements the FileListener
79  * interface.)</i>
80  *
81  * @author <a HREF="mailto:xknight@users.sourceforge.net">Christopher Ottley</a>
82  */

83 public class DefaultFileMonitor implements Runnable JavaDoc, FileMonitor
84 {
85     private final static Log log = LogFactory.getLog(DefaultFileMonitor.class);
86
87     /**
88      * Map from FileName to FileObject being monitored.
89      */

90     private final Map JavaDoc monitorMap = new HashMap JavaDoc();
91
92     /**
93      * The low priority thread used for checking the files being monitored.
94      */

95     private Thread JavaDoc monitorThread;
96
97     /**
98      * File objects to be removed from the monitor map.
99      */

100     private Stack JavaDoc deleteStack = new Stack JavaDoc();
101
102     /**
103      * File objects to be added to the monitor map.
104      */

105     private Stack JavaDoc addStack = new Stack JavaDoc();
106
107     /**
108      * A flag used to determine if the monitor thread should be running.
109      */

110     private boolean shouldRun = true;
111
112     /**
113      * A flag used to determine if adding files to be monitored should be recursive.
114      */

115     private boolean recursive = false;
116
117     /**
118      * Set the delay between checks
119      */

120     private long delay = 1000;
121
122     /**
123      * Set the number of files to check until a delay will be inserted
124      */

125     private int checksPerRun = 1000;
126
127     /**
128      * A listener object that if set, is notified on file creation and deletion.
129      */

130     private final FileListener listener;
131
132     public DefaultFileMonitor(final FileListener listener)
133     {
134         this.listener = listener;
135     }
136
137     /**
138      * Access method to get the recursive setting when adding files for monitoring.
139      */

140     public boolean isRecursive()
141     {
142         return this.recursive;
143     }
144
145     /**
146      * Access method to set the recursive setting when adding files for monitoring.
147      */

148     public void setRecursive(final boolean newRecursive)
149     {
150         this.recursive = newRecursive;
151     }
152
153     /**
154      * Access method to get the current FileListener object notified when there
155      * are changes with the files added.
156      */

157     FileListener getFileListener()
158     {
159         return this.listener;
160     }
161
162     /**
163      * Adds a file to be monitored.
164      */

165     public void addFile(final FileObject file)
166     {
167         synchronized (this.monitorMap)
168         {
169             if (this.monitorMap.get(file.getName()) == null)
170             {
171                 this.monitorMap.put(file.getName(), new FileMonitorAgent(this,
172                         file));
173
174                 try
175                 {
176                     if (this.listener != null)
177                     {
178                         file.getFileSystem().addListener(file, this.listener);
179                     }
180
181                     if (file.getType().hasChildren() && this.recursive)
182                     {
183                         // Traverse the children
184
final FileObject[] children = file.getChildren();
185                         for (int i = 0; i < children.length; i++)
186                         {
187                             this.addFile(children[i]); // Add depth first
188
}
189                     }
190
191                 }
192                 catch (FileSystemException fse)
193                 {
194                     log.error(fse.getLocalizedMessage(), fse);
195                 }
196
197             }
198         }
199     }
200
201     /**
202      * Removes a file from being monitored.
203      */

204     public void removeFile(final FileObject file)
205     {
206         synchronized (this.monitorMap)
207         {
208             FileName fn = file.getName();
209             if (this.monitorMap.get(fn) != null)
210             {
211                 FileObject parent;
212                 try
213                 {
214                     parent = file.getParent();
215                 }
216                 catch (FileSystemException fse)
217                 {
218                     parent = null;
219                 }
220
221                 this.monitorMap.remove(fn);
222
223                 if (parent != null)
224                 { // Not the root
225
FileMonitorAgent parentAgent =
226                         (FileMonitorAgent) this.monitorMap.get(parent.getName());
227                     if (parentAgent != null)
228                     {
229                         parentAgent.resetChildrenList();
230                     }
231                 }
232             }
233         }
234     }
235
236     /**
237      * Queues a file for removal from being monitored.
238      */

239     protected void queueRemoveFile(final FileObject file)
240     {
241         this.deleteStack.push(file);
242     }
243
244     /**
245      * Get the delay between runs
246      */

247     public long getDelay()
248     {
249         return delay;
250     }
251
252     /**
253      * Set the delay between runs
254      */

255     public void setDelay(long delay)
256     {
257         if (delay > 0)
258         {
259             this.delay = delay;
260         }
261         else
262         {
263             this.delay = 1000;
264         }
265     }
266
267     /**
268      * get the number of files to check per run
269      */

270     public int getChecksPerRun()
271     {
272         return checksPerRun;
273     }
274
275     /**
276      * set the number of files to check per run.
277      * a additional delay will be added if there are more files to check
278      *
279      * @param checksPerRun a value less than 1 will disable this feature
280      */

281     public void setChecksPerRun(int checksPerRun)
282     {
283         this.checksPerRun = checksPerRun;
284     }
285
286     /**
287      * Queues a file for addition to be monitored.
288      */

289     protected void queueAddFile(final FileObject file)
290     {
291         this.addStack.push(file);
292     }
293
294     /**
295      * Starts monitoring the files that have been added.
296      */

297     public void start()
298     {
299         if (this.monitorThread == null)
300         {
301             this.monitorThread = new Thread JavaDoc(this);
302             this.monitorThread.setDaemon(true);
303             this.monitorThread.setPriority(Thread.MIN_PRIORITY);
304         }
305         this.monitorThread.start();
306     }
307
308     /**
309      * Stops monitoring the files that have been added.
310      */

311     public void stop()
312     {
313         this.shouldRun = false;
314     }
315
316     /**
317      * Asks the agent for each file being monitored to check its file for changes.
318      */

319     public void run()
320     {
321         mainloop:
322         while (!Thread.currentThread().isInterrupted() && this.shouldRun)
323         {
324             while (!this.deleteStack.empty())
325             {
326                 this.removeFile((FileObject) this.deleteStack.pop());
327             }
328
329             // For each entry in the map
330
Object JavaDoc fileNames[];
331             synchronized (this.monitorMap)
332             {
333                 fileNames = this.monitorMap.keySet().toArray();
334             }
335             for (int iterFileNames = 0; iterFileNames < fileNames.length;
336                  iterFileNames++)
337             {
338                 FileName fileName = (FileName) fileNames[iterFileNames];
339                 FileMonitorAgent agent;
340                 synchronized (this.monitorMap)
341                 {
342                     agent = (FileMonitorAgent) this.monitorMap.get(fileName);
343                 }
344                 if (agent != null)
345                 {
346                     agent.check();
347                 }
348
349                 if (getChecksPerRun() > 0)
350                 {
351                     if ((iterFileNames % getChecksPerRun()) == 0)
352                     {
353                         try
354                         {
355                             Thread.sleep(getDelay());
356                         }
357                         catch (InterruptedException JavaDoc e)
358                         {
359
360                         }
361                     }
362                 }
363
364                 if (Thread.currentThread().isInterrupted() || !this.shouldRun)
365                 {
366                     continue mainloop;
367                 }
368             }
369
370             while (!this.addStack.empty())
371             {
372                 this.addFile((FileObject) this.addStack.pop());
373             }
374
375             try
376             {
377                 Thread.sleep(getDelay());
378             }
379             catch (InterruptedException JavaDoc e)
380             {
381                 continue;
382             }
383         }
384
385         this.shouldRun = true;
386     }
387
388     /**
389      * File monitor agent.
390      */

391     private static class FileMonitorAgent
392     {
393         private final FileObject file;
394         private final DefaultFileMonitor fm;
395
396         private boolean exists;
397         private long timestamp;
398         private Map JavaDoc children = null;
399
400         private FileMonitorAgent(DefaultFileMonitor fm, FileObject file)
401         {
402             this.fm = fm;
403             this.file = file;
404
405             this.refresh();
406             this.resetChildrenList();
407
408             try
409             {
410                 this.exists = this.file.exists();
411             }
412             catch (FileSystemException fse)
413             {
414                 this.exists = false;
415             }
416
417             try
418             {
419                 this.timestamp = this.file.getContent().getLastModifiedTime();
420             }
421             catch (FileSystemException fse)
422             {
423                 this.timestamp = -1;
424             }
425
426         }
427
428         private void resetChildrenList()
429         {
430             try
431             {
432                 if (this.file.getType() == FileType.FOLDER)
433                 {
434                     this.children = new HashMap JavaDoc();
435                     FileObject[] childrenList = this.file.getChildren();
436                     for (int i = 0; i < childrenList.length; i++)
437                     {
438                         this.children.put(childrenList[i].getName(), new
439                             Object JavaDoc()); // null?
440
}
441                 }
442             }
443             catch (FileSystemException fse)
444             {
445                 this.children = null;
446             }
447         }
448
449
450         /**
451          * Clear the cache and re-request the file object
452          */

453         private void refresh()
454         {
455             try
456             {
457                 // this.file = ((AbstractFileSystem) this.file.getFileSystem()).resolveFile(this.file.getName(), false);
458

459                 // close the file - this will detach and reattach its resources (for this thread) on the
460
// next access
461
this.file.close();
462             }
463             catch (FileSystemException fse)
464             {
465                 log.error(fse.getLocalizedMessage(), fse);
466             }
467         }
468
469
470         /**
471          * Recursively fires create events for all children if recursive descent is
472          * enabled. Otherwise the create event is only fired for the initial
473          * FileObject.
474          */

475         private void fireAllCreate(FileObject child)
476         {
477             // Add listener so that it can be triggered
478
if (this.fm.getFileListener() != null)
479             {
480                 child.getFileSystem().addListener(child, this.fm.getFileListener());
481             }
482
483             ((AbstractFileSystem) child.getFileSystem()).fireFileCreated(child);
484
485             // Remove it because a listener is added in the queueAddFile
486
if (this.fm.getFileListener() != null)
487             {
488                 child.getFileSystem().removeListener(child,
489                     this.fm.getFileListener());
490             }
491
492             this.fm.queueAddFile(child); // Add
493

494             try
495             {
496
497                 if (this.fm.isRecursive())
498                 {
499                     if (child.getType() == FileType.FOLDER)
500                     {
501                         FileObject[] newChildren = child.getChildren();
502                         for (int i = 0; i < newChildren.length; i++)
503                         {
504                             fireAllCreate(newChildren[i]);
505                         }
506                     }
507                 }
508
509             }
510             catch (FileSystemException fse)
511             {
512                 log.error(fse.getLocalizedMessage(), fse);
513             }
514         }
515
516         /**
517          * Only checks for new children. If children are removed, they'll
518          * eventually be checked.
519          */

520         private void checkForNewChildren()
521         {
522             try
523             {
524                 if (this.file.getType() == FileType.FOLDER)
525                 {
526                     FileObject[] newChildren = this.file.getChildren();
527                     if (this.children != null)
528                     {
529                         // See which new children are not listed in the current children map.
530
Map JavaDoc newChildrenMap = new HashMap JavaDoc();
531                         Stack JavaDoc missingChildren = new Stack JavaDoc();
532
533                         for (int i = 0; i < newChildren.length; i++)
534                         {
535                             newChildrenMap.put(newChildren[i].getName(), new
536                                 Object JavaDoc()); // null ?
537
// If the child's not there
538
if
539                             (!this.children.containsKey(newChildren[i].getName()))
540                             {
541                                 missingChildren.push(newChildren[i]);
542                             }
543                         }
544
545                         this.children = newChildrenMap;
546
547                         // If there were missing children
548
if (!missingChildren.empty())
549                         {
550
551                             while (!missingChildren.empty())
552                             {
553                                 FileObject child = (FileObject)
554                                     missingChildren.pop();
555                                 this.fireAllCreate(child);
556                             }
557                         }
558
559                     }
560                     else
561                     {
562                         // First set of children - Break out the cigars
563
if (newChildren.length > 0)
564                         {
565                             this.children = new HashMap JavaDoc();
566                         }
567                         for (int i = 0; i < newChildren.length; i++)
568                         {
569                             this.children.put(newChildren[i].getName(), new
570                                 Object JavaDoc()); // null?
571
this.fireAllCreate(newChildren[i]);
572                         }
573                     }
574                 }
575             }
576             catch (FileSystemException fse)
577             {
578                 log.error(fse.getLocalizedMessage(), fse);
579             }
580         }
581
582         private void check()
583         {
584             this.refresh();
585
586             try
587             {
588                 // If the file existed and now doesn't
589
if (this.exists && !this.file.exists())
590                 {
591                     this.exists = this.file.exists();
592                     this.timestamp = -1;
593
594                     // Fire delete event
595

596                     ((AbstractFileSystem)
597                         this.file.getFileSystem()).fireFileDeleted(this.file);
598
599                     // Remove listener in case file is re-created. Don't want to fire twice.
600
if (this.fm.getFileListener() != null)
601                     {
602                         this.file.getFileSystem().removeListener(this.file,
603                             this.fm.getFileListener());
604                     }
605
606                     // Remove from map
607
this.fm.queueRemoveFile(this.file);
608                 }
609                 else if (this.exists && this.file.exists())
610                 {
611
612                     // Check the timestamp to see if it has been modified
613
if (this.timestamp !=
614                             this.file.getContent().getLastModifiedTime())
615                     {
616                         this.timestamp =
617                             this.file.getContent().getLastModifiedTime();
618                         // Fire change event
619

620                         // Don't fire if it's a folder because new file children
621
// and deleted files in a folder have their own event triggered.
622
if (this.file.getType() != FileType.FOLDER)
623                         {
624                             ((AbstractFileSystem)
625                                 this.file.getFileSystem()).fireFileChanged(this.file);
626                         }
627                     }
628
629                 }
630
631                 this.checkForNewChildren();
632
633             }
634             catch (FileSystemException fse)
635             {
636                 log.error(fse.getLocalizedMessage(), fse);
637             }
638         }
639
640     }
641
642 }
Popular Tags