1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.eclipse.jetty.util;
16
17 import java.io.File;
18 import java.io.FilenameFilter;
19 import java.io.IOException;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Map.Entry;
28 import java.util.Set;
29 import java.util.Timer;
30 import java.util.TimerTask;
31
32 import org.eclipse.jetty.util.component.AbstractLifeCycle;
33 import org.eclipse.jetty.util.log.Log;
34 import org.eclipse.jetty.util.log.Logger;
35
36
37
38
39
40
41
42
43
44 public class Scanner extends AbstractLifeCycle
45 {
46 private static final Logger LOG = Log.getLogger(Scanner.class);
47 private static int __scannerId=0;
48 private int _scanInterval;
49 private int _scanCount = 0;
50 private final List<Listener> _listeners = new ArrayList<Listener>();
51 private final Map<String,TimeNSize> _prevScan = new HashMap<String,TimeNSize> ();
52 private final Map<String,TimeNSize> _currentScan = new HashMap<String,TimeNSize> ();
53 private FilenameFilter _filter;
54 private final List<File> _scanDirs = new ArrayList<File>();
55 private volatile boolean _running = false;
56 private boolean _reportExisting = true;
57 private boolean _reportDirs = true;
58 private Timer _timer;
59 private TimerTask _task;
60 private int _scanDepth=0;
61
62 public enum Notification { ADDED, CHANGED, REMOVED };
63 private final Map<String,Notification> _notifications = new HashMap<String,Notification>();
64
65 static class TimeNSize
66 {
67 final long _lastModified;
68 final long _size;
69
70 public TimeNSize(long lastModified, long size)
71 {
72 _lastModified = lastModified;
73 _size = size;
74 }
75
76 @Override
77 public int hashCode()
78 {
79 return (int)_lastModified^(int)_size;
80 }
81
82 @Override
83 public boolean equals(Object o)
84 {
85 if (o instanceof TimeNSize)
86 {
87 TimeNSize tns = (TimeNSize)o;
88 return tns._lastModified==_lastModified && tns._size==_size;
89 }
90 return false;
91 }
92
93 @Override
94 public String toString()
95 {
96 return "[lm="+_lastModified+",s="+_size+"]";
97 }
98 }
99
100
101
102
103
104
105 public interface Listener
106 {
107 }
108
109 public interface ScanListener extends Listener
110 {
111 public void scan();
112 }
113
114 public interface DiscreteListener extends Listener
115 {
116 public void fileChanged (String filename) throws Exception;
117 public void fileAdded (String filename) throws Exception;
118 public void fileRemoved (String filename) throws Exception;
119 }
120
121
122 public interface BulkListener extends Listener
123 {
124 public void filesChanged (List<String> filenames) throws Exception;
125 }
126
127
128
129
130 public interface ScanCycleListener extends Listener
131 {
132 public void scanStarted(int cycle) throws Exception;
133 public void scanEnded(int cycle) throws Exception;
134 }
135
136
137
138
139 public Scanner ()
140 {
141 }
142
143
144
145
146
147 public int getScanInterval()
148 {
149 return _scanInterval;
150 }
151
152
153
154
155
156 public synchronized void setScanInterval(int scanInterval)
157 {
158 _scanInterval = scanInterval;
159 schedule();
160 }
161
162
163
164
165
166
167 @Deprecated
168 public void setScanDir (File dir)
169 {
170 _scanDirs.clear();
171 _scanDirs.add(dir);
172 }
173
174
175
176
177
178
179 @Deprecated
180 public File getScanDir ()
181 {
182 return (_scanDirs==null?null:(File)_scanDirs.get(0));
183 }
184
185 public void setScanDirs (List<File> dirs)
186 {
187 _scanDirs.clear();
188 _scanDirs.addAll(dirs);
189 }
190
191 public synchronized void addScanDir( File dir )
192 {
193 _scanDirs.add( dir );
194 }
195
196 public List<File> getScanDirs ()
197 {
198 return Collections.unmodifiableList(_scanDirs);
199 }
200
201
202
203
204
205
206 public void setRecursive (boolean recursive)
207 {
208 _scanDepth=recursive?-1:0;
209 }
210
211
212
213
214
215
216 public boolean getRecursive ()
217 {
218 return _scanDepth==-1;
219 }
220
221
222
223
224
225 public int getScanDepth()
226 {
227 return _scanDepth;
228 }
229
230
231
232
233
234 public void setScanDepth(int scanDepth)
235 {
236 _scanDepth = scanDepth;
237 }
238
239
240
241
242
243
244 public void setFilenameFilter (FilenameFilter filter)
245 {
246 _filter = filter;
247 }
248
249
250
251
252
253 public FilenameFilter getFilenameFilter ()
254 {
255 return _filter;
256 }
257
258
259
260
261
262
263
264
265 public void setReportExistingFilesOnStartup (boolean reportExisting)
266 {
267 _reportExisting = reportExisting;
268 }
269
270
271 public boolean getReportExistingFilesOnStartup()
272 {
273 return _reportExisting;
274 }
275
276
277
278
279
280 public void setReportDirs(boolean dirs)
281 {
282 _reportDirs=dirs;
283 }
284
285
286 public boolean getReportDirs()
287 {
288 return _reportDirs;
289 }
290
291
292
293
294
295
296 public synchronized void addListener (Listener listener)
297 {
298 if (listener == null)
299 return;
300 _listeners.add(listener);
301 }
302
303
304
305
306
307
308
309 public synchronized void removeListener (Listener listener)
310 {
311 if (listener == null)
312 return;
313 _listeners.remove(listener);
314 }
315
316
317
318
319
320 @Override
321 public synchronized void doStart()
322 {
323 if (_running)
324 return;
325
326 _running = true;
327
328 if (_reportExisting)
329 {
330
331 scan();
332 scan();
333 }
334 else
335 {
336
337 scanFiles();
338 _prevScan.putAll(_currentScan);
339 }
340 schedule();
341 }
342
343 public TimerTask newTimerTask ()
344 {
345 return new TimerTask()
346 {
347 @Override
348 public void run() { scan(); }
349 };
350 }
351
352 public Timer newTimer ()
353 {
354 return new Timer("Scanner-"+__scannerId++, true);
355 }
356
357 public void schedule ()
358 {
359 if (_running)
360 {
361 if (_timer!=null)
362 _timer.cancel();
363 if (_task!=null)
364 _task.cancel();
365 if (getScanInterval() > 0)
366 {
367 _timer = newTimer();
368 _task = newTimerTask();
369 _timer.schedule(_task, 1010L*getScanInterval(),1010L*getScanInterval());
370 }
371 }
372 }
373
374
375
376 @Override
377 public synchronized void doStop()
378 {
379 if (_running)
380 {
381 _running = false;
382 if (_timer!=null)
383 _timer.cancel();
384 if (_task!=null)
385 _task.cancel();
386 _task=null;
387 _timer=null;
388 }
389 }
390
391
392
393
394 public synchronized void scan ()
395 {
396 reportScanStart(++_scanCount);
397 scanFiles();
398 reportDifferences(_currentScan, _prevScan);
399 _prevScan.clear();
400 _prevScan.putAll(_currentScan);
401 reportScanEnd(_scanCount);
402
403 for (Listener l : _listeners)
404 {
405 try
406 {
407 if (l instanceof ScanListener)
408 ((ScanListener)l).scan();
409 }
410 catch (Exception e)
411 {
412 LOG.warn(e);
413 }
414 catch (Error e)
415 {
416 LOG.warn(e);
417 }
418 }
419 }
420
421
422
423
424 public synchronized void scanFiles ()
425 {
426 if (_scanDirs==null)
427 return;
428
429 _currentScan.clear();
430 Iterator<File> itor = _scanDirs.iterator();
431 while (itor.hasNext())
432 {
433 File dir = itor.next();
434
435 if ((dir != null) && (dir.exists()))
436 try
437 {
438 scanFile(dir.getCanonicalFile(), _currentScan,0);
439 }
440 catch (IOException e)
441 {
442 LOG.warn("Error scanning files.", e);
443 }
444 }
445 }
446
447
448
449
450
451
452
453
454 public synchronized void reportDifferences (Map<String,TimeNSize> currentScan, Map<String,TimeNSize> oldScan)
455 {
456
457
458 Set<String> oldScanKeys = new HashSet<String>(oldScan.keySet());
459
460
461 for (Map.Entry<String, TimeNSize> entry: currentScan.entrySet())
462 {
463 String file = entry.getKey();
464 if (!oldScanKeys.contains(file))
465 {
466 Notification old=_notifications.put(file,Notification.ADDED);
467 if (old!=null)
468 {
469 switch(old)
470 {
471 case REMOVED:
472 case CHANGED:
473 _notifications.put(file,Notification.CHANGED);
474 }
475 }
476 }
477 else if (!oldScan.get(file).equals(currentScan.get(file)))
478 {
479 Notification old=_notifications.put(file,Notification.CHANGED);
480 if (old!=null)
481 {
482 switch(old)
483 {
484 case ADDED:
485 _notifications.put(file,Notification.ADDED);
486 }
487 }
488 }
489 }
490
491
492 for (String file : oldScan.keySet())
493 {
494 if (!currentScan.containsKey(file))
495 {
496 Notification old=_notifications.put(file,Notification.REMOVED);
497 if (old!=null)
498 {
499 switch(old)
500 {
501 case ADDED:
502 _notifications.remove(file);
503 }
504 }
505 }
506 }
507
508 if (LOG.isDebugEnabled())
509 LOG.debug("scanned "+_scanDirs+": "+_notifications);
510
511
512
513 List<String> bulkChanges = new ArrayList<String>();
514 for (Iterator<Entry<String,Notification>> iter = _notifications.entrySet().iterator();iter.hasNext();)
515 {
516 Entry<String,Notification> entry=iter.next();
517 String file=entry.getKey();
518
519
520 if (oldScan.containsKey(file))
521 {
522 if (!oldScan.get(file).equals(currentScan.get(file)))
523 continue;
524 }
525 else if (currentScan.containsKey(file))
526 continue;
527
528
529 Notification notification=entry.getValue();
530 iter.remove();
531 bulkChanges.add(file);
532 switch(notification)
533 {
534 case ADDED:
535 reportAddition(file);
536 break;
537 case CHANGED:
538 reportChange(file);
539 break;
540 case REMOVED:
541 reportRemoval(file);
542 break;
543 }
544 }
545 if (!bulkChanges.isEmpty())
546 reportBulkChanges(bulkChanges);
547 }
548
549
550
551
552
553
554
555
556 private void scanFile (File f, Map<String,TimeNSize> scanInfoMap, int depth)
557 {
558 try
559 {
560 if (!f.exists())
561 return;
562
563 if (f.isFile() || depth>0&& _reportDirs && f.isDirectory())
564 {
565 if ((_filter == null) || ((_filter != null) && _filter.accept(f.getParentFile(), f.getName())))
566 {
567 String name = f.getCanonicalPath();
568 scanInfoMap.put(name, new TimeNSize(f.lastModified(),f.length()));
569 }
570 }
571
572
573 if (f.isDirectory() && (depth<_scanDepth || _scanDepth==-1 || _scanDirs.contains(f)))
574 {
575 File[] files = f.listFiles();
576 for (int i=0;i<files.length;i++)
577 scanFile(files[i], scanInfoMap,depth+1);
578 }
579 }
580 catch (IOException e)
581 {
582 LOG.warn("Error scanning watched files", e);
583 }
584 }
585
586 private void warn(Object listener,String filename,Throwable th)
587 {
588 LOG.warn(listener+" failed on '"+filename, th);
589 }
590
591
592
593
594
595 private void reportAddition (String filename)
596 {
597 Iterator<Listener> itor = _listeners.iterator();
598 while (itor.hasNext())
599 {
600 Listener l = itor.next();
601 try
602 {
603 if (l instanceof DiscreteListener)
604 ((DiscreteListener)l).fileAdded(filename);
605 }
606 catch (Exception e)
607 {
608 warn(l,filename,e);
609 }
610 catch (Error e)
611 {
612 warn(l,filename,e);
613 }
614 }
615 }
616
617
618
619
620
621
622 private void reportRemoval (String filename)
623 {
624 Iterator<Listener> itor = _listeners.iterator();
625 while (itor.hasNext())
626 {
627 Object l = itor.next();
628 try
629 {
630 if (l instanceof DiscreteListener)
631 ((DiscreteListener)l).fileRemoved(filename);
632 }
633 catch (Exception e)
634 {
635 warn(l,filename,e);
636 }
637 catch (Error e)
638 {
639 warn(l,filename,e);
640 }
641 }
642 }
643
644
645
646
647
648
649 private void reportChange (String filename)
650 {
651 Iterator<Listener> itor = _listeners.iterator();
652 while (itor.hasNext())
653 {
654 Listener l = itor.next();
655 try
656 {
657 if (l instanceof DiscreteListener)
658 ((DiscreteListener)l).fileChanged(filename);
659 }
660 catch (Exception e)
661 {
662 warn(l,filename,e);
663 }
664 catch (Error e)
665 {
666 warn(l,filename,e);
667 }
668 }
669 }
670
671 private void reportBulkChanges (List<String> filenames)
672 {
673 Iterator<Listener> itor = _listeners.iterator();
674 while (itor.hasNext())
675 {
676 Listener l = itor.next();
677 try
678 {
679 if (l instanceof BulkListener)
680 ((BulkListener)l).filesChanged(filenames);
681 }
682 catch (Exception e)
683 {
684 warn(l,filenames.toString(),e);
685 }
686 catch (Error e)
687 {
688 warn(l,filenames.toString(),e);
689 }
690 }
691 }
692
693
694
695
696 private void reportScanStart(int cycle)
697 {
698 for (Listener listener : _listeners)
699 {
700 try
701 {
702 if (listener instanceof ScanCycleListener)
703 {
704 ((ScanCycleListener)listener).scanStarted(cycle);
705 }
706 }
707 catch (Exception e)
708 {
709 LOG.warn(listener + " failed on scan start for cycle " + cycle, e);
710 }
711 }
712 }
713
714
715
716
717 private void reportScanEnd(int cycle)
718 {
719 for (Listener listener : _listeners)
720 {
721 try
722 {
723 if (listener instanceof ScanCycleListener)
724 {
725 ((ScanCycleListener)listener).scanEnded(cycle);
726 }
727 }
728 catch (Exception e)
729 {
730 LOG.warn(listener + " failed on scan end for cycle " + cycle, e);
731 }
732 }
733 }
734
735 }