@ -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,10 +92,12 @@ 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" ) ;
checkNotStarted ( ) ;
this . listeners . add ( fileChangeListener ) ;
synchronized ( this . monitor ) {
checkNotStarted ( ) ;
this . listeners . add ( fileChangeListener ) ;
}
}
/ * *
@ -102,8 +107,10 @@ public class FileSystemWatcher {
* /
public void addSourceFolders ( Iterable < File > folders ) {
Assert . notNull ( folders , "Folders must not be null" ) ;
for ( File folder : folders ) {
addSourceFolder ( folder ) ;
synchronized ( this . monitor ) {
for ( File folder : folders ) {
addSourceFolder ( folder ) ;
}
}
}
@ -112,54 +119,49 @@ public class FileSystemWatcher {
* { @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" ) ;
checkNotStarted ( ) ;
this . folders . put ( folder , null ) ;
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 ) {
this . triggerFilter = triggerFilter ;
public void setTriggerFilter ( FileFilter triggerFilter ) {
synchronized ( this . monitor ) {
this . triggerFilter = triggerFilter ;
}
}
private void checkNotStarted ( ) {
Assert . state ( this . watchThread = = null , "FileSystemWatcher already started" ) ;
synchronized ( this . monitor ) {
Assert . state ( this . watchThread = = null , "FileSystemWatcher already started" ) ;
}
}
/ * *
* Start monitoring the source folder for changes .
* /
public synchronized void start ( ) {
saveInitialSnapshots ( ) ;
if ( this . watchThread = = null ) {
this . watchThread = new Thread ( ) {
@Override
public void run ( ) {
int remainingScans = FileSystemWatcher . this . remainingScans . get ( ) ;
while ( remainingScans > 0 | | remainingScans = = - 1 ) {
try {
if ( remainingScans > 0 ) {
FileSystemWatcher . this . remainingScans . decrementAndGet ( ) ;
}
scan ( ) ;
}
catch ( InterruptedException ex ) {
// Ignore
}
remainingScans = FileSystemWatcher . this . remainingScans . get ( ) ;
}
} ;
} ;
this . watchThread . setName ( "File Watcher" ) ;
this . watchThread . setDaemon ( this . daemon ) ;
this . remainingScans = new AtomicInteger ( - 1 ) ;
this . watchThread . start ( ) ;
public void start ( ) {
synchronized ( this . monitor ) {
saveInitialSnapshots ( ) ;
if ( this . watchThread = = null ) {
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 ( ) ;
}
}
}
@ -169,72 +171,10 @@ public class FileSystemWatcher {
}
}
private void scan ( ) throws InterruptedException {
Thread . sleep ( this . pollInterval - this . quietPeriod ) ;
Map < File , FolderSnapshot > previous ;
Map < File , FolderSnapshot > current = this . folders ;
do {
previous = current ;
current = getCurrentSnapshots ( ) ;
Thread . sleep ( this . quietPeriod ) ;
}
while ( isDifferent ( previous , current ) ) ;
if ( isDifferent ( this . folders , current ) ) {
updateSnapshots ( current . values ( ) ) ;
}
}
private boolean isDifferent ( Map < File , FolderSnapshot > previous ,
Map < File , FolderSnapshot > current ) {
if ( ! previous . keySet ( ) . equals ( current . keySet ( ) ) ) {
return true ;
}
for ( Map . Entry < File , FolderSnapshot > entry : previous . entrySet ( ) ) {
FolderSnapshot previousFolder = entry . getValue ( ) ;
FolderSnapshot currentFolder = current . get ( entry . getKey ( ) ) ;
if ( ! previousFolder . equals ( currentFolder , this . triggerFilter ) ) {
return true ;
}
}
return false ;
}
private Map < File , FolderSnapshot > getCurrentSnapshots ( ) {
Map < File , FolderSnapshot > snapshots = new LinkedHashMap < File , FolderSnapshot > ( ) ;
for ( File folder : this . folders . keySet ( ) ) {
snapshots . put ( folder , new FolderSnapshot ( folder ) ) ;
}
return snapshots ;
}
private void updateSnapshots ( Collection < FolderSnapshot > snapshots ) {
Map < File , FolderSnapshot > updated = new LinkedHashMap < File , FolderSnapshot > ( ) ;
Set < ChangedFiles > changeSet = new LinkedHashSet < ChangedFiles > ( ) ;
for ( FolderSnapshot snapshot : snapshots ) {
FolderSnapshot previous = this . folders . get ( snapshot . getFolder ( ) ) ;
updated . put ( snapshot . getFolder ( ) , snapshot ) ;
ChangedFiles changedFiles = previous . getChangedFiles ( snapshot ,
this . triggerFilter ) ;
if ( ! changedFiles . getFiles ( ) . isEmpty ( ) ) {
changeSet . add ( changedFiles ) ;
}
}
if ( ! changeSet . isEmpty ( ) ) {
fireListeners ( Collections . unmodifiableSet ( changeSet ) ) ;
}
this . folders = updated ;
}
private void fireListeners ( Set < ChangedFiles > changeSet ) {
for ( FileChangeListener listener : this . listeners ) {
listener . onChange ( changeSet ) ;
}
}
/ * *
* Stop monitoring the source folders .
* /
public synchronized void stop ( ) {
public void stop ( ) {
stopAfter ( 0 ) ;
}
@ -242,23 +182,131 @@ public class FileSystemWatcher {
* 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 ( ) ;
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 ;
}
if ( Thread . currentThread ( ) ! = thread ) {
}
}
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 = this . remainingScans . get ( ) ;
while ( remainingScans > 0 | | remainingScans = = - 1 ) {
try {
thread . join ( ) ;
if ( remainingScans > 0 ) {
this . remainingScans . decrementAndGet ( ) ;
}
scan ( ) ;
}
catch ( InterruptedException ex ) {
Thread . currentThread ( ) . interrupt ( ) ;
// Ignore
}
remainingScans = this . remainingScans . get ( ) ;
}
} ;
private void scan ( ) throws InterruptedException {
Thread . sleep ( this . pollInterval - this . quietPeriod ) ;
Map < File , FolderSnapshot > previous ;
Map < File , FolderSnapshot > current = this . folders ;
do {
previous = current ;
current = getCurrentSnapshots ( ) ;
Thread . sleep ( this . quietPeriod ) ;
}
while ( isDifferent ( previous , current ) ) ;
if ( isDifferent ( this . folders , current ) ) {
updateSnapshots ( current . values ( ) ) ;
}
this . watchThread = null ;
}
private boolean isDifferent ( Map < File , FolderSnapshot > previous ,
Map < File , FolderSnapshot > current ) {
if ( ! previous . keySet ( ) . equals ( current . keySet ( ) ) ) {
return true ;
}
for ( Map . Entry < File , FolderSnapshot > entry : previous . entrySet ( ) ) {
FolderSnapshot previousFolder = entry . getValue ( ) ;
FolderSnapshot currentFolder = current . get ( entry . getKey ( ) ) ;
if ( ! previousFolder . equals ( currentFolder , this . triggerFilter ) ) {
return true ;
}
}
return false ;
}
private Map < File , FolderSnapshot > getCurrentSnapshots ( ) {
Map < File , FolderSnapshot > snapshots = new LinkedHashMap < File , FolderSnapshot > ( ) ;
for ( File folder : this . folders . keySet ( ) ) {
snapshots . put ( folder , new FolderSnapshot ( folder ) ) ;
}
return snapshots ;
}
private void updateSnapshots ( Collection < FolderSnapshot > snapshots ) {
Map < File , FolderSnapshot > updated = new LinkedHashMap < File , FolderSnapshot > ( ) ;
Set < ChangedFiles > changeSet = new LinkedHashSet < ChangedFiles > ( ) ;
for ( FolderSnapshot snapshot : snapshots ) {
FolderSnapshot previous = this . folders . get ( snapshot . getFolder ( ) ) ;
updated . put ( snapshot . getFolder ( ) , snapshot ) ;
ChangedFiles changedFiles = previous . getChangedFiles ( snapshot ,
this . triggerFilter ) ;
if ( ! changedFiles . getFiles ( ) . isEmpty ( ) ) {
changeSet . add ( changedFiles ) ;
}
}
if ( ! changeSet . isEmpty ( ) ) {
fireListeners ( Collections . unmodifiableSet ( changeSet ) ) ;
}
this . folders = updated ;
}
private void fireListeners ( Set < ChangedFiles > changeSet ) {
for ( FileChangeListener listener : this . listeners ) {
listener . onChange ( changeSet ) ;
}
}
}
}