org.xnio.PollingFileSystemWatcher Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2013 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xnio;
import java.io.File;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.xnio._private.Messages;
/**
* Polling based file system watcher service, for use on JDK6
*
* @author Stuart Douglas
*/
class PollingFileSystemWatcher implements FileSystemWatcher, Runnable {
private static final AtomicInteger threadIdCounter = new AtomicInteger(0);
public static final String THREAD_NAME = "xnio-polling-file-watcher";
private final Map files = Collections.synchronizedMap(new HashMap());
private final Thread watchThread;
private final int pollInterval;
private volatile boolean stopped = false;
PollingFileSystemWatcher(final String name, int pollInterval, final boolean daemon) {
watchThread = new Thread(this, THREAD_NAME + "[" + name + "]-" + threadIdCounter);
watchThread.setDaemon(daemon);
watchThread.start();
this.pollInterval = pollInterval;
}
@Override
public void run() {
while (!stopped) {
try {
doNotify();
Thread.sleep(pollInterval);
} catch (InterruptedException e) {
//ignore
}
}
}
private void doNotify() {
for (Map.Entry entry : files.entrySet()) {
Map result = doScan(entry.getKey());
List currentDiff = doDiff(result, entry.getValue().currentFileState);
if (!currentDiff.isEmpty()) {
entry.getValue().currentFileState = result;
for(FileChangeCallback callback : entry.getValue().callbacks) {
invokeCallback(callback, currentDiff);
}
}
}
}
private List doDiff(Map newFileState, Map currentFileState) {
final List results = new ArrayList();
final Map currentCopy = new HashMap(currentFileState);
for (Map.Entry newEntry : newFileState.entrySet()) {
Long old = currentCopy.remove(newEntry.getKey());
if (old == null) {
results.add(new FileChangeEvent(newEntry.getKey(), FileChangeEvent.Type.ADDED));
} else {
if (!old.equals(newEntry.getValue()) && !newEntry.getKey().isDirectory()) {
//we don't add modified events for directories
//as we will be generating modified events for the files in the dir anyway
results.add(new FileChangeEvent(newEntry.getKey(), FileChangeEvent.Type.MODIFIED));
}
}
}
for (Map.Entry old : currentCopy.entrySet()) {
results.add(new FileChangeEvent(old.getKey(), FileChangeEvent.Type.REMOVED));
}
return results;
}
@Override
public synchronized void watchPath(File file, FileChangeCallback callback) {
PollHolder holder = files.get(file);
if(holder == null) {
files.put(file, holder = new PollHolder(doScan(file)));
}
holder.callbacks.add(callback);
}
@Override
public synchronized void unwatchPath(File file, final FileChangeCallback callback) {
PollHolder holder = files.get(file);
if(holder != null) {
holder.callbacks.remove(callback);
if(holder.callbacks.isEmpty()) {
files.remove(file);
}
}
files.remove(file);
}
@Override
public void close() throws IOException {
this.stopped = true;
watchThread.interrupt();
}
private class PollHolder {
Map currentFileState;
final List callbacks = new ArrayList();
private PollHolder(Map currentFileState) {
this.currentFileState = currentFileState;
}
}
static Map doScan(File file) {
final Map results = new HashMap();
final Deque toScan = new ArrayDeque();
toScan.add(file);
while (!toScan.isEmpty()) {
File next = toScan.pop();
if (next.isDirectory()) {
results.put(next, next.lastModified());
File[] list = next.listFiles();
if (list != null) {
for (File f : list) {
toScan.push(new File(f.getAbsolutePath()));
}
}
} else {
results.put(next, next.lastModified());
}
}
return results;
}
static void invokeCallback(FileChangeCallback callback, List results) {
try {
callback.handleChanges(results);
} catch (Exception e) {
Messages.msg.failedToInvokeFileWatchCallback(e);
}
}
}