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