com.xiaoleilu.hutool.io.watch.WatchMonitor Maven / Gradle / Ivy
package com.xiaoleilu.hutool.io.watch;
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.AccessDeniedException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import com.xiaoleilu.hutool.io.FileUtil;
import com.xiaoleilu.hutool.io.IORuntimeException;
import com.xiaoleilu.hutool.io.IoUtil;
import com.xiaoleilu.hutool.io.watch.watchers.WatcherChain;
import com.xiaoleilu.hutool.util.ArrayUtil;
import com.xiaoleilu.hutool.util.StrUtil;
import com.xiaoleilu.hutool.util.URLUtil;
/**
* 路径监听器
* 监听器可监听目录或文件
* 如果监听的Path不存在,则递归创建空目录然后监听此空目录
* 递归监听目录时,并不会监听新创建的目录
*
* @author Looly
*
*/
public class WatchMonitor extends Thread implements Closeable{
/** 事件丢失 */
public static final WatchEvent.Kind> OVERFLOW = StandardWatchEventKinds.OVERFLOW;
/** 修改事件 */
public static final WatchEvent.Kind> ENTRY_MODIFY = StandardWatchEventKinds.ENTRY_MODIFY;
/** 创建事件 */
public static final WatchEvent.Kind> ENTRY_CREATE = StandardWatchEventKinds.ENTRY_CREATE;
/** 删除事件 */
public static final WatchEvent.Kind> ENTRY_DELETE = StandardWatchEventKinds.ENTRY_DELETE;
/** 全部事件 */
public static final WatchEvent.Kind>[] EVENTS_ALL = {//
StandardWatchEventKinds.OVERFLOW, //事件丢失
StandardWatchEventKinds.ENTRY_MODIFY, //修改
StandardWatchEventKinds.ENTRY_CREATE, //创建
StandardWatchEventKinds.ENTRY_DELETE //删除
};
/** 监听路径,必须为目录 */
private Path path;
/** 递归目录的最大深度,当小于1时不递归下层目录 */
private int maxDepth;
/** 监听的文件,对于单文件监听不为空 */
private Path filePath;
/** 监听服务 */
private WatchService watchService;
/** 监听器 */
private Watcher watcher;
/** 监听事件列表 */
private WatchEvent.Kind>[] events;
/** 监听是否已经关闭 */
private boolean isClosed;
/** WatchKey 和 Path的对应表 */
private Map watchKeyPathMap = new HashMap<>();
//------------------------------------------------------ Static method start
/**
* 创建并初始化监听
* @param url URL
* @param events 监听的事件列表
* @return 监听对象
*/
public static WatchMonitor create(URL url, WatchEvent.Kind>... events){
return create(url, 0, events);
}
/**
* 创建并初始化监听
* @param url URL
* @param events 监听的事件列表
* @param maxDepth 当监听目录时,监听目录的最大深度,当设置值为1(或小于1)时,表示不递归监听子目录
* @return 监听对象
*/
public static WatchMonitor create(URL url, int maxDepth, WatchEvent.Kind>... events){
return create(URLUtil.toURI(url), maxDepth, events);
}
/**
* 创建并初始化监听
* @param uri URI
* @param events 监听的事件列表
* @return 监听对象
*/
public static WatchMonitor create(URI uri, WatchEvent.Kind>... events){
return create(uri, 0, events);
}
/**
* 创建并初始化监听
* @param uri URI
* @param events 监听的事件列表
* @param maxDepth 当监听目录时,监听目录的最大深度,当设置值为1(或小于1)时,表示不递归监听子目录
* @return 监听对象
*/
public static WatchMonitor create(URI uri, int maxDepth, WatchEvent.Kind>... events){
return create(Paths.get(uri), maxDepth, events);
}
/**
* 创建并初始化监听
* @param file 文件
* @param events 监听的事件列表
* @return 监听对象
*/
public static WatchMonitor create(File file, WatchEvent.Kind>... events){
return create(file, 0, events);
}
/**
* 创建并初始化监听
* @param file 文件
* @param events 监听的事件列表
* @param maxDepth 当监听目录时,监听目录的最大深度,当设置值为1(或小于1)时,表示不递归监听子目录
* @return 监听对象
*/
public static WatchMonitor create(File file, int maxDepth, WatchEvent.Kind>... events){
return create(file.toPath(), maxDepth, events);
}
/**
* 创建并初始化监听
* @param path 路径
* @param events 监听的事件列表
* @return 监听对象
*/
public static WatchMonitor create(String path, WatchEvent.Kind>... events){
return create(path, 0, events);
}
/**
* 创建并初始化监听
* @param path 路径
* @param events 监听的事件列表
* @param maxDepth 当监听目录时,监听目录的最大深度,当设置值为1(或小于1)时,表示不递归监听子目录
* @return 监听对象
*/
public static WatchMonitor create(String path, int maxDepth, WatchEvent.Kind>... events){
return create(Paths.get(path), maxDepth, events);
}
/**
* 创建并初始化监听
* @param path 路径
* @param events 监听事件列表
* @return 监听对象
*/
public static WatchMonitor create(Path path, WatchEvent.Kind>... events){
return create(path, 0, events);
}
/**
* 创建并初始化监听
* @param path 路径
* @param events 监听事件列表
* @param maxDepth 当监听目录时,监听目录的最大深度,当设置值为1(或小于1)时,表示不递归监听子目录
* @return 监听对象
*/
public static WatchMonitor create(Path path, int maxDepth, WatchEvent.Kind>... events){
return new WatchMonitor(path, maxDepth, events);
}
//--------- createAll
/**
* 创建并初始化监听,监听所有事件
* @param uri URI
* @param watcher {@link Watcher}
* @return {@link WatchMonitor}
*/
public static WatchMonitor createAll(URI uri, Watcher watcher){
return createAll(Paths.get(uri), watcher);
}
/**
* 创建并初始化监听,监听所有事件
* @param url URL
* @param watcher {@link Watcher}
* @return {@link WatchMonitor}
*/
public static WatchMonitor createAll(URL url, Watcher watcher){
try {
return createAll(Paths.get(url.toURI()), watcher);
} catch (URISyntaxException e) {
throw new WatchException(e);
}
}
/**
* 创建并初始化监听,监听所有事件
* @param file 被监听文件
* @param watcher {@link Watcher}
* @return {@link WatchMonitor}
*/
public static WatchMonitor createAll(File file, Watcher watcher){
return createAll(file.toPath(), watcher);
}
/**
* 创建并初始化监听,监听所有事件
* @param path 路径
* @param watcher {@link Watcher}
* @return {@link WatchMonitor}
*/
public static WatchMonitor createAll(String path, Watcher watcher){
return createAll(Paths.get(path), watcher);
}
/**
* 创建并初始化监听,监听所有事件
* @param path 路径
* @param watcher {@link Watcher}
* @return {@link WatchMonitor}
*/
public static WatchMonitor createAll(Path path, Watcher watcher){
final WatchMonitor watchMonitor = create(path, EVENTS_ALL);
watchMonitor.setWatcher(watcher);
return watchMonitor;
}
//------------------------------------------------------ Static method end
//------------------------------------------------------ Constructor method start
/**
* 构造
* @param file 文件
* @param events 监听的事件列表
*/
public WatchMonitor(File file, WatchEvent.Kind>... events) {
this(file.toPath(), events);
}
/**
* 构造
* @param path 字符串路径
* @param events 监听的事件列表
*/
public WatchMonitor(String path, WatchEvent.Kind>... events) {
this(Paths.get(path), events);
}
/**
* 构造
* @param path 字符串路径
* @param events 监听事件列表
*/
public WatchMonitor(Path path, WatchEvent.Kind>... events) {
this(path, 0, events);
}
/**
* 构造
* 例如设置:
*
* maxDepth <= 1 表示只监听当前目录
* maxDepth = 2 表示监听当前目录以及下层目录
* maxDepth = 3 表示监听当前目录以及下层
*
*
* @param path 字符串路径
* @param maxDepth 递归目录的最大深度,当小于2时不递归下层目录
* @param events 监听事件列表
*/
public WatchMonitor(Path path, int maxDepth, WatchEvent.Kind>... events) {
this.path = path;
this.maxDepth = maxDepth;
this.events = events;
this.init();
}
//------------------------------------------------------ Constructor method end
/**
* 初始化
* 初始化包括:
*
* 1、解析传入的路径,判断其为目录还是文件
* 2、创建{@link WatchService} 对象
*
*
* @throws WatchException 监听异常,IO异常时抛出此异常
*/
public void init() throws WatchException{
//获取目录或文件路径
if(false ==Files.exists(this.path, LinkOption.NOFOLLOW_LINKS)) {
final Path lastPathEle = FileUtil.getLastPathEle(this.path);
if(null != lastPathEle) {
final String lastPathEleStr = lastPathEle.toString();
//带有点表示有扩展名,按照未创建的文件对待。Linux下.d的为目录,排除之
if(StrUtil.contains(lastPathEleStr, StrUtil.C_DOT) && false ==StrUtil.endWithIgnoreCase(lastPathEleStr, ".d")) {
this.filePath = this.path;
this.path = this.filePath.getParent();
}
}
//创建不存在的目录或父目录
try {
Files.createDirectories(this.path);
} catch (IOException e) {
throw new IORuntimeException(e);
}
}else if(Files.isRegularFile(this.path, LinkOption.NOFOLLOW_LINKS)){
this.filePath = this.path;
this.path = this.filePath.getParent();
}
//初始化监听
try {
watchService = FileSystems.getDefault().newWatchService();
} catch (IOException e) {
throw new WatchException(e);
}
isClosed = false;
}
/**
* 设置监听
* 多个监听请使用{@link WatcherChain}
*
* @param watcher 监听
* @return {@link WatchMonitor}
*/
public WatchMonitor setWatcher(Watcher watcher){
this.watcher = watcher;
return this;
}
@Override
public void run() {
watch();
}
/**
* 开始监听事件,阻塞当前进程
*/
public void watch(){
watch(this.watcher);
}
/**
* 开始监听事件,阻塞当前进程
* @param watcher 监听
* @throws WatchException 监听异常,如果监听关闭抛出此异常
*/
public void watch(Watcher watcher) throws WatchException{
if(isClosed){
throw new WatchException("Watch Monitor is closed !");
}
registerPath();
// log.debug("Start watching path: [{}]", this.path);
while (false == isClosed) {
WatchKey wk;
try {
wk = watchService.take();
} catch (InterruptedException e) {
// log.warn(e);
return;
}
final Path currentPath = watchKeyPathMap.get(wk);
WatchEvent.Kind> kind;
for (WatchEvent> event : wk.pollEvents()) {
kind = event.kind();
if(null != this.filePath && false == this.filePath.endsWith(event.context().toString())){
// log.debug("[{}] is not fit for [{}], pass it.", event.context(), this.filePath.getFileName());
continue;
}
if(kind == StandardWatchEventKinds.ENTRY_CREATE){
watcher.onCreate(event, currentPath);
}else if(kind == StandardWatchEventKinds.ENTRY_MODIFY){
watcher.onModify(event, currentPath);
}else if(kind == StandardWatchEventKinds.ENTRY_DELETE){
watcher.onDelete(event, currentPath);
}else if(kind == StandardWatchEventKinds.OVERFLOW){
watcher.onOverflow(event, currentPath);
}
}
wk.reset();
}
}
/**
* 当监听目录时,监听目录的最大深度
* 当设置值为1(或小于1)时,表示不递归监听子目录
* 例如设置:
*
* maxDepth <= 1 表示只监听当前目录
* maxDepth = 2 表示监听当前目录以及下层目录
* maxDepth = 3 表示监听当前目录以及下层
*
*
* @param maxDepth 最大深度,当设置值为1(或小于1)时,表示不递归监听子目录,监听所有子目录请传{@link Integer#MAX_VALUE}
* @return this
*/
public WatchMonitor setMaxDepth(int maxDepth) {
this.maxDepth = maxDepth;
return this;
}
/**
* 关闭监听
*/
@Override
public void close(){
isClosed = true;
IoUtil.close(watchService);
}
//------------------------------------------------------ private method start
/**
* 注册监听路径
*/
private void registerPath() {
registerPath(this.path, (null != this.filePath) ? 0 : this.maxDepth);
}
/**
* 将指定路径加入到监听中
* @param path 路径
* @param maxDepth 递归下层目录的最大深度
* @return {@link WatchKey}
*/
private void registerPath(Path path, int maxDepth) {
try {
final WatchKey key = path.register(watchService, ArrayUtil.isEmpty(this.events) ? EVENTS_ALL : this.events);
watchKeyPathMap.put(key, 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);
}
});
}
} catch (IOException e) {
if(e instanceof AccessDeniedException) {
//对于禁止访问的目录,跳过监听
return;
}
throw new WatchException(e);
}
}
//------------------------------------------------------ private method end
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy