cn.godmao.io.monitor.FileMonitor Maven / Gradle / Ivy
package cn.godmao.io.monitor;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 文件监听器
*
*
* 监听器可监听目录或文件
* 如果监听的Path不存在,则递归创建空目录然后监听此空目录
* 递归监听目录时,并不会监听新创建的目录
*/
public class FileMonitor extends Thread implements Closeable {
/**
* 监听路径,必须为目录
*/
private Path path;
/**
* 监听的文件,对于单文件监听不为空
*/
private Path filePath;
/**
* 监听服务
*/
private WatchService watchService;
/**
* 监听是否已经开启
*/
private boolean running = false;
/**
* 监听器
*/
private final List listeners;
/**
* WatchKey 和 Path的对应表
*/
private final Map watchKeyPathMap = new HashMap<>();
//------------------------------------------------------ Static method start
/**
* 创建并初始化监听,监听所有事件
*
* @param uri URI
* @param listeners {@link FileListener}
* @return FileMonitor
*/
public static FileMonitor create(URI uri, FileListener... listeners) throws IOException {
return create(Paths.get(uri), listeners);
}
/**
* 创建并初始化监听,监听所有事件
*
* @param url URL
* @param listeners {@link FileListener}
* @return FileMonitor
*/
public static FileMonitor create(URL url, FileListener... listeners) throws URISyntaxException, IOException {
return create(url.toURI(), listeners);
}
/**
* 创建并初始化监听,监听所有事件
*
* @param file 被监听文件
* @param listeners {@link FileListener}
* @return FileMonitor
*/
public static FileMonitor create(File file, FileListener... listeners) throws IOException {
return create(file.toPath(), listeners);
}
/**
* 创建并初始化监听,监听所有事件
*
* @param path 路径
* @param listeners {@link FileListener}
* @return FileMonitor
*/
public static FileMonitor create(String path, FileListener... listeners) throws IOException {
return create(Paths.get(path), listeners);
}
/**
* 创建并初始化监听,监听所有事件
*
* @param path 路径
* @param listeners {@link FileListener}
* @return FileMonitor
*/
public static FileMonitor create(Path path, FileListener... listeners) throws IOException {
return new FileMonitor(path, listeners);
}
//------------------------------------------------------ Static method end
//------------------------------------------------------ Constructor method start
/**
* 构造
*
* @param file 文件
* @param listeners 监听器
*/
public FileMonitor(File file, FileListener... listeners) throws IOException {
this(file.toPath(), listeners);
}
/**
* 构造
*
* @param path 字符串路径
* @param listeners 监听器
*/
public FileMonitor(String path, FileListener... listeners) throws IOException {
this(Paths.get(path), listeners);
}
/**
* 构造
*
* @param path 字符串路径
* @param listeners 监听器
*/
public FileMonitor(Path path, FileListener... listeners) throws IOException {
this.listeners = (null == listeners || listeners.length < 1) ? new CopyOnWriteArrayList<>() : Arrays.asList(listeners);
this.path = path;
this.init();
}
//------------------------------------------------------ Constructor method end
@FunctionalInterface
interface Action {
/**
* 事件处理,通过实现此方法处理各种事件。
* 事件可以调用 {@link WatchEvent#kind()}获取
*
* @param event 事件
* @param currentPath 事件发生的当前Path路径
*/
void doAction(WatchEvent> event, Path currentPath);
}
/**
* 初始化
* 初始化包括:
*
* 1、解析传入的路径,判断其为目录还是文件
* 2、创建{@link WatchService} 对象
*
*/
private void init() throws IOException {
// 获取目录或文件路径
if (!Files.exists(this.path, LinkOption.NOFOLLOW_LINKS)) {
// 不存在的路径
final Path lastPathEle = path.subpath(this.path.getNameCount() - 1, this.path.getNameCount());
final String lastPathEleStr = lastPathEle.toString();
// 带有点表示有扩展名,按照未创建的文件对待
// Linux下.d的为目录,排除
if (lastPathEleStr.indexOf('.') > -1 && !lastPathEleStr.regionMatches(true, lastPathEleStr.length() - ".d".length(), ".d", 0, ".d".length())) {
this.filePath = this.path;
this.path = this.filePath.getParent();
}
//创建不存在的目录或父目录
Files.createDirectories(this.path);
} else if (Files.isRegularFile(this.path, LinkOption.NOFOLLOW_LINKS)) {
// 文件路径
this.filePath = this.path;
this.path = this.filePath.getParent();
}
//初始化监听
watchService = FileSystems.getDefault().newWatchService();
}
public FileMonitor addListener(FileListener... listeners) {
this.listeners.addAll(Arrays.asList(listeners));
return this;
}
public FileMonitor removeListener(FileListener listener) {
while (listeners.remove(listener)) {
// empty
}
return this;
}
@Override
public void run() {
// 按照层级注册路径及其子路径
try {
registerPath(this.path);
} catch (IOException e) {
throw new RuntimeException(e);
}
while (running) {
// 执行事件获取并处理
watch((event, currentPath) -> {
final WatchEvent.Kind> kind = event.kind();
for (FileListener listener : listeners) {
if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
listener.onCreate(event, currentPath);
} else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
listener.onModify(event, currentPath);
} else if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
listener.onDelete(event, currentPath);
} else if (kind == StandardWatchEventKinds.OVERFLOW) {
listener.onOverflow(event, currentPath);
}
}
});
}
}
/**
* 执行事件获取并处理
*
* @param action 监听回调函数,实现此函数接口用于处理WatchEvent事件
*/
private void watch(Action action) {
WatchKey watchKey;
try {
watchKey = watchService.take();
} catch (InterruptedException | ClosedWatchServiceException e) {
// 用户中断
close();
return;
}
if (null == watchKey) {
return;
}
final Path currentPath = watchKeyPathMap.get(watchKey);
for (WatchEvent> event : watchKey.pollEvents()) {
// 如果监听文件,检查当前事件是否与所监听文件关联
if (!(null == filePath || filePath.endsWith(event.context().toString()))) {
continue;
}
action.doAction(event, currentPath);
}
watchKey.reset();
}
/**
* 将指定路径加入到监听中
*
* @param path 路径
*/
public void registerPath(Path path, int maxDepth) throws IOException {
final WatchKey watchKey = path.register(this.watchService,
// 事件丢失
StandardWatchEventKinds.OVERFLOW,
// 修改事件
StandardWatchEventKinds.ENTRY_MODIFY,
// 创建事件
StandardWatchEventKinds.ENTRY_CREATE,
// 删除事件
StandardWatchEventKinds.ENTRY_DELETE
);
watchKeyPathMap.put(watchKey, path);
// 递归注册下一层层级的目录
if (maxDepth > 1) {
Files.walkFileTree(path, EnumSet.noneOf(FileVisitOption.class), maxDepth, new SimpleFileVisitor() {
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
registerPath(dir, 0);//继续添加目录
return super.postVisitDirectory(dir, exc);
}
});
}
}
public void registerPath(URI uri, int maxDepth) throws IOException {
registerPath(Paths.get(uri), maxDepth);
}
public void registerPath(URL url, int maxDepth) throws URISyntaxException, IOException {
registerPath(url.toURI(), maxDepth);
}
public void registerPath(String path, int maxDepth) throws IOException {
registerPath(Paths.get(path), maxDepth);
}
public void registerPath(File file, int maxDepth) throws IOException {
registerPath(file.toPath(), maxDepth);
}
public void registerPath(Path path) throws IOException {
registerPath(path, Integer.MAX_VALUE);
}
public void registerPath(String path) throws IOException {
registerPath(Paths.get(path));
}
public void registerPath(File file) throws IOException {
registerPath(file.toPath());
}
public void registerPath(URI uri) throws IOException {
registerPath(Paths.get(uri));
}
public void registerPath(URL url) throws URISyntaxException, IOException {
registerPath(url.toURI());
}
// ---------------------------------------
@Override
public void start() {
if (running) {
throw new IllegalStateException("Monitor is already running");
}
this.running = true;
super.start();
}
/**
* 关闭监听
*/
@Override
public void close() {
running = false;
try {
watchService.close();
} catch (Exception e) {
// 静默关闭
}
}
// ---------------------------------------
}