Merge branch '1.3.x'

pull/6108/head
Andy Wilkinson 9 years ago
commit a3f4b3c704

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

Loading…
Cancel
Save