View Javadoc

1   //========================================================================
2   //$Id: Scanner.java 3022 2008-06-18 04:06:37Z janb $
3   //Copyright 2006 Mort Bay Consulting Pty. Ltd.
4   //------------------------------------------------------------------------
5   //Licensed under the Apache License, Version 2.0 (the "License");
6   //you may not use this file except in compliance with the License.
7   //You may obtain a copy of the License at 
8   //http://www.apache.org/licenses/LICENSE-2.0
9   //Unless required by applicable law or agreed to in writing, software
10  //distributed under the License is distributed on an "AS IS" BASIS,
11  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  //See the License for the specific language governing permissions and
13  //limitations under the License.
14  //========================================================================
15  
16  
17  package org.mortbay.util;
18  
19  import java.io.File;
20  import java.io.FilenameFilter;
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.Timer;
31  import java.util.TimerTask;
32  
33  import org.mortbay.log.Log;
34  
35  
36  /**
37   * Scanner
38   * 
39   * Utility for scanning a directory for added, removed and changed
40   * files and reporting these events via registered Listeners.
41   *
42   * TODO AbstractLifeCycle
43   */
44  public class Scanner
45  {
46      private int _scanInterval;
47      private List _listeners = Collections.synchronizedList(new ArrayList());
48      private Map _prevScan = new HashMap();
49      private Map _currentScan = new HashMap();
50      private FilenameFilter _filter;
51      private List _scanDirs;
52      private volatile boolean _running = false;
53      private boolean _reportExisting = true;
54      private Timer _timer;
55      private TimerTask _task;
56      private boolean _recursive=true;
57  
58  
59      /**
60       * Listener
61       * 
62       * Marker for notifications re file changes.
63       */
64      public interface Listener
65      {
66      }
67  
68      
69      public interface DiscreteListener extends Listener
70      {
71          public void fileChanged (String filename) throws Exception;
72          public void fileAdded (String filename) throws Exception;
73          public void fileRemoved (String filename) throws Exception;
74      }
75      
76      
77      public interface BulkListener extends Listener
78      {
79          public void filesChanged (List filenames) throws Exception;
80      }
81  
82  
83      /**
84       * 
85       */
86      public Scanner ()
87      {       
88      }
89  
90      /**
91       * Get the scan interval
92       * @return interval between scans in seconds
93       */
94      public int getScanInterval()
95      {
96          return _scanInterval;
97      }
98  
99      /**
100      * Set the scan interval
101      * @param scanInterval pause between scans in seconds
102      */
103     public synchronized void setScanInterval(int scanInterval)
104     {
105         this._scanInterval = scanInterval;
106         schedule();
107     }
108 
109     /**
110      * Set the location of the directory to scan.
111      * @param dir
112      * @deprecated use setScanDirs(List dirs) instead
113      */
114     public void setScanDir (File dir)
115     {
116         _scanDirs = new ArrayList();
117         _scanDirs.add(dir);
118     }
119 
120     /**
121      * Get the location of the directory to scan
122      * @return
123      * @deprecated use getScanDirs() instead
124      */
125     public File getScanDir ()
126     {
127         return (_scanDirs==null?null:(File)_scanDirs.get(0));
128     }
129 
130     public void setScanDirs (List dirs)
131     {
132         _scanDirs = dirs;
133     }
134     
135     public List getScanDirs ()
136     {
137         return _scanDirs;
138     }
139     
140     public void setRecursive (boolean recursive)
141     {
142         _recursive=recursive;
143     }
144     
145     public boolean getRecursive ()
146     {
147         return _recursive;
148     }
149     /**
150      * Apply a filter to files found in the scan directory.
151      * Only files matching the filter will be reported as added/changed/removed.
152      * @param filter
153      */
154     public void setFilenameFilter (FilenameFilter filter)
155     {
156         this._filter = filter;
157     }
158 
159     /**
160      * Get any filter applied to files in the scan dir.
161      * @return
162      */
163     public FilenameFilter getFilenameFilter ()
164     {
165         return _filter;
166     }
167 
168     /**
169      * Whether or not an initial scan will report all files as being
170      * added.
171      * @param reportExisting if true, all files found on initial scan will be 
172      * reported as being added, otherwise not
173      */
174     public void setReportExistingFilesOnStartup (boolean reportExisting)
175     {
176         this._reportExisting = reportExisting;
177     }
178 
179     /**
180      * Add an added/removed/changed listener
181      * @param listener
182      */
183     public synchronized void addListener (Listener listener)
184     {
185         if (listener == null)
186             return;
187         _listeners.add(listener);   
188     }
189 
190 
191 
192     /**
193      * Remove a registered listener
194      * @param listener the Listener to be removed
195      */
196     public synchronized void removeListener (Listener listener)
197     {
198         if (listener == null)
199             return;
200         _listeners.remove(listener);    
201     }
202 
203 
204     /**
205      * Start the scanning action.
206      */
207     public synchronized void start ()
208     {
209         if (_running)
210             return;
211 
212         _running = true;
213 
214         if (_reportExisting)
215         {
216             // if files exist at startup, report them
217             scan();
218         }
219         else
220         {
221             //just register the list of existing files and only report changes
222             scanFiles();
223             _prevScan.putAll(_currentScan);
224         }
225         schedule();
226     }
227 
228     public TimerTask newTimerTask ()
229     {
230         return new TimerTask()
231         {
232             public void run() { scan(); }
233         };
234     }
235 
236     public Timer newTimer ()
237     {
238         return new Timer(true);
239     }
240     
241     public void schedule ()
242     {  
243         if (_running)
244         {
245             if (_timer!=null)
246                 _timer.cancel();
247             if (_task!=null)
248                 _task.cancel();
249             if (getScanInterval() > 0)
250             {
251                 _timer = newTimer();
252                 _task = newTimerTask();
253                 _timer.schedule(_task, 1000L*getScanInterval(),1000L*getScanInterval());
254             }
255         }
256     }
257     /**
258      * Stop the scanning.
259      */
260     public synchronized void stop ()
261     {
262         if (_running)
263         {
264             _running = false; 
265             if (_timer!=null)
266                 _timer.cancel();
267             if (_task!=null)
268                 _task.cancel();
269             _task=null;
270             _timer=null;
271         }
272     }
273 
274     /**
275      * Perform a pass of the scanner and report changes
276      */
277     public void scan ()
278     {
279         scanFiles();
280         reportDifferences(_currentScan, _prevScan);
281         _prevScan.clear();
282         _prevScan.putAll(_currentScan);
283     }
284 
285     /**
286      * Recursively scan all files in the designated directories.
287      * @return Map of name of file to last modified time
288      */
289     public void scanFiles ()
290     {
291         if (_scanDirs==null)
292             return;
293         
294         _currentScan.clear();
295         Iterator itor = _scanDirs.iterator();
296         while (itor.hasNext())
297         {
298             File dir = (File)itor.next();
299             
300             if ((dir != null) && (dir.exists()))
301                 scanFile(dir, _currentScan);
302         }
303     }
304 
305 
306     /**
307      * Report the adds/changes/removes to the registered listeners
308      * 
309      * @param currentScan the info from the most recent pass
310      * @param oldScan info from the previous pass
311      */
312     public void reportDifferences (Map currentScan, Map oldScan) 
313     {
314         List bulkChanges = new ArrayList();
315         
316         Set oldScanKeys = new HashSet(oldScan.keySet());
317         Iterator itor = currentScan.entrySet().iterator();
318         while (itor.hasNext())
319         {
320             Map.Entry entry = (Map.Entry)itor.next();
321             if (!oldScanKeys.contains(entry.getKey()))
322             {
323                 Log.debug("File added: "+entry.getKey());
324                 reportAddition ((String)entry.getKey());
325                 bulkChanges.add(entry.getKey());
326             }
327             else if (!oldScan.get(entry.getKey()).equals(entry.getValue()))
328             {
329                 Log.debug("File changed: "+entry.getKey());
330                 reportChange((String)entry.getKey());
331                 oldScanKeys.remove(entry.getKey());
332                 bulkChanges.add(entry.getKey());
333             }
334             else
335                 oldScanKeys.remove(entry.getKey());
336         }
337 
338         if (!oldScanKeys.isEmpty())
339         {
340 
341             Iterator keyItor = oldScanKeys.iterator();
342             while (keyItor.hasNext())
343             {
344                 String filename = (String)keyItor.next();
345                 Log.debug("File removed: "+filename);
346                 reportRemoval(filename);
347                 bulkChanges.add(filename);
348             }
349         }
350         
351         if (!bulkChanges.isEmpty())
352             reportBulkChanges(bulkChanges);
353     }
354 
355 
356     /**
357      * Get last modified time on a single file or recurse if
358      * the file is a directory. 
359      * @param f file or directory
360      * @param scanInfoMap map of filenames to last modified times
361      */
362     private void scanFile (File f, Map scanInfoMap)
363     {
364         try
365         {
366             if (!f.exists())
367                 return;
368 
369             if (f.isFile())
370             {
371                 if ((_filter == null) || ((_filter != null) && _filter.accept(f.getParentFile(), f.getName())))
372                 {
373                     String name = f.getCanonicalPath();
374                     long lastModified = f.lastModified();
375                     scanInfoMap.put(name, new Long(lastModified));
376                 }
377             }
378             else if (f.isDirectory() && (_recursive || _scanDirs.contains(f)))
379             {
380                 File[] files = f.listFiles();
381                 for (int i=0;i<files.length;i++)
382                     scanFile(files[i], scanInfoMap);
383             }
384         }
385         catch (IOException e)
386         {
387             Log.warn("Error scanning watched files", e);
388         }
389     }
390 
391     private void warn(Object listener,String filename,Throwable th)
392     {
393         Log.warn(th);
394         Log.warn(listener+" failed on '"+filename);
395     }
396 
397     /**
398      * Report a file addition to the registered FileAddedListeners
399      * @param filename
400      */
401     private void reportAddition (String filename)
402     {
403         Iterator itor = _listeners.iterator();
404         while (itor.hasNext())
405         {
406             Object l = itor.next();
407             try
408             {
409                 if (l instanceof DiscreteListener)
410                     ((DiscreteListener)l).fileAdded(filename);
411             }
412             catch (Exception e)
413             {
414                 warn(l,filename,e);
415             }
416             catch (Error e)
417             {
418                 warn(l,filename,e);
419             }
420         }
421     }
422 
423 
424     /**
425      * Report a file removal to the FileRemovedListeners
426      * @param filename
427      */
428     private void reportRemoval (String filename)
429     {
430         Iterator itor = _listeners.iterator();
431         while (itor.hasNext())
432         {
433             Object l = itor.next();
434             try
435             {
436                 if (l instanceof DiscreteListener)
437                     ((DiscreteListener)l).fileRemoved(filename);
438             }
439             catch (Exception e)
440             {
441                 warn(l,filename,e);
442             }
443             catch (Error e)
444             {
445                 warn(l,filename,e);
446             }
447         }
448     }
449 
450 
451     /**
452      * Report a file change to the FileChangedListeners
453      * @param filename
454      */
455     private void reportChange (String filename)
456     {
457         Iterator itor = _listeners.iterator();
458         while (itor.hasNext())
459         {
460             Object l = itor.next();
461             try
462             {
463                 if (l instanceof DiscreteListener)
464                     ((DiscreteListener)l).fileChanged(filename);
465             }
466             catch (Exception e)
467             {
468                 warn(l,filename,e);
469             }
470             catch (Error e)
471             {
472                 warn(l,filename,e);
473             }
474         }
475     }
476     
477     private void reportBulkChanges (List filenames)
478     {
479         Iterator itor = _listeners.iterator();
480         while (itor.hasNext())
481         {
482             Object l = itor.next();
483             try
484             {
485                 if (l instanceof BulkListener)
486                     ((BulkListener)l).filesChanged(filenames);
487             }
488             catch (Exception e)
489             {
490                 warn(l,filenames.toString(),e);
491             }
492             catch (Error e)
493             {
494                 warn(l,filenames.toString(),e);
495             }
496         }
497     }
498 
499 }