fix WatchMonitor

This commit is contained in:
Looly 2023-12-29 02:53:51 +08:00
parent bedee44420
commit 76bcbfe67c
15 changed files with 384 additions and 492 deletions

View File

@ -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
/**

View File

@ -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;
}
/**
* 将指定对象强制转换为指定类型
*

View File

@ -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;

View File

@ -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>

View File

@ -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) {

View File

@ -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
}

View File

@ -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>
* 1com.sun.nio.file.StandardWatchEventKinds
* 2com.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);
}
}

View File

@ -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>
* 1com.sun.nio.file.StandardWatchEventKinds
* 2com.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();
}
}

View File

@ -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服务

View File

@ -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);
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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;

View File

@ -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;