Mercurial Hosting > luan
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/Scanner.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,746 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + + +package org.eclipse.jetty.util; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; + +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/** + * Scanner + * + * Utility for scanning a directory for added, removed and changed + * files and reporting these events via registered Listeners. + * + */ +public class Scanner extends AbstractLifeCycle +{ + private static final Logger LOG = Log.getLogger(Scanner.class); + private static int __scannerId=0; + private int _scanInterval; + private int _scanCount = 0; + private final List<Listener> _listeners = new ArrayList<Listener>(); + private final Map<String,TimeNSize> _prevScan = new HashMap<String,TimeNSize> (); + private final Map<String,TimeNSize> _currentScan = new HashMap<String,TimeNSize> (); + private FilenameFilter _filter; + private final List<File> _scanDirs = new ArrayList<File>(); + private volatile boolean _running = false; + private boolean _reportExisting = true; + private boolean _reportDirs = true; + private Timer _timer; + private TimerTask _task; + private int _scanDepth=0; + + public enum Notification { ADDED, CHANGED, REMOVED }; + private final Map<String,Notification> _notifications = new HashMap<String,Notification>(); + + static class TimeNSize + { + final long _lastModified; + final long _size; + + public TimeNSize(long lastModified, long size) + { + _lastModified = lastModified; + _size = size; + } + + @Override + public int hashCode() + { + return (int)_lastModified^(int)_size; + } + + @Override + public boolean equals(Object o) + { + if (o instanceof TimeNSize) + { + TimeNSize tns = (TimeNSize)o; + return tns._lastModified==_lastModified && tns._size==_size; + } + return false; + } + + @Override + public String toString() + { + return "[lm="+_lastModified+",s="+_size+"]"; + } + } + + /** + * Listener + * + * Marker for notifications re file changes. + */ + public interface Listener + { + } + + public interface ScanListener extends Listener + { + public void scan(); + } + + public interface DiscreteListener extends Listener + { + public void fileChanged (String filename) throws Exception; + public void fileAdded (String filename) throws Exception; + public void fileRemoved (String filename) throws Exception; + } + + + public interface BulkListener extends Listener + { + public void filesChanged (List<String> filenames) throws Exception; + } + + /** + * Listener that notifies when a scan has started and when it has ended. + */ + public interface ScanCycleListener extends Listener + { + public void scanStarted(int cycle) throws Exception; + public void scanEnded(int cycle) throws Exception; + } + + /** + * + */ + public Scanner () + { + } + + /** + * Get the scan interval + * @return interval between scans in seconds + */ + public int getScanInterval() + { + return _scanInterval; + } + + /** + * Set the scan interval + * @param scanInterval pause between scans in seconds, or 0 for no scan after the initial scan. + */ + public synchronized void setScanInterval(int scanInterval) + { + _scanInterval = scanInterval; + schedule(); + } + + /** + * Set the location of the directory to scan. + * @param dir + * @deprecated use setScanDirs(List dirs) instead + */ + @Deprecated + public void setScanDir (File dir) + { + _scanDirs.clear(); + _scanDirs.add(dir); + } + + /** + * Get the location of the directory to scan + * @return the first directory (of {@link #getScanDirs()} being scanned) + * @deprecated use getScanDirs() instead + */ + @Deprecated + public File getScanDir () + { + return (_scanDirs==null?null:(File)_scanDirs.get(0)); + } + + public void setScanDirs (List<File> dirs) + { + _scanDirs.clear(); + _scanDirs.addAll(dirs); + } + + public synchronized void addScanDir( File dir ) + { + _scanDirs.add( dir ); + } + + public List<File> getScanDirs () + { + return Collections.unmodifiableList(_scanDirs); + } + + /* ------------------------------------------------------------ */ + /** + * @param recursive True if scanning is recursive + * @see #setScanDepth(int) + */ + public void setRecursive (boolean recursive) + { + _scanDepth=recursive?-1:0; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if scanning is fully recursive (scandepth==-1) + * @see #getScanDepth() + */ + public boolean getRecursive () + { + return _scanDepth==-1; + } + + /* ------------------------------------------------------------ */ + /** Get the scanDepth. + * @return the scanDepth + */ + public int getScanDepth() + { + return _scanDepth; + } + + /* ------------------------------------------------------------ */ + /** Set the scanDepth. + * @param scanDepth the scanDepth to set + */ + public void setScanDepth(int scanDepth) + { + _scanDepth = scanDepth; + } + + /** + * Apply a filter to files found in the scan directory. + * Only files matching the filter will be reported as added/changed/removed. + * @param filter + */ + public void setFilenameFilter (FilenameFilter filter) + { + _filter = filter; + } + + /** + * Get any filter applied to files in the scan dir. + * @return the filename filter + */ + public FilenameFilter getFilenameFilter () + { + return _filter; + } + + /* ------------------------------------------------------------ */ + /** + * Whether or not an initial scan will report all files as being + * added. + * @param reportExisting if true, all files found on initial scan will be + * reported as being added, otherwise not + */ + public void setReportExistingFilesOnStartup (boolean reportExisting) + { + _reportExisting = reportExisting; + } + + /* ------------------------------------------------------------ */ + public boolean getReportExistingFilesOnStartup() + { + return _reportExisting; + } + + /* ------------------------------------------------------------ */ + /** Set if found directories should be reported. + * @param dirs + */ + public void setReportDirs(boolean dirs) + { + _reportDirs=dirs; + } + + /* ------------------------------------------------------------ */ + public boolean getReportDirs() + { + return _reportDirs; + } + + /* ------------------------------------------------------------ */ + /** + * Add an added/removed/changed listener + * @param listener + */ + public synchronized void addListener (Listener listener) + { + if (listener == null) + return; + _listeners.add(listener); + } + + + + /** + * Remove a registered listener + * @param listener the Listener to be removed + */ + public synchronized void removeListener (Listener listener) + { + if (listener == null) + return; + _listeners.remove(listener); + } + + + /** + * Start the scanning action. + */ + @Override + public synchronized void doStart() + { + if (_running) + return; + + _running = true; + + if (_reportExisting) + { + // if files exist at startup, report them + scan(); + scan(); // scan twice so files reported as stable + } + else + { + //just register the list of existing files and only report changes + scanFiles(); + _prevScan.putAll(_currentScan); + } + schedule(); + } + + public TimerTask newTimerTask () + { + return new TimerTask() + { + @Override + public void run() { scan(); } + }; + } + + public Timer newTimer () + { + return new Timer("Scanner-"+__scannerId++, true); + } + + public void schedule () + { + if (_running) + { + if (_timer!=null) + _timer.cancel(); + if (_task!=null) + _task.cancel(); + if (getScanInterval() > 0) + { + _timer = newTimer(); + _task = newTimerTask(); + _timer.schedule(_task, 1010L*getScanInterval(),1010L*getScanInterval()); + } + } + } + /** + * Stop the scanning. + */ + @Override + public synchronized void doStop() + { + if (_running) + { + _running = false; + if (_timer!=null) + _timer.cancel(); + if (_task!=null) + _task.cancel(); + _task=null; + _timer=null; + } + } + + /** + * Perform a pass of the scanner and report changes + */ + public synchronized void scan () + { + reportScanStart(++_scanCount); + scanFiles(); + reportDifferences(_currentScan, _prevScan); + _prevScan.clear(); + _prevScan.putAll(_currentScan); + reportScanEnd(_scanCount); + + for (Listener l : _listeners) + { + try + { + if (l instanceof ScanListener) + ((ScanListener)l).scan(); + } + catch (Exception e) + { + LOG.warn(e); + } + catch (Error e) + { + LOG.warn(e); + } + } + } + + /** + * Recursively scan all files in the designated directories. + */ + public synchronized void scanFiles () + { + if (_scanDirs==null) + return; + + _currentScan.clear(); + Iterator<File> itor = _scanDirs.iterator(); + while (itor.hasNext()) + { + File dir = itor.next(); + + if ((dir != null) && (dir.exists())) + try + { + scanFile(dir.getCanonicalFile(), _currentScan,0); + } + catch (IOException e) + { + LOG.warn("Error scanning files.", e); + } + } + } + + + /** + * Report the adds/changes/removes to the registered listeners + * + * @param currentScan the info from the most recent pass + * @param oldScan info from the previous pass + */ + public synchronized void reportDifferences (Map<String,TimeNSize> currentScan, Map<String,TimeNSize> oldScan) + { + // scan the differences and add what was found to the map of notifications: + + Set<String> oldScanKeys = new HashSet<String>(oldScan.keySet()); + + // Look for new and changed files + for (Map.Entry<String, TimeNSize> entry: currentScan.entrySet()) + { + String file = entry.getKey(); + if (!oldScanKeys.contains(file)) + { + Notification old=_notifications.put(file,Notification.ADDED); + if (old!=null) + { + switch(old) + { + case REMOVED: + case CHANGED: + _notifications.put(file,Notification.CHANGED); + } + } + } + else if (!oldScan.get(file).equals(currentScan.get(file))) + { + Notification old=_notifications.put(file,Notification.CHANGED); + if (old!=null) + { + switch(old) + { + case ADDED: + _notifications.put(file,Notification.ADDED); + } + } + } + } + + // Look for deleted files + for (String file : oldScan.keySet()) + { + if (!currentScan.containsKey(file)) + { + Notification old=_notifications.put(file,Notification.REMOVED); + if (old!=null) + { + switch(old) + { + case ADDED: + _notifications.remove(file); + } + } + } + } + + if (LOG.isDebugEnabled()) + LOG.debug("scanned "+_scanDirs+": "+_notifications); + + // Process notifications + // Only process notifications that are for stable files (ie same in old and current scan). + List<String> bulkChanges = new ArrayList<String>(); + for (Iterator<Entry<String,Notification>> iter = _notifications.entrySet().iterator();iter.hasNext();) + { + Entry<String,Notification> entry=iter.next(); + String file=entry.getKey(); + + // Is the file stable? + if (oldScan.containsKey(file)) + { + if (!oldScan.get(file).equals(currentScan.get(file))) + continue; + } + else if (currentScan.containsKey(file)) + continue; + + // File is stable so notify + Notification notification=entry.getValue(); + iter.remove(); + bulkChanges.add(file); + switch(notification) + { + case ADDED: + reportAddition(file); + break; + case CHANGED: + reportChange(file); + break; + case REMOVED: + reportRemoval(file); + break; + } + } + if (!bulkChanges.isEmpty()) + reportBulkChanges(bulkChanges); + } + + + /** + * Get last modified time on a single file or recurse if + * the file is a directory. + * @param f file or directory + * @param scanInfoMap map of filenames to last modified times + */ + private void scanFile (File f, Map<String,TimeNSize> scanInfoMap, int depth) + { + try + { + if (!f.exists()) + return; + + if (f.isFile() || depth>0&& _reportDirs && f.isDirectory()) + { + if ((_filter == null) || ((_filter != null) && _filter.accept(f.getParentFile(), f.getName()))) + { + String name = f.getCanonicalPath(); + scanInfoMap.put(name, new TimeNSize(f.lastModified(),f.length())); + } + } + + // If it is a directory, scan if it is a known directory or the depth is OK. + if (f.isDirectory() && (depth<_scanDepth || _scanDepth==-1 || _scanDirs.contains(f))) + { + File[] files = f.listFiles(); + if (files != null) + { + for (int i=0;i<files.length;i++) + scanFile(files[i], scanInfoMap,depth+1); + } + else + LOG.warn("Error listing files in directory {}", f); + + } + } + catch (IOException e) + { + LOG.warn("Error scanning watched files", e); + } + } + + private void warn(Object listener,String filename,Throwable th) + { + LOG.warn(listener+" failed on '"+filename, th); + } + + /** + * Report a file addition to the registered FileAddedListeners + * @param filename + */ + private void reportAddition (String filename) + { + Iterator<Listener> itor = _listeners.iterator(); + while (itor.hasNext()) + { + Listener l = itor.next(); + try + { + if (l instanceof DiscreteListener) + ((DiscreteListener)l).fileAdded(filename); + } + catch (Exception e) + { + warn(l,filename,e); + } + catch (Error e) + { + warn(l,filename,e); + } + } + } + + + /** + * Report a file removal to the FileRemovedListeners + * @param filename + */ + private void reportRemoval (String filename) + { + Iterator<Listener> itor = _listeners.iterator(); + while (itor.hasNext()) + { + Object l = itor.next(); + try + { + if (l instanceof DiscreteListener) + ((DiscreteListener)l).fileRemoved(filename); + } + catch (Exception e) + { + warn(l,filename,e); + } + catch (Error e) + { + warn(l,filename,e); + } + } + } + + + /** + * Report a file change to the FileChangedListeners + * @param filename + */ + private void reportChange (String filename) + { + Iterator<Listener> itor = _listeners.iterator(); + while (itor.hasNext()) + { + Listener l = itor.next(); + try + { + if (l instanceof DiscreteListener) + ((DiscreteListener)l).fileChanged(filename); + } + catch (Exception e) + { + warn(l,filename,e); + } + catch (Error e) + { + warn(l,filename,e); + } + } + } + + private void reportBulkChanges (List<String> filenames) + { + Iterator<Listener> itor = _listeners.iterator(); + while (itor.hasNext()) + { + Listener l = itor.next(); + try + { + if (l instanceof BulkListener) + ((BulkListener)l).filesChanged(filenames); + } + catch (Exception e) + { + warn(l,filenames.toString(),e); + } + catch (Error e) + { + warn(l,filenames.toString(),e); + } + } + } + + /** + * signal any scan cycle listeners that a scan has started + */ + private void reportScanStart(int cycle) + { + for (Listener listener : _listeners) + { + try + { + if (listener instanceof ScanCycleListener) + { + ((ScanCycleListener)listener).scanStarted(cycle); + } + } + catch (Exception e) + { + LOG.warn(listener + " failed on scan start for cycle " + cycle, e); + } + } + } + + /** + * sign + */ + private void reportScanEnd(int cycle) + { + for (Listener listener : _listeners) + { + try + { + if (listener instanceof ScanCycleListener) + { + ((ScanCycleListener)listener).scanEnded(cycle); + } + } + catch (Exception e) + { + LOG.warn(listener + " failed on scan end for cycle " + cycle, e); + } + } + } + +}