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