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     {
fileChanged(String filename)71         public void fileChanged (String filename) throws Exception;
fileAdded(String filename)72         public void fileAdded (String filename) throws Exception;
fileRemoved(String filename)73         public void fileRemoved (String filename) throws Exception;
74     }
75 
76 
77     public interface BulkListener extends Listener
78     {
filesChanged(List filenames)79         public void filesChanged (List filenames) throws Exception;
80     }
81 
82 
83     /**
84      *
85      */
Scanner()86     public Scanner ()
87     {
88     }
89 
90     /**
91      * Get the scan interval
92      * @return interval between scans in seconds
93      */
getScanInterval()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      */
setScanInterval(int scanInterval)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      */
setScanDir(File dir)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      */
getScanDir()125     public File getScanDir ()
126     {
127         return (_scanDirs==null?null:(File)_scanDirs.get(0));
128     }
129 
setScanDirs(List dirs)130     public void setScanDirs (List dirs)
131     {
132         _scanDirs = dirs;
133     }
134 
getScanDirs()135     public List getScanDirs ()
136     {
137         return _scanDirs;
138     }
139 
setRecursive(boolean recursive)140     public void setRecursive (boolean recursive)
141     {
142         _recursive=recursive;
143     }
144 
getRecursive()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      */
setFilenameFilter(FilenameFilter filter)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      */
getFilenameFilter()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      */
setReportExistingFilesOnStartup(boolean reportExisting)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      */
addListener(Listener listener)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      */
removeListener(Listener listener)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      */
start()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 
newTimerTask()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 }
500