mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
fix WatchMonitor
This commit is contained in:
parent
bedee44420
commit
76bcbfe67c
@ -30,6 +30,7 @@ import org.dromara.hutool.core.util.RandomUtil;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
@ -467,6 +468,7 @@ public class ArrayUtil extends PrimitiveArrayUtil {
|
||||
// endregion
|
||||
|
||||
// region ----- newArray
|
||||
|
||||
/**
|
||||
* 新建一个空数组
|
||||
*
|
||||
@ -493,6 +495,7 @@ public class ArrayUtil extends PrimitiveArrayUtil {
|
||||
// endregion
|
||||
|
||||
// region ----- type
|
||||
|
||||
/**
|
||||
* 获取数组对象的元素类型,方法调用参数与返回结果举例:
|
||||
* <ul>
|
||||
@ -1048,6 +1051,7 @@ public class ArrayUtil extends PrimitiveArrayUtil {
|
||||
// endregion
|
||||
|
||||
// region ----- zip
|
||||
|
||||
/**
|
||||
* 映射键值(参考Python的zip()函数)<br>
|
||||
* 例如:<br>
|
||||
@ -1422,6 +1426,7 @@ public class ArrayUtil extends PrimitiveArrayUtil {
|
||||
}
|
||||
|
||||
// region ----- join
|
||||
|
||||
/**
|
||||
* 以 conjunction 为分隔符将数组转换为字符串
|
||||
*
|
||||
@ -1840,6 +1845,21 @@ public class ArrayUtil extends PrimitiveArrayUtil {
|
||||
public static <T, R> Set<R> mapToSet(final T[] array, final Function<? super T, ? extends R> func) {
|
||||
return Arrays.stream(array).map(func).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 按照指定规则,将一种类型的数组元素转换为另一种类型,并保存为 {@link Set}
|
||||
*
|
||||
* @param array 被转换的数组
|
||||
* @param func 转换规则函数
|
||||
* @param generator 数组生成器,如返回String[],则传入String[]::new
|
||||
* @param <T> 原数组类型
|
||||
* @param <R> 目标数组类型
|
||||
* @return 集合
|
||||
*/
|
||||
public static <T, R> R[] mapToArray(final T[] array, final Function<? super T, ? extends R> func,
|
||||
final IntFunction<R[]> generator) {
|
||||
return Arrays.stream(array).map(func).toArray(generator);
|
||||
}
|
||||
// endregion
|
||||
|
||||
/**
|
||||
|
@ -27,6 +27,18 @@ import java.util.Set;
|
||||
*/
|
||||
public class CastUtil {
|
||||
|
||||
/**
|
||||
* 将指定对象强制转换为指定类型
|
||||
*
|
||||
* @param <T> 目标类型
|
||||
* @param value 被转换的对象
|
||||
* @return 转换后的对象
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T cast(final Object value) {
|
||||
return (T)value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定对象强制转换为指定类型
|
||||
*
|
||||
|
@ -13,7 +13,7 @@
|
||||
package org.dromara.hutool.core.io.file;
|
||||
|
||||
import org.dromara.hutool.core.io.IORuntimeException;
|
||||
import org.dromara.hutool.core.io.watch.SimpleWatcher;
|
||||
import org.dromara.hutool.core.io.watch.watchers.SimpleWatcher;
|
||||
import org.dromara.hutool.core.func.SerConsumer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -12,7 +12,6 @@
|
||||
|
||||
package org.dromara.hutool.core.io.file;
|
||||
|
||||
import org.dromara.hutool.core.collection.set.SetUtil;
|
||||
import org.dromara.hutool.core.io.IORuntimeException;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.util.CharsetUtil;
|
||||
@ -115,6 +114,7 @@ public class PathUtil {
|
||||
return fileList;
|
||||
}
|
||||
|
||||
// region ----- walkFiles
|
||||
/**
|
||||
* 遍历指定path下的文件并做处理
|
||||
*
|
||||
@ -162,6 +162,7 @@ public class PathUtil {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* 删除文件或者文件夹,不追踪软链<br>
|
||||
|
@ -16,9 +16,10 @@ import org.dromara.hutool.core.date.DateUnit;
|
||||
import org.dromara.hutool.core.exception.HutoolException;
|
||||
import org.dromara.hutool.core.io.IORuntimeException;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.io.watch.SimpleWatcher;
|
||||
import org.dromara.hutool.core.io.watch.watchers.SimpleWatcher;
|
||||
import org.dromara.hutool.core.io.watch.WatchKind;
|
||||
import org.dromara.hutool.core.io.watch.WatchMonitor;
|
||||
import org.dromara.hutool.core.io.watch.WatchUtil;
|
||||
import org.dromara.hutool.core.lang.Console;
|
||||
import org.dromara.hutool.core.func.SerConsumer;
|
||||
import org.dromara.hutool.core.text.CharUtil;
|
||||
@ -158,7 +159,7 @@ public class Tailer implements Serializable {
|
||||
|
||||
// 监听删除
|
||||
if(stopOnDelete){
|
||||
fileDeleteWatchMonitor = WatchMonitor.of(this.filePath, WatchKind.DELETE.getValue());
|
||||
fileDeleteWatchMonitor = WatchUtil.of(this.filePath, WatchKind.DELETE.getValue());
|
||||
fileDeleteWatchMonitor.setWatcher(new SimpleWatcher(){
|
||||
@Override
|
||||
public void onDelete(final WatchEvent<?> event, final Path currentPath) {
|
||||
|
@ -15,15 +15,15 @@ package org.dromara.hutool.core.io.watch;
|
||||
import org.dromara.hutool.core.io.file.FileUtil;
|
||||
import org.dromara.hutool.core.io.file.PathUtil;
|
||||
import org.dromara.hutool.core.io.watch.watchers.WatcherChain;
|
||||
import org.dromara.hutool.core.net.url.UrlUtil;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.core.text.CharUtil;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.*;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.WatchEvent;
|
||||
import java.nio.file.WatchService;
|
||||
|
||||
/**
|
||||
* 路径监听器
|
||||
@ -35,237 +35,37 @@ import java.nio.file.*;
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class WatchMonitor extends WatchServer {
|
||||
public class WatchMonitor extends Thread implements Closeable, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final WatchServiceWrapper watchService;
|
||||
|
||||
/**
|
||||
* 监听路径,必须为目录
|
||||
*/
|
||||
private Path path;
|
||||
/**
|
||||
* 递归目录的最大深度,当小于1时不递归下层目录
|
||||
*/
|
||||
private int maxDepth;
|
||||
private Path dir;
|
||||
/**
|
||||
* 监听的文件,对于单文件监听不为空
|
||||
*/
|
||||
private Path filePath;
|
||||
|
||||
/**
|
||||
* 递归目录的最大深度,当小于1时不递归下层目录
|
||||
*/
|
||||
private int maxDepth;
|
||||
/**
|
||||
* 监听器
|
||||
*/
|
||||
private Watcher watcher;
|
||||
//------------------------------------------------------ Static method start
|
||||
|
||||
/**
|
||||
* 创建并初始化监听
|
||||
*
|
||||
* @param url URL
|
||||
* @param events 监听的事件列表,见{@link WatchKind}
|
||||
* @return 监听对象
|
||||
*/
|
||||
public static WatchMonitor of(final URL url, final WatchEvent.Kind<?>... events) {
|
||||
return of(url, 0, events);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建并初始化监听
|
||||
*
|
||||
* @param url URL
|
||||
* @param events 监听的事件列表,见{@link WatchKind}
|
||||
* @param maxDepth 当监听目录时,监听目录的最大深度,当设置值为1(或小于1)时,表示不递归监听子目录
|
||||
* @return 监听对象
|
||||
*/
|
||||
public static WatchMonitor of(final URL url, final int maxDepth, final WatchEvent.Kind<?>... events) {
|
||||
return of(UrlUtil.toURI(url), maxDepth, events);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建并初始化监听
|
||||
*
|
||||
* @param uri URI
|
||||
* @param events 监听的事件列表,见{@link WatchKind}
|
||||
* @return 监听对象
|
||||
*/
|
||||
public static WatchMonitor of(final URI uri, final WatchEvent.Kind<?>... events) {
|
||||
return of(uri, 0, events);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建并初始化监听
|
||||
*
|
||||
* @param uri URI
|
||||
* @param events 监听的事件列表,见{@link WatchKind}
|
||||
* @param maxDepth 当监听目录时,监听目录的最大深度,当设置值为1(或小于1)时,表示不递归监听子目录
|
||||
* @return 监听对象
|
||||
*/
|
||||
public static WatchMonitor of(final URI uri, final int maxDepth, final WatchEvent.Kind<?>... events) {
|
||||
return of(Paths.get(uri), maxDepth, events);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建并初始化监听
|
||||
*
|
||||
* @param file 文件
|
||||
* @param events 监听的事件列表,见{@link WatchKind}
|
||||
* @return 监听对象
|
||||
*/
|
||||
public static WatchMonitor of(final File file, final WatchEvent.Kind<?>... events) {
|
||||
return of(file, 0, events);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建并初始化监听
|
||||
*
|
||||
* @param file 文件
|
||||
* @param events 监听的事件列表,见{@link WatchKind}
|
||||
* @param maxDepth 当监听目录时,监听目录的最大深度,当设置值为1(或小于1)时,表示不递归监听子目录
|
||||
* @return 监听对象
|
||||
*/
|
||||
public static WatchMonitor of(final File file, final int maxDepth, final WatchEvent.Kind<?>... events) {
|
||||
return of(file.toPath(), maxDepth, events);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建并初始化监听
|
||||
*
|
||||
* @param path 路径
|
||||
* @param events 监听的事件列表,见{@link WatchKind}
|
||||
* @return 监听对象
|
||||
*/
|
||||
public static WatchMonitor of(final String path, final WatchEvent.Kind<?>... events) {
|
||||
return of(path, 0, events);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建并初始化监听
|
||||
*
|
||||
* @param path 路径
|
||||
* @param events 监听的事件列表,见{@link WatchKind}
|
||||
* @param maxDepth 当监听目录时,监听目录的最大深度,当设置值为1(或小于1)时,表示不递归监听子目录
|
||||
* @return 监听对象
|
||||
*/
|
||||
public static WatchMonitor of(final String path, final int maxDepth, final WatchEvent.Kind<?>... events) {
|
||||
return of(Paths.get(path), maxDepth, events);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建并初始化监听
|
||||
*
|
||||
* @param path 路径
|
||||
* @param events 监听事件列表,见{@link WatchKind}
|
||||
* @return 监听对象
|
||||
*/
|
||||
public static WatchMonitor of(final Path path, final WatchEvent.Kind<?>... events) {
|
||||
return of(path, 0, events);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建并初始化监听
|
||||
*
|
||||
* @param path 路径
|
||||
* @param events 监听事件列表,见{@link WatchKind}
|
||||
* @param maxDepth 当监听目录时,监听目录的最大深度,当设置值为1(或小于1)时,表示不递归监听子目录
|
||||
* @return 监听对象
|
||||
*/
|
||||
public static WatchMonitor of(final Path path, final int maxDepth, final WatchEvent.Kind<?>... events) {
|
||||
return new WatchMonitor(path, maxDepth, events);
|
||||
}
|
||||
|
||||
//--------- createAll
|
||||
|
||||
/**
|
||||
* 创建并初始化监听,监听所有事件
|
||||
*
|
||||
* @param uri URI
|
||||
* @param watcher {@link Watcher}
|
||||
* @return WatchMonitor
|
||||
*/
|
||||
public static WatchMonitor ofAll(final URI uri, final Watcher watcher) {
|
||||
return ofAll(Paths.get(uri), watcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建并初始化监听,监听所有事件
|
||||
*
|
||||
* @param url URL
|
||||
* @param watcher {@link Watcher}
|
||||
* @return WatchMonitor
|
||||
*/
|
||||
public static WatchMonitor ofAll(final URL url, final Watcher watcher) {
|
||||
try {
|
||||
return ofAll(Paths.get(url.toURI()), watcher);
|
||||
} catch (final URISyntaxException e) {
|
||||
throw new WatchException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建并初始化监听,监听所有事件
|
||||
*
|
||||
* @param file 被监听文件
|
||||
* @param watcher {@link Watcher}
|
||||
* @return WatchMonitor
|
||||
*/
|
||||
public static WatchMonitor ofAll(final File file, final Watcher watcher) {
|
||||
return ofAll(file.toPath(), watcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建并初始化监听,监听所有事件
|
||||
*
|
||||
* @param path 路径
|
||||
* @param watcher {@link Watcher}
|
||||
* @return WatchMonitor
|
||||
*/
|
||||
public static WatchMonitor ofAll(final String path, final Watcher watcher) {
|
||||
return ofAll(Paths.get(path), watcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建并初始化监听,监听所有事件
|
||||
*
|
||||
* @param path 路径
|
||||
* @param watcher {@link Watcher}
|
||||
* @return WatchMonitor
|
||||
*/
|
||||
public static WatchMonitor ofAll(final Path path, final Watcher watcher) {
|
||||
final WatchMonitor watchMonitor = of(path, WatchKind.ALL);
|
||||
watchMonitor.setWatcher(watcher);
|
||||
return watchMonitor;
|
||||
}
|
||||
//------------------------------------------------------ Static method end
|
||||
|
||||
//------------------------------------------------------ Constructor method start
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param file 文件
|
||||
* @param events 监听的事件列表
|
||||
*/
|
||||
public WatchMonitor(final File file, final WatchEvent.Kind<?>... events) {
|
||||
this(file.toPath(), events);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param path 字符串路径
|
||||
* @param events 监听的事件列表
|
||||
*/
|
||||
public WatchMonitor(final String path, final WatchEvent.Kind<?>... events) {
|
||||
this(Paths.get(path), events);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param path 字符串路径
|
||||
* @param dir 字符串路径
|
||||
* @param events 监听事件列表
|
||||
*/
|
||||
public WatchMonitor(final Path path, final WatchEvent.Kind<?>... events) {
|
||||
this(path, 0, events);
|
||||
public WatchMonitor(final Path dir, final WatchEvent.Kind<?>... events) {
|
||||
this(dir, 0, events);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -277,53 +77,16 @@ public class WatchMonitor extends WatchServer {
|
||||
* maxDepth = 3 表示监听当前目录以及下两层
|
||||
* </pre>
|
||||
*
|
||||
* @param path 字符串路径
|
||||
* @param dir 字符串路径
|
||||
* @param maxDepth 递归目录的最大深度,当小于2时不递归下层目录
|
||||
* @param events 监听事件列表
|
||||
*/
|
||||
public WatchMonitor(final Path path, final int maxDepth, final WatchEvent.Kind<?>... events) {
|
||||
this.path = path;
|
||||
public WatchMonitor(final Path dir, final int maxDepth, final WatchEvent.Kind<?>... events) {
|
||||
this.watchService = new WatchServiceWrapper().setEvents(events);
|
||||
this.dir = dir;
|
||||
this.maxDepth = maxDepth;
|
||||
this.events = events;
|
||||
this.init();
|
||||
}
|
||||
//------------------------------------------------------ Constructor method end
|
||||
|
||||
/**
|
||||
* 初始化<br>
|
||||
* 初始化包括:
|
||||
* <pre>
|
||||
* 1、解析传入的路径,判断其为目录还是文件
|
||||
* 2、创建{@link WatchService} 对象
|
||||
* </pre>
|
||||
*
|
||||
* @throws WatchException 监听异常,IO异常时抛出此异常
|
||||
*/
|
||||
@Override
|
||||
public void init() throws WatchException {
|
||||
//获取目录或文件路径
|
||||
if (!PathUtil.exists(this.path, false)) {
|
||||
// 不存在的路径
|
||||
final Path lastPathEle = FileUtil.getLastPathEle(this.path);
|
||||
if (null != lastPathEle) {
|
||||
final String lastPathEleStr = lastPathEle.toString();
|
||||
//带有点表示有扩展名,按照未创建的文件对待。Linux下.d的为目录,排除之
|
||||
if (StrUtil.contains(lastPathEleStr, CharUtil.DOT) && !StrUtil.endWithIgnoreCase(lastPathEleStr, ".d")) {
|
||||
this.filePath = this.path;
|
||||
this.path = this.filePath.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
//创建不存在的目录或父目录
|
||||
PathUtil.mkdir(this.path);
|
||||
} else if (PathUtil.isFile(this.path, false)) {
|
||||
// 文件路径
|
||||
this.filePath = this.path;
|
||||
this.path = this.filePath.getParent();
|
||||
}
|
||||
|
||||
super.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置监听<br>
|
||||
@ -356,7 +119,7 @@ public class WatchMonitor extends WatchServer {
|
||||
* @throws WatchException 监听异常,如果监听关闭抛出此异常
|
||||
*/
|
||||
public void watch(final Watcher watcher) throws WatchException {
|
||||
if (isClosed) {
|
||||
if (this.watchService.isClosed()) {
|
||||
throw new WatchException("Watch Monitor is closed !");
|
||||
}
|
||||
|
||||
@ -364,7 +127,7 @@ public class WatchMonitor extends WatchServer {
|
||||
registerPath();
|
||||
// log.debug("Start watching path: [{}]", this.path);
|
||||
|
||||
while (!isClosed) {
|
||||
while (!this.watchService.isClosed()) {
|
||||
doTakeAndWatch(watcher);
|
||||
}
|
||||
}
|
||||
@ -389,20 +152,58 @@ public class WatchMonitor extends WatchServer {
|
||||
|
||||
//------------------------------------------------------ private method start
|
||||
|
||||
/**
|
||||
* 初始化<br>
|
||||
* 初始化包括:
|
||||
* <pre>
|
||||
* 1、解析传入的路径,判断其为目录还是文件
|
||||
* 2、创建{@link WatchService} 对象
|
||||
* </pre>
|
||||
*
|
||||
* @throws WatchException 监听异常,IO异常时抛出此异常
|
||||
*/
|
||||
private void init() throws WatchException {
|
||||
//获取目录或文件路径
|
||||
if (!PathUtil.exists(this.dir, false)) {
|
||||
// 不存在的路径
|
||||
final Path lastPathEle = FileUtil.getLastPathEle(this.dir);
|
||||
if (null != lastPathEle) {
|
||||
final String lastPathEleStr = lastPathEle.toString();
|
||||
//带有点表示有扩展名,按照未创建的文件对待。Linux下.d的为目录,排除之
|
||||
if (StrUtil.contains(lastPathEleStr, CharUtil.DOT) && !StrUtil.endWithIgnoreCase(lastPathEleStr, ".d")) {
|
||||
this.filePath = this.dir;
|
||||
this.dir = this.filePath.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
//创建不存在的目录或父目录
|
||||
PathUtil.mkdir(this.dir);
|
||||
} else if (PathUtil.isFile(this.dir, false)) {
|
||||
// 文件路径
|
||||
this.filePath = this.dir;
|
||||
this.dir = this.filePath.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行事件获取并处理
|
||||
*
|
||||
* @param watcher {@link Watcher}
|
||||
*/
|
||||
private void doTakeAndWatch(final Watcher watcher) {
|
||||
super.watch(watcher, watchEvent -> null == filePath || filePath.endsWith(watchEvent.context().toString()));
|
||||
this.watchService.watch(watcher, watchEvent -> null == filePath || filePath.endsWith(watchEvent.context().toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册监听路径
|
||||
*/
|
||||
private void registerPath() {
|
||||
registerPath(this.path, (null != this.filePath) ? 0 : this.maxDepth);
|
||||
this.watchService.registerPath(this.dir, (null != this.filePath) ? 0 : this.maxDepth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
this.watchService.close();
|
||||
}
|
||||
//------------------------------------------------------ private method end
|
||||
}
|
||||
|
@ -1,202 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* https://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.core.io.watch;
|
||||
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.func.SerBiConsumer;
|
||||
import org.dromara.hutool.core.array.ArrayUtil;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.nio.file.ClosedWatchServiceException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.FileVisitOption;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
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 java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* 文件监听服务,此服务可以同时监听多个路径。
|
||||
*
|
||||
* @author loolly
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public class WatchServer extends Thread implements Closeable, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 监听服务
|
||||
*/
|
||||
private WatchService watchService;
|
||||
/**
|
||||
* 监听事件列表
|
||||
*/
|
||||
protected WatchEvent.Kind<?>[] events;
|
||||
/**
|
||||
* 监听选项,例如监听频率等
|
||||
*/
|
||||
private WatchEvent.Modifier[] modifiers;
|
||||
/**
|
||||
* 监听是否已经关闭
|
||||
*/
|
||||
protected boolean isClosed;
|
||||
/**
|
||||
* WatchKey 和 Path的对应表
|
||||
*/
|
||||
private final Map<WatchKey, Path> watchKeyPathMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 初始化<br>
|
||||
* 初始化包括:
|
||||
* <pre>
|
||||
* 1、解析传入的路径,判断其为目录还是文件
|
||||
* 2、创建{@link WatchService} 对象
|
||||
* </pre>
|
||||
*
|
||||
* @throws WatchException 监听异常,IO异常时抛出此异常
|
||||
*/
|
||||
public void init() throws WatchException {
|
||||
//初始化监听
|
||||
try {
|
||||
watchService = FileSystems.getDefault().newWatchService();
|
||||
} catch (final IOException e) {
|
||||
throw new WatchException(e);
|
||||
}
|
||||
|
||||
isClosed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置监听选项,例如监听频率等,可设置项包括:
|
||||
*
|
||||
* <pre>
|
||||
* 1、com.sun.nio.file.StandardWatchEventKinds
|
||||
* 2、com.sun.nio.file.SensitivityWatchEventModifier
|
||||
* </pre>
|
||||
*
|
||||
* @param modifiers 监听选项,例如监听频率等
|
||||
*/
|
||||
public void setModifiers(final WatchEvent.Modifier[] modifiers) {
|
||||
this.modifiers = modifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定路径加入到监听中
|
||||
*
|
||||
* @param path 路径
|
||||
* @param maxDepth 递归下层目录的最大深度
|
||||
*/
|
||||
public void registerPath(final Path path, final int maxDepth) {
|
||||
final WatchEvent.Kind<?>[] kinds = ArrayUtil.defaultIfEmpty(this.events, WatchKind.ALL);
|
||||
|
||||
try {
|
||||
final WatchKey key;
|
||||
if (ArrayUtil.isEmpty(this.modifiers)) {
|
||||
key = path.register(this.watchService, kinds);
|
||||
} else {
|
||||
key = path.register(this.watchService, kinds, this.modifiers);
|
||||
}
|
||||
watchKeyPathMap.put(key, path);
|
||||
|
||||
// 递归注册下一层层级的目录
|
||||
if (maxDepth > 1) {
|
||||
//遍历所有子目录并加入监听
|
||||
Files.walkFileTree(path, EnumSet.noneOf(FileVisitOption.class), maxDepth, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
|
||||
registerPath(dir, 0);//继续添加目录
|
||||
return super.postVisitDirectory(dir, exc);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
if (!(e instanceof AccessDeniedException)) {
|
||||
throw new WatchException(e);
|
||||
}
|
||||
|
||||
//对于禁止访问的目录,跳过监听
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行事件获取并处理
|
||||
*
|
||||
* @param action 监听回调函数,实现此函数接口用于处理WatchEvent事件
|
||||
* @param watchFilter 监听过滤接口,通过实现此接口过滤掉不需要监听的情况,{@link Predicate#test(Object)}为{@code true}保留,null表示不过滤
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public void watch(final SerBiConsumer<WatchEvent<?>, Path> action, final Predicate<WatchEvent<?>> watchFilter) {
|
||||
final WatchKey wk;
|
||||
try {
|
||||
wk = watchService.take();
|
||||
} catch (final InterruptedException | ClosedWatchServiceException e) {
|
||||
// 用户中断
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
final Path currentPath = watchKeyPathMap.get(wk);
|
||||
|
||||
for (final WatchEvent<?> event : wk.pollEvents()) {
|
||||
// 如果监听文件,检查当前事件是否与所监听文件关联
|
||||
if (null != watchFilter && !watchFilter.test(event)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
action.accept(event, currentPath);
|
||||
}
|
||||
|
||||
wk.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行事件获取并处理
|
||||
*
|
||||
* @param watcher {@link Watcher}
|
||||
* @param watchFilter 监听过滤接口,通过实现此接口过滤掉不需要监听的情况,{@link Predicate#test(Object)}为{@code true}保留,null表示不过滤
|
||||
*/
|
||||
public void watch(final Watcher watcher, final Predicate<WatchEvent<?>> watchFilter) {
|
||||
watch((event, currentPath)->{
|
||||
final WatchEvent.Kind<?> kind = event.kind();
|
||||
|
||||
if (kind == WatchKind.CREATE.getValue()) {
|
||||
watcher.onCreate(event, currentPath);
|
||||
} else if (kind == WatchKind.MODIFY.getValue()) {
|
||||
watcher.onModify(event, currentPath);
|
||||
} else if (kind == WatchKind.DELETE.getValue()) {
|
||||
watcher.onDelete(event, currentPath);
|
||||
} else if (kind == WatchKind.OVERFLOW.getValue()) {
|
||||
watcher.onOverflow(event, currentPath);
|
||||
}
|
||||
}, watchFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭监听
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
isClosed = true;
|
||||
IoUtil.closeQuietly(watchService);
|
||||
}
|
||||
}
|
@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright (c) 2023. looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* https://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.core.io.watch;
|
||||
|
||||
import org.dromara.hutool.core.array.ArrayUtil;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.io.file.PathUtil;
|
||||
import org.dromara.hutool.core.lang.wrapper.Wrapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.nio.file.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* {@link WatchEvent} 包装类,提供可选的监听事件和监听选项<br>
|
||||
* 通过多次调用{@link #registerPath(Path, int)} 可以递归注册多个路径
|
||||
*
|
||||
* @author looly
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class WatchServiceWrapper implements WatchService, Wrapper<WatchService>, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 监听服务
|
||||
*/
|
||||
private final WatchService watchService;
|
||||
/**
|
||||
* 监听事件列表
|
||||
*/
|
||||
private WatchEvent.Kind<?>[] events;
|
||||
/**
|
||||
* 监听选项,例如监听频率等
|
||||
*/
|
||||
private WatchEvent.Modifier[] modifiers;
|
||||
/**
|
||||
* 监听是否已经关闭
|
||||
*/
|
||||
private boolean isClosed;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public WatchServiceWrapper() {
|
||||
//初始化监听
|
||||
try {
|
||||
watchService = FileSystems.getDefault().newWatchService();
|
||||
} catch (final IOException e) {
|
||||
throw new WatchException(e);
|
||||
}
|
||||
|
||||
isClosed = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WatchService getRaw() {
|
||||
return this.watchService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否已关闭
|
||||
*
|
||||
* @return 是否已关闭
|
||||
*/
|
||||
public boolean isClosed() {
|
||||
return this.isClosed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (!this.isClosed) {
|
||||
this.isClosed = true;
|
||||
IoUtil.closeQuietly(this.watchService);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WatchKey poll() {
|
||||
return this.watchService.poll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WatchKey poll(final long timeout, final TimeUnit unit) throws InterruptedException {
|
||||
return this.watchService.poll(timeout, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WatchKey take() throws InterruptedException {
|
||||
return this.watchService.take();
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听事件列表,见:StandardWatchEventKinds
|
||||
*
|
||||
* @param kinds 事件列表
|
||||
* @return this
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
public WatchServiceWrapper setEvents(final WatchKind... kinds) {
|
||||
if (ArrayUtil.isNotEmpty(kinds)) {
|
||||
setEvents(ArrayUtil.mapToArray(kinds, WatchKind::getValue, WatchEvent.Kind<?>[]::new));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听事件列表,见:StandardWatchEventKinds
|
||||
*
|
||||
* @param events 事件列表
|
||||
* @return this
|
||||
*/
|
||||
public WatchServiceWrapper setEvents(final WatchEvent.Kind<?>... events) {
|
||||
this.events = events;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置监听选项,例如监听频率等,可设置项包括:
|
||||
*
|
||||
* <pre>
|
||||
* 1、com.sun.nio.file.StandardWatchEventKinds
|
||||
* 2、com.sun.nio.file.SensitivityWatchEventModifier
|
||||
* </pre>
|
||||
*
|
||||
* @param modifiers 监听选项,例如监听频率等
|
||||
* @return this
|
||||
*/
|
||||
public WatchServiceWrapper setModifiers(final WatchEvent.Modifier... modifiers) {
|
||||
this.modifiers = modifiers;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册单一的监听
|
||||
*
|
||||
* @param watchable 可注册对象,如Path
|
||||
* @return {@link WatchKey},如果为{@code null},表示注册失败
|
||||
*/
|
||||
public WatchKey register(final Watchable watchable) {
|
||||
final WatchEvent.Kind<?>[] kinds = ArrayUtil.defaultIfEmpty(this.events, WatchKind.ALL);
|
||||
|
||||
WatchKey watchKey = null;
|
||||
try {
|
||||
if (ArrayUtil.isEmpty(this.modifiers)) {
|
||||
watchKey = watchable.register(this.watchService, kinds);
|
||||
} else {
|
||||
watchKey = watchable.register(this.watchService, kinds, this.modifiers);
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
if (!(e instanceof AccessDeniedException)) {
|
||||
throw new WatchException(e);
|
||||
}
|
||||
}
|
||||
|
||||
return watchKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归将指定路径加入到监听中<br>
|
||||
* 如果提供的是目录,则监听目录本身和目录下的目录和文件,深度取决于maxDepth
|
||||
*
|
||||
* @param path 路径
|
||||
* @param maxDepth 递归下层目录的最大深度
|
||||
*/
|
||||
public void registerPath(final Path path, final int maxDepth) {
|
||||
// 注册当前目录或文件
|
||||
final WatchKey watchKey = register(path);
|
||||
if (null == watchKey) {
|
||||
// 注册失败,跳过(可能目录或文件无权限)
|
||||
return;
|
||||
}
|
||||
|
||||
// 递归注册下一层层级的目录和文件
|
||||
PathUtil.walkFiles(path, maxDepth, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
|
||||
//继续添加目录
|
||||
registerPath(dir, 0);
|
||||
return super.postVisitDirectory(dir, exc);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行事件获取并处理
|
||||
*
|
||||
* @param watcher {@link Watcher}
|
||||
* @param watchFilter 监听过滤接口,通过实现此接口过滤掉不需要监听的情况,{@link Predicate#test(Object)}为{@code true}保留,null表示不过滤
|
||||
*/
|
||||
public void watch(final Watcher watcher, final Predicate<WatchEvent<?>> watchFilter) {
|
||||
watch((event, watchKey) -> {
|
||||
final WatchEvent.Kind<?> kind = event.kind();
|
||||
|
||||
if (kind == WatchKind.CREATE.getValue()) {
|
||||
watcher.onCreate(event, watchKey);
|
||||
} else if (kind == WatchKind.MODIFY.getValue()) {
|
||||
watcher.onModify(event, watchKey);
|
||||
} else if (kind == WatchKind.DELETE.getValue()) {
|
||||
watcher.onDelete(event, watchKey);
|
||||
} else if (kind == WatchKind.OVERFLOW.getValue()) {
|
||||
watcher.onOverflow(event, watchKey);
|
||||
}
|
||||
}, watchFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行事件获取并处理
|
||||
*
|
||||
* @param action 监听回调函数,实现此函数接口用于处理WatchEvent事件
|
||||
* @param watchFilter 监听过滤接口,通过实现此接口过滤掉不需要监听的情况,{@link Predicate#test(Object)}为{@code true}保留,null表示不过滤
|
||||
*/
|
||||
public void watch(final BiConsumer<WatchEvent<?>, WatchKey> action, final Predicate<WatchEvent<?>> watchFilter) {
|
||||
final WatchKey wk;
|
||||
try {
|
||||
wk = watchService.take();
|
||||
} catch (final InterruptedException | ClosedWatchServiceException e) {
|
||||
// 用户中断
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
for (final WatchEvent<?> event : wk.pollEvents()) {
|
||||
// 如果监听文件,检查当前事件是否与所监听文件关联
|
||||
if (null != watchFilter && !watchFilter.test(event)) {
|
||||
continue;
|
||||
}
|
||||
action.accept(event, wk);
|
||||
}
|
||||
|
||||
wk.reset();
|
||||
}
|
||||
}
|
@ -34,6 +34,8 @@ import java.nio.file.Watchable;
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public class WatchUtil {
|
||||
|
||||
// region ----- of
|
||||
/**
|
||||
* 创建并初始化监听
|
||||
*
|
||||
@ -148,8 +150,9 @@ public class WatchUtil {
|
||||
public static WatchMonitor of(final Path path, final int maxDepth, final WatchEvent.Kind<?>... events) {
|
||||
return new WatchMonitor(path, maxDepth, events);
|
||||
}
|
||||
// endregion
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------- createAll
|
||||
// region ----- ofAll
|
||||
/**
|
||||
* 创建并初始化监听,监听所有事件
|
||||
*
|
||||
@ -266,8 +269,9 @@ public class WatchUtil {
|
||||
watchMonitor.setWatcher(watcher);
|
||||
return watchMonitor;
|
||||
}
|
||||
// endregion
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------- createModify
|
||||
// region ----- createModify
|
||||
/**
|
||||
* 创建并初始化监听,监听修改事件
|
||||
*
|
||||
@ -394,6 +398,7 @@ public class WatchUtil {
|
||||
watchMonitor.setWatcher(watcher);
|
||||
return watchMonitor;
|
||||
}
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* 注册Watchable对象到WatchService服务
|
||||
|
@ -12,8 +12,8 @@
|
||||
|
||||
package org.dromara.hutool.core.io.watch;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.WatchEvent;
|
||||
import java.nio.file.WatchKey;
|
||||
|
||||
/**
|
||||
* 观察者(监视器)
|
||||
@ -24,33 +24,33 @@ public interface Watcher {
|
||||
/**
|
||||
* 文件创建时执行的方法
|
||||
*
|
||||
* @param event 事件
|
||||
* @param currentPath 事件发生的当前Path路径
|
||||
* @param event 事件
|
||||
* @param key 事件发生的{@link WatchKey}
|
||||
*/
|
||||
void onCreate(WatchEvent<?> event, Path currentPath);
|
||||
void onCreate(WatchEvent<?> event, WatchKey key);
|
||||
|
||||
/**
|
||||
* 文件修改时执行的方法<br>
|
||||
* 文件修改可能触发多次
|
||||
*
|
||||
* @param event 事件
|
||||
* @param currentPath 事件发生的当前Path路径
|
||||
* @param event 事件
|
||||
* @param key 事件发生的{@link WatchKey}
|
||||
*/
|
||||
void onModify(WatchEvent<?> event, Path currentPath);
|
||||
void onModify(WatchEvent<?> event, WatchKey key);
|
||||
|
||||
/**
|
||||
* 文件删除时执行的方法
|
||||
*
|
||||
* @param event 事件
|
||||
* @param currentPath 事件发生的当前Path路径
|
||||
* @param event 事件
|
||||
* @param key 事件发生的{@link WatchKey}
|
||||
*/
|
||||
void onDelete(WatchEvent<?> event, Path currentPath);
|
||||
void onDelete(WatchEvent<?> event, WatchKey key);
|
||||
|
||||
/**
|
||||
* 事件丢失或出错时执行的方法
|
||||
*
|
||||
* @param event 事件
|
||||
* @param currentPath 事件发生的当前Path路径
|
||||
* @param event 事件
|
||||
* @param key 事件发生的{@link WatchKey}
|
||||
*/
|
||||
void onOverflow(WatchEvent<?> event, Path currentPath);
|
||||
void onOverflow(WatchEvent<?> event, WatchKey key);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
||||
* Copyright (c) 2023. looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
@ -10,7 +10,7 @@
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.core.io.watch;
|
||||
package org.dromara.hutool.core.io.watch.watchers;
|
||||
|
||||
import org.dromara.hutool.core.io.watch.watchers.IgnoreWatcher;
|
||||
|
@ -164,13 +164,20 @@ public class ArrayUtilTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapTest() {
|
||||
public void zipTest() {
|
||||
final String[] keys = {"a", "b", "c"};
|
||||
final Integer[] values = {1, 2, 3};
|
||||
final Map<String, Integer> map = ArrayUtil.zip(keys, values, true);
|
||||
Assertions.assertEquals(Objects.requireNonNull(map).toString(), "{a=1, b=2, c=3}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapToArrayTest() {
|
||||
final String[] keys = {"a", "b", "c"};
|
||||
final Integer[] integers = ArrayUtil.mapToArray(keys, String::length, Integer[]::new);
|
||||
Assertions.assertArrayEquals(integers, new Integer[]{1, 1, 1});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void castTest() {
|
||||
final Object[] values = {"1", "2", "3"};
|
||||
@ -453,7 +460,7 @@ public class ArrayUtilTest {
|
||||
final Object a = new int[]{1, 2, 3, 4};
|
||||
final Object[] wrapA = ArrayUtil.wrap(a);
|
||||
for (final Object o : wrapA) {
|
||||
Assertions.assertTrue(o instanceof Integer);
|
||||
Assertions.assertInstanceOf(Integer.class, o);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,8 +15,9 @@ package org.dromara.hutool.core.io;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.WatchEvent;
|
||||
|
||||
import org.dromara.hutool.core.io.watch.SimpleWatcher;
|
||||
import org.dromara.hutool.core.io.watch.watchers.SimpleWatcher;
|
||||
import org.dromara.hutool.core.io.watch.WatchMonitor;
|
||||
import org.dromara.hutool.core.io.watch.WatchUtil;
|
||||
import org.dromara.hutool.core.io.watch.Watcher;
|
||||
import org.dromara.hutool.core.io.watch.watchers.DelayWatcher;
|
||||
import org.dromara.hutool.core.lang.Console;
|
||||
@ -56,7 +57,8 @@ public class WatchMonitorTest {
|
||||
}
|
||||
};
|
||||
|
||||
final WatchMonitor monitor = WatchMonitor.ofAll("d:/test/aaa.txt", new DelayWatcher(watcher, 500));
|
||||
//noinspection resource
|
||||
final WatchMonitor monitor = WatchUtil.ofAll("d:/test/aaa.txt", new DelayWatcher(watcher, 500));
|
||||
|
||||
monitor.setMaxDepth(0);
|
||||
monitor.start();
|
||||
|
@ -18,7 +18,7 @@ import org.dromara.hutool.core.io.file.FileUtil;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.io.resource.Resource;
|
||||
import org.dromara.hutool.core.io.resource.ResourceUtil;
|
||||
import org.dromara.hutool.core.io.watch.SimpleWatcher;
|
||||
import org.dromara.hutool.core.io.watch.watchers.SimpleWatcher;
|
||||
import org.dromara.hutool.core.io.watch.WatchMonitor;
|
||||
import org.dromara.hutool.core.io.watch.WatchUtil;
|
||||
import org.dromara.hutool.core.lang.Assert;
|
||||
|
@ -22,7 +22,7 @@ import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.io.file.FileUtil;
|
||||
import org.dromara.hutool.core.io.resource.Resource;
|
||||
import org.dromara.hutool.core.io.resource.ResourceUtil;
|
||||
import org.dromara.hutool.core.io.watch.SimpleWatcher;
|
||||
import org.dromara.hutool.core.io.watch.watchers.SimpleWatcher;
|
||||
import org.dromara.hutool.core.io.watch.WatchMonitor;
|
||||
import org.dromara.hutool.core.io.watch.WatchUtil;
|
||||
import org.dromara.hutool.core.lang.Assert;
|
||||
|
Loading…
x
Reference in New Issue
Block a user