1 //======================================================================== 2 //$Id: Scanner.java 3022 2008-06-18 04:06:37Z janb $ 3 //Copyright 2006 Mort Bay Consulting Pty. Ltd. 4 //------------------------------------------------------------------------ 5 //Licensed under the Apache License, Version 2.0 (the "License"); 6 //you may not use this file except in compliance with the License. 7 //You may obtain a copy of the License at 8 //http://www.apache.org/licenses/LICENSE-2.0 9 //Unless required by applicable law or agreed to in writing, software 10 //distributed under the License is distributed on an "AS IS" BASIS, 11 //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 //See the License for the specific language governing permissions and 13 //limitations under the License. 14 //======================================================================== 15 16 17 package org.mortbay.util; 18 19 import java.io.File; 20 import java.io.FilenameFilter; 21 import java.io.IOException; 22 import java.util.ArrayList; 23 import java.util.Collections; 24 import java.util.HashMap; 25 import java.util.HashSet; 26 import java.util.Iterator; 27 import java.util.List; 28 import java.util.Map; 29 import java.util.Set; 30 import java.util.Timer; 31 import java.util.TimerTask; 32 33 import org.mortbay.log.Log; 34 35 36 /** 37 * Scanner 38 * 39 * Utility for scanning a directory for added, removed and changed 40 * files and reporting these events via registered Listeners. 41 * 42 * TODO AbstractLifeCycle 43 */ 44 public class Scanner 45 { 46 private int _scanInterval; 47 private List _listeners = Collections.synchronizedList(new ArrayList()); 48 private Map _prevScan = new HashMap(); 49 private Map _currentScan = new HashMap(); 50 private FilenameFilter _filter; 51 private List _scanDirs; 52 private volatile boolean _running = false; 53 private boolean _reportExisting = true; 54 private Timer _timer; 55 private TimerTask _task; 56 private boolean _recursive=true; 57 58 59 /** 60 * Listener 61 * 62 * Marker for notifications re file changes. 63 */ 64 public interface Listener 65 { 66 } 67 68 69 public interface DiscreteListener extends Listener 70 { fileChanged(String filename)71 public void fileChanged (String filename) throws Exception; fileAdded(String filename)72 public void fileAdded (String filename) throws Exception; fileRemoved(String filename)73 public void fileRemoved (String filename) throws Exception; 74 } 75 76 77 public interface BulkListener extends Listener 78 { filesChanged(List filenames)79 public void filesChanged (List filenames) throws Exception; 80 } 81 82 83 /** 84 * 85 */ Scanner()86 public Scanner () 87 { 88 } 89 90 /** 91 * Get the scan interval 92 * @return interval between scans in seconds 93 */ getScanInterval()94 public int getScanInterval() 95 { 96 return _scanInterval; 97 } 98 99 /** 100 * Set the scan interval 101 * @param scanInterval pause between scans in seconds 102 */ setScanInterval(int scanInterval)103 public synchronized void setScanInterval(int scanInterval) 104 { 105 this._scanInterval = scanInterval; 106 schedule(); 107 } 108 109 /** 110 * Set the location of the directory to scan. 111 * @param dir 112 * @deprecated use setScanDirs(List dirs) instead 113 */ setScanDir(File dir)114 public void setScanDir (File dir) 115 { 116 _scanDirs = new ArrayList(); 117 _scanDirs.add(dir); 118 } 119 120 /** 121 * Get the location of the directory to scan 122 * @return 123 * @deprecated use getScanDirs() instead 124 */ getScanDir()125 public File getScanDir () 126 { 127 return (_scanDirs==null?null:(File)_scanDirs.get(0)); 128 } 129 setScanDirs(List dirs)130 public void setScanDirs (List dirs) 131 { 132 _scanDirs = dirs; 133 } 134 getScanDirs()135 public List getScanDirs () 136 { 137 return _scanDirs; 138 } 139 setRecursive(boolean recursive)140 public void setRecursive (boolean recursive) 141 { 142 _recursive=recursive; 143 } 144 getRecursive()145 public boolean getRecursive () 146 { 147 return _recursive; 148 } 149 /** 150 * Apply a filter to files found in the scan directory. 151 * Only files matching the filter will be reported as added/changed/removed. 152 * @param filter 153 */ setFilenameFilter(FilenameFilter filter)154 public void setFilenameFilter (FilenameFilter filter) 155 { 156 this._filter = filter; 157 } 158 159 /** 160 * Get any filter applied to files in the scan dir. 161 * @return 162 */ getFilenameFilter()163 public FilenameFilter getFilenameFilter () 164 { 165 return _filter; 166 } 167 168 /** 169 * Whether or not an initial scan will report all files as being 170 * added. 171 * @param reportExisting if true, all files found on initial scan will be 172 * reported as being added, otherwise not 173 */ setReportExistingFilesOnStartup(boolean reportExisting)174 public void setReportExistingFilesOnStartup (boolean reportExisting) 175 { 176 this._reportExisting = reportExisting; 177 } 178 179 /** 180 * Add an added/removed/changed listener 181 * @param listener 182 */ addListener(Listener listener)183 public synchronized void addListener (Listener listener) 184 { 185 if (listener == null) 186 return; 187 _listeners.add(listener); 188 } 189 190 191 192 /** 193 * Remove a registered listener 194 * @param listener the Listener to be removed 195 */ removeListener(Listener listener)196 public synchronized void removeListener (Listener listener) 197 { 198 if (listener == null) 199 return; 200 _listeners.remove(listener); 201 } 202 203 204 /** 205 * Start the scanning action. 206 */ start()207 public synchronized void start () 208 { 209 if (_running) 210 return; 211 212 _running = true; 213 214 if (_reportExisting) 215 { 216 // if files exist at startup, report them 217 scan(); 218 } 219 else 220 { 221 //just register the list of existing files and only report changes 222 scanFiles(); 223 _prevScan.putAll(_currentScan); 224 } 225 schedule(); 226 } 227 newTimerTask()228 public TimerTask newTimerTask () 229 { 230 return new TimerTask() 231 { 232 public void run() { scan(); } 233 }; 234 } 235 236 public Timer newTimer () 237 { 238 return new Timer(true); 239 } 240 241 public void schedule () 242 { 243 if (_running) 244 { 245 if (_timer!=null) 246 _timer.cancel(); 247 if (_task!=null) 248 _task.cancel(); 249 if (getScanInterval() > 0) 250 { 251 _timer = newTimer(); 252 _task = newTimerTask(); 253 _timer.schedule(_task, 1000L*getScanInterval(),1000L*getScanInterval()); 254 } 255 } 256 } 257 /** 258 * Stop the scanning. 259 */ 260 public synchronized void stop () 261 { 262 if (_running) 263 { 264 _running = false; 265 if (_timer!=null) 266 _timer.cancel(); 267 if (_task!=null) 268 _task.cancel(); 269 _task=null; 270 _timer=null; 271 } 272 } 273 274 /** 275 * Perform a pass of the scanner and report changes 276 */ 277 public void scan () 278 { 279 scanFiles(); 280 reportDifferences(_currentScan, _prevScan); 281 _prevScan.clear(); 282 _prevScan.putAll(_currentScan); 283 } 284 285 /** 286 * Recursively scan all files in the designated directories. 287 * @return Map of name of file to last modified time 288 */ 289 public void scanFiles () 290 { 291 if (_scanDirs==null) 292 return; 293 294 _currentScan.clear(); 295 Iterator itor = _scanDirs.iterator(); 296 while (itor.hasNext()) 297 { 298 File dir = (File)itor.next(); 299 300 if ((dir != null) && (dir.exists())) 301 scanFile(dir, _currentScan); 302 } 303 } 304 305 306 /** 307 * Report the adds/changes/removes to the registered listeners 308 * 309 * @param currentScan the info from the most recent pass 310 * @param oldScan info from the previous pass 311 */ 312 public void reportDifferences (Map currentScan, Map oldScan) 313 { 314 List bulkChanges = new ArrayList(); 315 316 Set oldScanKeys = new HashSet(oldScan.keySet()); 317 Iterator itor = currentScan.entrySet().iterator(); 318 while (itor.hasNext()) 319 { 320 Map.Entry entry = (Map.Entry)itor.next(); 321 if (!oldScanKeys.contains(entry.getKey())) 322 { 323 Log.debug("File added: "+entry.getKey()); 324 reportAddition ((String)entry.getKey()); 325 bulkChanges.add(entry.getKey()); 326 } 327 else if (!oldScan.get(entry.getKey()).equals(entry.getValue())) 328 { 329 Log.debug("File changed: "+entry.getKey()); 330 reportChange((String)entry.getKey()); 331 oldScanKeys.remove(entry.getKey()); 332 bulkChanges.add(entry.getKey()); 333 } 334 else 335 oldScanKeys.remove(entry.getKey()); 336 } 337 338 if (!oldScanKeys.isEmpty()) 339 { 340 341 Iterator keyItor = oldScanKeys.iterator(); 342 while (keyItor.hasNext()) 343 { 344 String filename = (String)keyItor.next(); 345 Log.debug("File removed: "+filename); 346 reportRemoval(filename); 347 bulkChanges.add(filename); 348 } 349 } 350 351 if (!bulkChanges.isEmpty()) 352 reportBulkChanges(bulkChanges); 353 } 354 355 356 /** 357 * Get last modified time on a single file or recurse if 358 * the file is a directory. 359 * @param f file or directory 360 * @param scanInfoMap map of filenames to last modified times 361 */ 362 private void scanFile (File f, Map scanInfoMap) 363 { 364 try 365 { 366 if (!f.exists()) 367 return; 368 369 if (f.isFile()) 370 { 371 if ((_filter == null) || ((_filter != null) && _filter.accept(f.getParentFile(), f.getName()))) 372 { 373 String name = f.getCanonicalPath(); 374 long lastModified = f.lastModified(); 375 scanInfoMap.put(name, new Long(lastModified)); 376 } 377 } 378 else if (f.isDirectory() && (_recursive || _scanDirs.contains(f))) 379 { 380 File[] files = f.listFiles(); 381 for (int i=0;i<files.length;i++) 382 scanFile(files[i], scanInfoMap); 383 } 384 } 385 catch (IOException e) 386 { 387 Log.warn("Error scanning watched files", e); 388 } 389 } 390 391 private void warn(Object listener,String filename,Throwable th) 392 { 393 Log.warn(th); 394 Log.warn(listener+" failed on '"+filename); 395 } 396 397 /** 398 * Report a file addition to the registered FileAddedListeners 399 * @param filename 400 */ 401 private void reportAddition (String filename) 402 { 403 Iterator itor = _listeners.iterator(); 404 while (itor.hasNext()) 405 { 406 Object l = itor.next(); 407 try 408 { 409 if (l instanceof DiscreteListener) 410 ((DiscreteListener)l).fileAdded(filename); 411 } 412 catch (Exception e) 413 { 414 warn(l,filename,e); 415 } 416 catch (Error e) 417 { 418 warn(l,filename,e); 419 } 420 } 421 } 422 423 424 /** 425 * Report a file removal to the FileRemovedListeners 426 * @param filename 427 */ 428 private void reportRemoval (String filename) 429 { 430 Iterator itor = _listeners.iterator(); 431 while (itor.hasNext()) 432 { 433 Object l = itor.next(); 434 try 435 { 436 if (l instanceof DiscreteListener) 437 ((DiscreteListener)l).fileRemoved(filename); 438 } 439 catch (Exception e) 440 { 441 warn(l,filename,e); 442 } 443 catch (Error e) 444 { 445 warn(l,filename,e); 446 } 447 } 448 } 449 450 451 /** 452 * Report a file change to the FileChangedListeners 453 * @param filename 454 */ 455 private void reportChange (String filename) 456 { 457 Iterator itor = _listeners.iterator(); 458 while (itor.hasNext()) 459 { 460 Object l = itor.next(); 461 try 462 { 463 if (l instanceof DiscreteListener) 464 ((DiscreteListener)l).fileChanged(filename); 465 } 466 catch (Exception e) 467 { 468 warn(l,filename,e); 469 } 470 catch (Error e) 471 { 472 warn(l,filename,e); 473 } 474 } 475 } 476 477 private void reportBulkChanges (List filenames) 478 { 479 Iterator itor = _listeners.iterator(); 480 while (itor.hasNext()) 481 { 482 Object l = itor.next(); 483 try 484 { 485 if (l instanceof BulkListener) 486 ((BulkListener)l).filesChanged(filenames); 487 } 488 catch (Exception e) 489 { 490 warn(l,filenames.toString(),e); 491 } 492 catch (Error e) 493 { 494 warn(l,filenames.toString(),e); 495 } 496 } 497 } 498 499 } 500