From 9029b10a52960633de63483bcb1cc8628e8e8e1f Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 5 Dec 2019 17:28:58 +0800 Subject: [PATCH] add WatchServer --- .../cn/hutool/core/collection/CollUtil.java | 11 +- .../cn/hutool/core/io/watch/WatchKind.java | 57 ++++++ .../cn/hutool/core/io/watch/WatchMonitor.java | 2 +- .../cn/hutool/core/io/watch/WatchServer.java | 173 ++++++++++++++++++ 4 files changed, 237 insertions(+), 6 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/io/watch/WatchKind.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/io/watch/WatchServer.java diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index 8b6377019..df23352d4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -776,6 +776,7 @@ public class CollUtil { } else if (collectionType.isAssignableFrom(LinkedHashSet.class)) { list = new LinkedHashSet<>(); } else if (collectionType.isAssignableFrom(TreeSet.class)) { + //noinspection SortedCollectionWithNonComparableKeys list = new TreeSet<>(); } else if (collectionType.isAssignableFrom(EnumSet.class)) { list = (Collection) EnumSet.noneOf((Class) ClassUtil.getTypeArgument(collectionType)); @@ -2082,7 +2083,7 @@ public class CollUtil { list.addAll(coll); } if (null != comparator) { - Collections.sort(list, comparator); + list.sort(comparator); } return page(pageNo, pageSize, list); @@ -2130,7 +2131,7 @@ public class CollUtil { */ public static List sort(Collection collection, Comparator comparator) { List list = new ArrayList<>(collection); - Collections.sort(list, comparator); + list.sort(comparator); return list; } @@ -2144,7 +2145,7 @@ public class CollUtil { * @see Collections#sort(List, Comparator) */ public static List sort(List list, Comparator c) { - Collections.sort(list, c); + list.sort(c); return list; } @@ -2224,7 +2225,7 @@ public class CollUtil { */ public static LinkedHashMap sortToMap(Collection> entryCollection, Comparator> comparator) { List> list = new LinkedList<>(entryCollection); - Collections.sort(list, comparator); + list.sort(comparator); LinkedHashMap result = new LinkedHashMap<>(); for (Map.Entry entry : list) { @@ -2258,7 +2259,7 @@ public class CollUtil { @SuppressWarnings({"unchecked", "rawtypes"}) public static List> sortEntryToList(Collection> collection) { List> list = new LinkedList<>(collection); - Collections.sort(list, (o1, o2) -> { + list.sort((o1, o2) -> { V v1 = o1.getValue(); V v2 = o2.getValue(); diff --git a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchKind.java b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchKind.java new file mode 100644 index 000000000..e49dd7f0b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchKind.java @@ -0,0 +1,57 @@ +package cn.hutool.core.io.watch; + +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; + +/** + * 监听事件类型枚举,包括: + * + *
+ *      1. 事件丢失 OVERFLOW -》StandardWatchEventKinds.OVERFLOW
+ *      2. 修改事件 MODIFY   -》StandardWatchEventKinds.ENTRY_MODIFY
+ *      3. 创建事件 CREATE   -》StandardWatchEventKinds.ENTRY_CREATE
+ *      4. 删除事件 DELETE   -》StandardWatchEventKinds.ENTRY_DELETE
+ * 
+ * + * @author loolly + * @since 5.1.0 + */ +public enum WatchKind { + + /** + * 事件丢失 + */ + OVERFLOW(StandardWatchEventKinds.OVERFLOW), + /** + * 修改事件 + */ + MODIFY(StandardWatchEventKinds.ENTRY_MODIFY), + /** + * 创建事件 + */ + CREATE(StandardWatchEventKinds.ENTRY_CREATE), + /** + * 删除事件 + */ + DELETE(StandardWatchEventKinds.ENTRY_DELETE); + + private WatchEvent.Kind value; + + /** + * 构造 + * + * @param value 事件类型 + */ + WatchKind(WatchEvent.Kind value) { + this.value = value; + } + + /** + * 获取枚举对应的事件类型 + * + * @return 事件类型值 + */ + public WatchEvent.Kind getValue() { + return this.value; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchMonitor.java b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchMonitor.java index a380466c3..1f5d48f6f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchMonitor.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchMonitor.java @@ -318,7 +318,7 @@ public class WatchMonitor extends Thread implements Closeable, Serializable { *
 	 * maxDepth <= 1 表示只监听当前目录
 	 * maxDepth = 2 表示监听当前目录以及下层目录
-	 * maxDepth = 3 表示监听当前目录以及下层
+	 * maxDepth = 3 表示监听当前目录以及下两层
 	 * 
* * @param path 字符串路径 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchServer.java b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchServer.java new file mode 100644 index 000000000..65baba9f5 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchServer.java @@ -0,0 +1,173 @@ +package cn.hutool.core.io.watch; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Filter; +import cn.hutool.core.util.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.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; + +/** + * 文件监听服务,此服务可以同时监听多个路径。 + * + * @author loolly + * @since 5.1.0 + */ +public class WatchServer extends Thread implements Closeable, Serializable { + private static final long serialVersionUID = 1L; + + /** + * 监听服务 + */ + private WatchService watchService; + /** + * 监听事件列表 + */ + private WatchEvent.Kind[] events; + /** + * 监听选项,例如监听频率等 + */ + private WatchEvent.Modifier[] modifiers; + /** + * 监听是否已经关闭 + */ + private boolean isClosed; + /** + * WatchKey 和 Path的对应表 + */ + private Map watchKeyPathMap = new HashMap<>(); + + /** + * 初始化
+ * 初始化包括: + *
+	 * 1、解析传入的路径,判断其为目录还是文件
+	 * 2、创建{@link WatchService} 对象
+	 * 
+ * + * @throws WatchException 监听异常,IO异常时抛出此异常 + */ + public void init() throws WatchException { + //初始化监听 + try { + watchService = FileSystems.getDefault().newWatchService(); + } catch (IOException e) { + throw new WatchException(e); + } + + isClosed = false; + } + + /** + * 设置监听选项,例如监听频率等,可设置项包括: + * + *
+	 * 1、com.sun.nio.file.StandardWatchEventKinds
+	 * 2、com.sun.nio.file.SensitivityWatchEventModifier
+	 * 
+ * + * @param modifiers 监听选项,例如监听频率等 + */ + public void setModifiers(WatchEvent.Modifier[] modifiers) { + this.modifiers = modifiers; + } + + /** + * 将指定路径加入到监听中 + * + * @param path 路径 + * @param maxDepth 递归下层目录的最大深度 + */ + public void registerPath(Path path, int maxDepth) { + try { + final WatchKey key; + if (ArrayUtil.isEmpty(this.modifiers)) { + key = path.register(this.watchService, this.events); + } else { + key = path.register(this.watchService, this.events, this.modifiers); + } + 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 (false == (e instanceof AccessDeniedException)) { + throw new WatchException(e); + } + + //对于禁止访问的目录,跳过监听 + } + } + + /** + * 执行事件获取并处理 + * + * @param watcher {@link Watcher} + * @param watchFilter 监听过滤接口,通过实现此接口过滤掉不需要监听的情况,null表示不过滤 + */ + public void watch(Watcher watcher, Filter> watchFilter) { + WatchKey wk; + try { + wk = watchService.take(); + } catch (InterruptedException | ClosedWatchServiceException e) { + // 用户中断 + return; + } + + final Path currentPath = watchKeyPathMap.get(wk); + WatchEvent.Kind kind; + for (WatchEvent event : wk.pollEvents()) { + kind = event.kind(); + + // 如果监听文件,检查当前事件是否与所监听文件关联 + if (null != watchFilter && false == watchFilter.accept(event)) { + 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(); + } + + /** + * 关闭监听 + */ + @Override + public void close() { + isClosed = true; + IoUtil.close(watchService); + } +}