001: //========================================================================
002: //$Id: Scanner.java 1460 2007-01-04 09:07:49Z gregw $
003: //Copyright 2006 Mort Bay Consulting Pty. Ltd.
004: //------------------------------------------------------------------------
005: //Licensed under the Apache License, Version 2.0 (the "License");
006: //you may not use this file except in compliance with the License.
007: //You may obtain a copy of the License at
008: //http://www.apache.org/licenses/LICENSE-2.0
009: //Unless required by applicable law or agreed to in writing, software
010: //distributed under the License is distributed on an "AS IS" BASIS,
011: //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: //See the License for the specific language governing permissions and
013: //limitations under the License.
014: //========================================================================
015:
016: package org.mortbay.util;
017:
018: import java.io.File;
019: import java.io.FilenameFilter;
020: import java.io.IOException;
021: import java.util.ArrayList;
022: import java.util.Collections;
023: import java.util.Date;
024: import java.util.HashMap;
025: import java.util.HashSet;
026: import java.util.Iterator;
027: import java.util.List;
028: import java.util.Map;
029: import java.util.Set;
030:
031: import org.mortbay.log.Log;
032:
033: /**
034: * Scanner
035: *
036: * Utility for scanning a directory for added, removed and changed
037: * files and reporting these events via registered Listeners.
038: *
039: * TODO AbstractLifeCycle
040: */
041: public class Scanner implements Runnable {
042: private int _scanInterval;
043:
044: private List _listeners = Collections
045: .synchronizedList(new ArrayList());
046: private Map _prevScan = Collections.EMPTY_MAP;
047: private FilenameFilter _filter;
048: private File _scanDir;
049: private Thread _thread;
050: private volatile boolean _running = false;
051: private boolean _reportExisting = true;
052:
053: /**
054: * Listener
055: *
056: * Signature of notifications re file changes.
057: */
058: public interface Listener {
059: public void fileChanged(String filename) throws Exception;
060:
061: public void fileAdded(String filename) throws Exception;
062:
063: public void fileRemoved(String filename) throws Exception;
064: }
065:
066: /**
067: *
068: */
069: public Scanner() {
070: }
071:
072: /**
073: * Get the scan interval
074: * @return interval between scans in seconds
075: */
076: public int getScanInterval() {
077: return _scanInterval;
078: }
079:
080: /**
081: * Set the scan interval
082: * @param scanInterval pause between scans in seconds
083: */
084: public void setScanInterval(int scanInterval) {
085: this ._scanInterval = scanInterval;
086: }
087:
088: /**
089: * Set the location of the directory to scan.
090: * @param dir
091: */
092: public void setScanDir(File dir) {
093: _scanDir = dir;
094: }
095:
096: /**
097: * Get the location of the directory to scan
098: * @return
099: */
100: public File getScanDir() {
101: return _scanDir;
102: }
103:
104: /**
105: * Apply a filter to files found in the scan directory.
106: * Only files matching the filter will be reported as added/changed/removed.
107: * @param filter
108: */
109: public void setFilenameFilter(FilenameFilter filter) {
110: this ._filter = filter;
111: }
112:
113: /**
114: * Get any filter applied to files in the scan dir.
115: * @return
116: */
117: public FilenameFilter getFilenameFilter() {
118: return _filter;
119: }
120:
121: /**
122: * Whether or not an initial scan will report all files as being
123: * added.
124: * @param reportExisting if true, all files found on initial scan will be
125: * reported as being added, otherwise not
126: */
127: public void setReportExistingFilesOnStartup(boolean reportExisting) {
128: this ._reportExisting = reportExisting;
129: }
130:
131: /**
132: * Add an added/removed/changed listener
133: * @param listener
134: */
135: public synchronized void addListener(Listener listener) {
136: if (listener == null)
137: return;
138:
139: _listeners.add(listener);
140: }
141:
142: /**
143: * Remove a registered listener
144: * @param listener the Listener to be removed
145: */
146: public synchronized void removeListener(Listener listener) {
147: if (listener == null)
148: return;
149: _listeners.remove(listener);
150: }
151:
152: /**
153: * Scan the configured directory, sleeping for the
154: * configured scanInterval (in seconds) between each pass.
155: *
156: * @see java.lang.Runnable#run()
157: */
158: public void run() {
159: // set the sleep interval
160: long sleepMillis = getScanInterval() * 1000L;
161:
162: if (_reportExisting) {
163: // if files exist at startup, report them
164: scan();
165: } else {
166: //just register the list of existing files and only report changes
167: _prevScan = scanFiles();
168: }
169:
170: _running = true;
171: while (_running) {
172: try {
173: //wake up and scan the files
174: Thread.sleep(sleepMillis);
175: scan();
176: } catch (InterruptedException e) {
177: _running = false;
178: }
179: }
180: }
181:
182: /**
183: * Start the scanning action.
184: */
185: public void start() {
186: if (_running)
187: throw new IllegalStateException("Already running");
188:
189: _thread = new Thread(this , "scanner");
190: _thread.setDaemon(true);
191: _thread.start();
192: }
193:
194: /**
195: * Stop the scanning.
196: */
197: public void stop() {
198: if (_running)
199: _thread.interrupt();
200: }
201:
202: /**
203: * Perform a pass of the scanner and report changes
204: */
205: public void scan() {
206: Map currentScan = scanFiles();
207: reportDifferences(currentScan, _prevScan);
208: _prevScan = currentScan;
209: }
210:
211: /**
212: * Recursively scan all files in the designated directory.
213: * @return
214: */
215: public Map scanFiles() {
216: File dir = getScanDir();
217:
218: Log.debug("Scanning directory " + getScanDir());
219: HashMap scanInfo = new HashMap();
220:
221: if ((dir != null) && (dir.exists()))
222: scanFile(dir, scanInfo);
223:
224: Log.debug("Scan complete at " + new Date());
225: return scanInfo;
226: }
227:
228: /**
229: * Report the adds/changes/removes to the registered listeners
230: *
231: * @param currentScan the info from the most recent pass
232: * @param oldScan info from the previous pass
233: */
234: public void reportDifferences(Map currentScan, Map oldScan) {
235: Set oldScanKeys = new HashSet(oldScan.keySet());
236: Iterator itor = currentScan.entrySet().iterator();
237: while (itor.hasNext()) {
238: Map.Entry entry = (Map.Entry) itor.next();
239: if (!oldScanKeys.contains(entry.getKey())) {
240: Log.debug("File added: " + entry.getKey());
241: reportAddition((String) entry.getKey());
242: } else if (!oldScan.get(entry.getKey()).equals(
243: entry.getValue())) {
244: Log.debug("File changed: " + entry.getKey());
245: reportChange((String) entry.getKey());
246: oldScanKeys.remove(entry.getKey());
247: } else
248: oldScanKeys.remove(entry.getKey());
249: }
250:
251: if (!oldScanKeys.isEmpty()) {
252:
253: Iterator keyItor = oldScanKeys.iterator();
254: while (keyItor.hasNext()) {
255: String filename = (String) keyItor.next();
256: Log.debug("File removed: " + filename);
257: reportRemoval(filename);
258: }
259: }
260: }
261:
262: /**
263: * Get last modified time on a single file or recurse if
264: * the file is a directory.
265: * @param f file or directory
266: * @param scanInfoMap map of filenames to last modified times
267: */
268: private void scanFile(File f, Map scanInfoMap) {
269: try {
270: if (!f.exists())
271: return;
272:
273: if (f.isFile()) {
274: Log.debug("Checking file " + f.getName());
275: if ((_filter == null)
276: || ((_filter != null) && _filter.accept(f
277: .getParentFile(), f.getName()))) {
278: Log.debug("File accepted");
279: String name = f.getCanonicalPath();
280: long lastModified = f.lastModified();
281: scanInfoMap.put(name, new Long(lastModified));
282: }
283: } else if (f.isDirectory()) {
284: File[] files = f.listFiles();
285: for (int i = 0; i < files.length; i++)
286: scanFile(files[i], scanInfoMap);
287: }
288: } catch (IOException e) {
289: Log.warn("Error scanning watched files", e);
290: }
291: }
292:
293: /**
294: * Report a file addition to the registered FileAddedListeners
295: * @param filename
296: */
297: private void reportAddition(String filename) {
298: Iterator itor = _listeners.iterator();
299: while (itor.hasNext()) {
300: try {
301: ((Listener) itor.next()).fileAdded(filename);
302: } catch (Exception e) {
303: Log.warn(e);
304: } catch (Error e) {
305: Log.warn(e);
306: }
307: }
308: }
309:
310: /**
311: * Report a file removal to the FileRemovedListeners
312: * @param filename
313: */
314: private void reportRemoval(String filename) {
315: Iterator itor = _listeners.iterator();
316: while (itor.hasNext()) {
317: try {
318: ((Listener) itor.next()).fileRemoved(filename);
319: } catch (Exception e) {
320: Log.warn(e);
321: } catch (Error e) {
322: Log.warn(e);
323: }
324: }
325: }
326:
327: /**
328: * Report a file change to the FileChangedListeners
329: * @param filename
330: */
331: private void reportChange(String filename) {
332: Iterator itor = _listeners.iterator();
333: while (itor.hasNext()) {
334: try {
335: ((Listener) itor.next()).fileChanged(filename);
336: } catch (Exception e) {
337: Log.warn(e);
338: } catch (Error e) {
339: Log.warn(e);
340: }
341: }
342: }
343:
344: }
|