@ -21,6 +21,7 @@ import java.io.FileFilter;
import java.util.ArrayList ;
import java.util.Collection ;
import java.util.Collections ;
import java.util.HashMap ;
import java.util.LinkedHashMap ;
import java.util.LinkedHashSet ;
import java.util.List ;
@ -44,7 +45,7 @@ public class FileSystemWatcher {
private static final long DEFAULT_QUIET_PERIOD = 400 ;
private List < FileChangeListener > listeners = new ArrayList < FileChangeListener > ( ) ;
private final List < FileChangeListener > listeners = new ArrayList < FileChangeListener > ( ) ;
private final boolean daemon ;
@ -52,14 +53,16 @@ public class FileSystemWatcher {
private final long quietPeriod ;
private Thread watchThread ;
private final AtomicInteger remainingScans = new AtomicInteger ( - 1 ) ;
private AtomicInteger remainingScans = new AtomicInteger ( - 1 ) ;
private final Map < File , FolderSnapshot > folders = new HashMap < File , FolderSnapshot > ( ) ;
private Map< File , FolderSnapshot > folders = new LinkedHashMap < File , FolderSnapshot > ( ) ;
private Thread watchThread ;
private FileFilter triggerFilter ;
private final Object monitor = new Object ( ) ;
/ * *
* Create a new { @link FileSystemWatcher } instance .
* /
@ -89,11 +92,13 @@ public class FileSystemWatcher {
* { @link # start ( ) started } .
* @param fileChangeListener the listener to add
* /
public synchronized void addListener ( FileChangeListener fileChangeListener ) {
public void addListener ( FileChangeListener fileChangeListener ) {
Assert . notNull ( fileChangeListener , "FileChangeListener must not be null" ) ;
synchronized ( this . monitor ) {
checkNotStarted ( ) ;
this . listeners . add ( fileChangeListener ) ;
}
}
/ * *
* Add source folders to monitor . Cannot be called after the watcher has been
@ -102,72 +107,143 @@ public class FileSystemWatcher {
* /
public void addSourceFolders ( Iterable < File > folders ) {
Assert . notNull ( folders , "Folders must not be null" ) ;
synchronized ( this . monitor ) {
for ( File folder : folders ) {
addSourceFolder ( folder ) ;
}
}
}
/ * *
* Add a source folder to monitor . Cannot be called after the watcher has been
* { @link # start ( ) started } .
* @param folder the folder to monitor
* /
public synchronized void addSourceFolder ( File folder ) {
public void addSourceFolder ( File folder ) {
Assert . notNull ( folder , "Folder must not be null" ) ;
Assert . isTrue ( folder . isDirectory ( ) ,
"Folder '" + folder + "' must exist and must" + " be a directory" ) ;
synchronized ( this . monitor ) {
checkNotStarted ( ) ;
this . folders . put ( folder , null ) ;
}
}
/ * *
* Set an optional { @link FileFilter } used to limit the files that trigger a change .
* @param triggerFilter a trigger filter or null
* /
public synchronized void setTriggerFilter ( FileFilter triggerFilter ) {
public void setTriggerFilter ( FileFilter triggerFilter ) {
synchronized ( this . monitor ) {
this . triggerFilter = triggerFilter ;
}
}
private void checkNotStarted ( ) {
synchronized ( this . monitor ) {
Assert . state ( this . watchThread = = null , "FileSystemWatcher already started" ) ;
}
}
/ * *
* Start monitoring the source folder for changes .
* /
public synchronized void start ( ) {
public void start ( ) {
synchronized ( this . monitor ) {
saveInitialSnapshots ( ) ;
if ( this . watchThread = = null ) {
this . watchThread = new Thread ( ) {
Map < File , FolderSnapshot > localFolders = new HashMap < File , FolderSnapshot > ( ) ;
localFolders . putAll ( this . folders ) ;
this . watchThread = new Thread ( new Watcher ( this . remainingScans ,
new ArrayList < FileChangeListener > ( this . listeners ) ,
this . triggerFilter , this . pollInterval , this . quietPeriod ,
localFolders ) ) ;
this . watchThread . setName ( "File Watcher" ) ;
this . watchThread . setDaemon ( this . daemon ) ;
this . watchThread . start ( ) ;
}
}
}
private void saveInitialSnapshots ( ) {
for ( File folder : this . folders . keySet ( ) ) {
this . folders . put ( folder , new FolderSnapshot ( folder ) ) ;
}
}
/ * *
* Stop monitoring the source folders .
* /
public void stop ( ) {
stopAfter ( 0 ) ;
}
/ * *
* Stop monitoring the source folders .
* @param remainingScans the number of remaining scans
* /
void stopAfter ( int remainingScans ) {
synchronized ( this . monitor ) {
Thread thread = this . watchThread ;
if ( thread ! = null ) {
this . remainingScans . set ( remainingScans ) ;
if ( remainingScans < = 0 ) {
thread . interrupt ( ) ;
}
if ( Thread . currentThread ( ) ! = thread ) {
try {
thread . join ( ) ;
}
catch ( InterruptedException ex ) {
Thread . currentThread ( ) . interrupt ( ) ;
}
}
this . watchThread = null ;
}
}
}
private static final class Watcher implements Runnable {
private final AtomicInteger remainingScans ;
private final List < FileChangeListener > listeners ;
private final FileFilter triggerFilter ;
private final long pollInterval ;
private final long quietPeriod ;
private Map < File , FolderSnapshot > folders ;
private Watcher ( AtomicInteger remainingScans , List < FileChangeListener > listeners ,
FileFilter triggerFilter , long pollInterval , long quietPeriod ,
Map < File , FolderSnapshot > folders ) {
this . remainingScans = remainingScans ;
this . listeners = listeners ;
this . triggerFilter = triggerFilter ;
this . pollInterval = pollInterval ;
this . quietPeriod = quietPeriod ;
this . folders = folders ;
}
@Override
public void run ( ) {
int remainingScans = FileSystemWatcher . this . remainingScans . get ( ) ;
int remainingScans = this. remainingScans . get ( ) ;
while ( remainingScans > 0 | | remainingScans = = - 1 ) {
try {
if ( remainingScans > 0 ) {
FileSystemWatcher . this . remainingScans . decrementAndGet ( ) ;
this. remainingScans . decrementAndGet ( ) ;
}
scan ( ) ;
}
catch ( InterruptedException ex ) {
// Ignore
}
remainingScans = FileSystemWatcher . this . remainingScans . get ( ) ;
remainingScans = this. remainingScans . get ( ) ;
}
} ;
} ;
this . watchThread . setName ( "File Watcher" ) ;
this . watchThread . setDaemon ( this . daemon ) ;
this . remainingScans = new AtomicInteger ( - 1 ) ;
this . watchThread . start ( ) ;
}
}
private void saveInitialSnapshots ( ) {
for ( File folder : this . folders . keySet ( ) ) {
this . folders . put ( folder , new FolderSnapshot ( folder ) ) ;
}
}
private void scan ( ) throws InterruptedException {
Thread . sleep ( this . pollInterval - this . quietPeriod ) ;
@ -231,34 +307,6 @@ public class FileSystemWatcher {
}
}
/ * *
* Stop monitoring the source folders .
* /
public synchronized void stop ( ) {
stopAfter ( 0 ) ;
}
/ * *
* Stop monitoring the source folders .
* @param remainingScans the number of remaining scans
* /
synchronized void stopAfter ( int remainingScans ) {
Thread thread = this . watchThread ;
if ( thread ! = null ) {
this . remainingScans . set ( remainingScans ) ;
if ( remainingScans < = 0 ) {
thread . interrupt ( ) ;
}
if ( Thread . currentThread ( ) ! = thread ) {
try {
thread . join ( ) ;
}
catch ( InterruptedException ex ) {
Thread . currentThread ( ) . interrupt ( ) ;
}
}
this . watchThread = null ;
}
}
}