From 52fb6267a5a7dc2302a58d5facd30b4599dea571 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 5 Oct 2019 20:54:41 +0800 Subject: [PATCH] add method --- CHANGELOG.md | 1 + .../cn/hutool/core/collection/CollUtil.java | 14 + .../cn/hutool/core/io/watch/WatchMonitor.java | 351 +++++++++++------- .../java/cn/hutool/core/io/watch/Watcher.java | 19 +- .../main/java/cn/hutool/core/map/MapUtil.java | 29 +- .../java/cn/hutool/core/util/ArrayUtil.java | 13 + 6 files changed, 264 insertions(+), 163 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 265ce3088..b359ebd00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * 【core】 ClassScanner支持自定义ClassLoader * 【core】 修改错别字(pr#568@Github) * 【core】 增加DateUtil.parseCST方法(issue#570@Github) +* 【core】 增加defaultIfEmpty方法 ### Bug修复 * 【all】 修复阶乘计算错误bug(issue#I12XE4@Gitee) 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 0ca5f7e71..961747b54 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 @@ -1389,6 +1389,20 @@ public class CollUtil { return collection == null || collection.isEmpty(); } + /** + * 如果给定集合为空,返回默认集合 + * + * @param 集合类型 + * @param 集合元素类型 + * @param collection 集合 + * @param defaultCollection 默认数组 + * @return 非空(empty)的原集合或默认集合 + * @since 4.6.9 + */ + public static , E> T defaultIfEmpty(T collection, T defaultCollection){ + return isEmpty(collection) ? defaultCollection : collection; + } + /** * Map是否为空 * 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 1bb2ea4be..c5992d3d5 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 @@ -34,245 +34,293 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; /** - * 路径监听器
+ * 路径监听器 + * + *

* 监听器可监听目录或文件
* 如果监听的Path不存在,则递归创建空目录然后监听此空目录
* 递归监听目录时,并不会监听新创建的目录 - * - * @author Looly * + * @author Looly */ -public class WatchMonitor extends Thread implements Closeable, Serializable{ +public class WatchMonitor extends Thread implements Closeable, Serializable { private static final long serialVersionUID = 1L; - - /** 事件丢失 */ + + /** + * 事件丢失 + */ 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 = {// - OVERFLOW, //事件丢失 + OVERFLOW, //事件丢失 ENTRY_MODIFY, //修改 ENTRY_CREATE, //创建 ENTRY_DELETE //删除 }; - - /** 监听路径,必须为目录 */ + + /** + * 监听路径,必须为目录 + */ private Path path; - /** 递归目录的最大深度,当小于1时不递归下层目录 */ + /** + * 递归目录的最大深度,当小于1时不递归下层目录 + */ private int maxDepth; - /** 监听的文件,对于单文件监听不为空 */ + /** + * 监听的文件,对于单文件监听不为空 + */ private Path filePath; - - /** 监听服务 */ + + /** + * 监听服务 + */ private WatchService watchService; - /** 监听器 */ + /** + * 监听器 + */ private Watcher watcher; - /** 监听事件列表 */ + /** + * 监听事件列表 + */ private WatchEvent.Kind[] events; - - /** 监听是否已经关闭 */ + + /** + * 监听是否已经关闭 + */ private boolean isClosed; - - /** WatchKey 和 Path的对应表 */ + + /** + * WatchKey 和 Path的对应表 + */ private Map watchKeyPathMap = new HashMap<>(); - + //------------------------------------------------------ Static method start + /** * 创建并初始化监听 - * @param url URL + * + * @param url URL * @param events 监听的事件列表 * @return 监听对象 */ - public static WatchMonitor create(URL url, WatchEvent.Kind... events){ + public static WatchMonitor create(URL url, WatchEvent.Kind... events) { return create(url, 0, events); } - + /** * 创建并初始化监听 - * @param url URL - * @param events 监听的事件列表 + * + * @param url URL + * @param events 监听的事件列表 * @param maxDepth 当监听目录时,监听目录的最大深度,当设置值为1(或小于1)时,表示不递归监听子目录 * @return 监听对象 */ - public static WatchMonitor create(URL url, int maxDepth, WatchEvent.Kind... events){ + public static WatchMonitor create(URL url, int maxDepth, WatchEvent.Kind... events) { return create(URLUtil.toURI(url), maxDepth, events); } - + /** * 创建并初始化监听 - * @param uri URI + * + * @param uri URI * @param events 监听的事件列表 * @return 监听对象 */ - public static WatchMonitor create(URI uri, WatchEvent.Kind... events){ + public static WatchMonitor create(URI uri, WatchEvent.Kind... events) { return create(uri, 0, events); } - + /** * 创建并初始化监听 - * @param uri URI - * @param events 监听的事件列表 + * + * @param uri URI + * @param events 监听的事件列表 * @param maxDepth 当监听目录时,监听目录的最大深度,当设置值为1(或小于1)时,表示不递归监听子目录 * @return 监听对象 */ - public static WatchMonitor create(URI uri, int maxDepth, WatchEvent.Kind... events){ + public static WatchMonitor create(URI uri, int maxDepth, WatchEvent.Kind... events) { return create(Paths.get(uri), maxDepth, events); } - + /** * 创建并初始化监听 - * @param file 文件 + * + * @param file 文件 * @param events 监听的事件列表 * @return 监听对象 */ - public static WatchMonitor create(File file, WatchEvent.Kind... events){ + public static WatchMonitor create(File file, WatchEvent.Kind... events) { return create(file, 0, events); } - + /** * 创建并初始化监听 - * @param file 文件 - * @param events 监听的事件列表 + * + * @param file 文件 + * @param events 监听的事件列表 * @param maxDepth 当监听目录时,监听目录的最大深度,当设置值为1(或小于1)时,表示不递归监听子目录 * @return 监听对象 */ - public static WatchMonitor create(File file, int maxDepth, WatchEvent.Kind... events){ + public static WatchMonitor create(File file, int maxDepth, WatchEvent.Kind... events) { return create(file.toPath(), maxDepth, events); } - + /** * 创建并初始化监听 - * @param path 路径 + * + * @param path 路径 * @param events 监听的事件列表 * @return 监听对象 */ - public static WatchMonitor create(String path, WatchEvent.Kind... events){ + public static WatchMonitor create(String path, WatchEvent.Kind... events) { return create(path, 0, events); } - + /** * 创建并初始化监听 - * @param path 路径 - * @param events 监听的事件列表 + * + * @param path 路径 + * @param events 监听的事件列表 * @param maxDepth 当监听目录时,监听目录的最大深度,当设置值为1(或小于1)时,表示不递归监听子目录 * @return 监听对象 */ - public static WatchMonitor create(String path, int maxDepth, WatchEvent.Kind... events){ + public static WatchMonitor create(String path, int maxDepth, WatchEvent.Kind... events) { return create(Paths.get(path), maxDepth, events); } - + /** * 创建并初始化监听 - * @param path 路径 + * + * @param path 路径 * @param events 监听事件列表 * @return 监听对象 */ - public static WatchMonitor create(Path path, WatchEvent.Kind... events){ + public static WatchMonitor create(Path path, WatchEvent.Kind... events) { return create(path, 0, events); } - + /** * 创建并初始化监听 - * @param path 路径 - * @param events 监听事件列表 + * + * @param path 路径 + * @param events 监听事件列表 * @param maxDepth 当监听目录时,监听目录的最大深度,当设置值为1(或小于1)时,表示不递归监听子目录 * @return 监听对象 */ - public static WatchMonitor create(Path path, int maxDepth, WatchEvent.Kind... events){ + public static WatchMonitor create(Path path, int maxDepth, WatchEvent.Kind... events) { return new WatchMonitor(path, maxDepth, events); } - + //--------- createAll + /** * 创建并初始化监听,监听所有事件 - * @param uri URI + * + * @param uri URI * @param watcher {@link Watcher} * @return {@link WatchMonitor} */ - public static WatchMonitor createAll(URI uri, Watcher watcher){ + public static WatchMonitor createAll(URI uri, Watcher watcher) { return createAll(Paths.get(uri), watcher); } - + /** * 创建并初始化监听,监听所有事件 - * @param url URL + * + * @param url URL * @param watcher {@link Watcher} * @return {@link WatchMonitor} */ - public static WatchMonitor createAll(URL url, Watcher watcher){ + 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 file 被监听文件 * @param watcher {@link Watcher} * @return {@link WatchMonitor} */ - public static WatchMonitor createAll(File file, Watcher watcher){ + public static WatchMonitor createAll(File file, Watcher watcher) { return createAll(file.toPath(), watcher); } - + /** * 创建并初始化监听,监听所有事件 - * @param path 路径 + * + * @param path 路径 * @param watcher {@link Watcher} * @return {@link WatchMonitor} */ - public static WatchMonitor createAll(String path, Watcher watcher){ + public static WatchMonitor createAll(String path, Watcher watcher) { return createAll(Paths.get(path), watcher); } - + /** * 创建并初始化监听,监听所有事件 - * @param path 路径 + * + * @param path 路径 * @param watcher {@link Watcher} * @return {@link WatchMonitor} */ - public static WatchMonitor createAll(Path path, Watcher watcher){ + 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 file 文件 * @param events 监听的事件列表 */ public WatchMonitor(File file, WatchEvent.Kind... events) { this(file.toPath(), events); } - + /** * 构造 - * @param path 字符串路径 + * + * @param path 字符串路径 * @param events 监听的事件列表 */ public WatchMonitor(String path, WatchEvent.Kind... events) { this(Paths.get(path), events); } - + /** * 构造 - * @param path 字符串路径 + * + * @param path 字符串路径 * @param events 监听事件列表 */ public WatchMonitor(Path path, WatchEvent.Kind... events) { this(path, 0, events); } - + /** * 构造
* 例如设置: @@ -281,10 +329,10 @@ public class WatchMonitor extends Thread implements Closeable, Serializable{ * maxDepth = 2 表示监听当前目录以及下层目录 * maxDepth = 3 表示监听当前目录以及下层 * - * - * @param path 字符串路径 + * + * @param path 字符串路径 * @param maxDepth 递归目录的最大深度,当小于2时不递归下层目录 - * @param events 监听事件列表 + * @param events 监听事件列表 */ public WatchMonitor(Path path, int maxDepth, WatchEvent.Kind... events) { this.path = path; @@ -293,7 +341,7 @@ public class WatchMonitor extends Thread implements Closeable, Serializable{ this.init(); } //------------------------------------------------------ Constructor method end - + /** * 初始化
* 初始化包括: @@ -301,111 +349,89 @@ public class WatchMonitor extends Thread implements Closeable, Serializable{ * 1、解析传入的路径,判断其为目录还是文件 * 2、创建{@link WatchService} 对象 * - * + * * @throws WatchException 监听异常,IO异常时抛出此异常 */ - public void init() throws WatchException{ + public void init() throws WatchException { //获取目录或文件路径 - if(false ==Files.exists(this.path, LinkOption.NOFOLLOW_LINKS)) { + if (false == Files.exists(this.path, LinkOption.NOFOLLOW_LINKS)) { + // 不存在的路径 final Path lastPathEle = FileUtil.getLastPathEle(this.path); - if(null != lastPathEle) { + if (null != lastPathEle) { final String lastPathEleStr = lastPathEle.toString(); //带有点表示有扩展名,按照未创建的文件对待。Linux下.d的为目录,排除之 - if(StrUtil.contains(lastPathEleStr, StrUtil.C_DOT) && false ==StrUtil.endWithIgnoreCase(lastPathEleStr, ".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)){ + } 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){ + public WatchMonitor setWatcher(Watcher watcher) { this.watcher = watcher; return this; } - + @Override public void run() { watch(); } - + /** * 开始监听事件,阻塞当前进程 */ - public void watch(){ + public void watch() { watch(this.watcher); } - + /** * 开始监听事件,阻塞当前进程 + * * @param watcher 监听 * @throws WatchException 监听异常,如果监听关闭抛出此异常 */ - public void watch(Watcher watcher) throws WatchException{ - if(isClosed){ + 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 | ClosedWatchServiceException 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(); + doTakeAndWatch(watcher); } } - + /** * 当监听目录时,监听目录的最大深度
* 当设置值为1(或小于1)时,表示不递归监听子目录
@@ -415,7 +441,7 @@ public class WatchMonitor extends Thread implements Closeable, Serializable{ * maxDepth = 2 表示监听当前目录以及下层目录 * maxDepth = 3 表示监听当前目录以及下层 * - * + * * @param maxDepth 最大深度,当设置值为1(或小于1)时,表示不递归监听子目录,监听所有子目录请传{@link Integer#MAX_VALUE} * @return this */ @@ -423,37 +449,76 @@ public class WatchMonitor extends Thread implements Closeable, Serializable{ this.maxDepth = maxDepth; return this; } - + /** * 关闭监听 */ @Override - public void close(){ + public void close() { isClosed = true; IoUtil.close(watchService); } - + //------------------------------------------------------ private method start + + /** + * 执行事件获取并处理 + * + * @param watcher {@link Watcher} + */ + private void doTakeAndWatch(Watcher watcher){ + 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 != 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(); + } + /** * 注册监听路径 */ private void registerPath() { registerPath(this.path, (null != this.filePath) ? 0 : this.maxDepth); } - + /** * 将指定路径加入到监听中 - * @param path 路径 + * + * @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); + final WatchKey key = path.register(this.watchService, ArrayUtil.defaultIfEmpty(this.events, EVENTS_ALL)); watchKeyPathMap.put(key, path); - if(maxDepth > 1) { + if (maxDepth > 1) { //遍历所有子目录并加入监听 - Files.walkFileTree(path, EnumSet.noneOf(FileVisitOption.class), maxDepth, new SimpleFileVisitor(){ + Files.walkFileTree(path, EnumSet.noneOf(FileVisitOption.class), maxDepth, new SimpleFileVisitor() { @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { registerPath(dir, 0);//继续添加目录 @@ -462,7 +527,7 @@ public class WatchMonitor extends Thread implements Closeable, Serializable{ }); } } catch (IOException e) { - if(e instanceof AccessDeniedException) { + if (e instanceof AccessDeniedException) { //对于禁止访问的目录,跳过监听 return; } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/watch/Watcher.java b/hutool-core/src/main/java/cn/hutool/core/io/watch/Watcher.java index 826328a4e..6ab28d31b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/watch/Watcher.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/watch/Watcher.java @@ -5,34 +5,39 @@ import java.nio.file.WatchEvent; /** * 观察者(监视器) + * * @author Looly */ public interface Watcher { /** * 文件创建时执行的方法 - * @param event 事件 + * + * @param event 事件 * @param currentPath 事件发生的当前Path路径 */ void onCreate(WatchEvent event, Path currentPath); - + /** * 文件修改时执行的方法
* 文件修改可能触发多次 - * @param event 事件 + * + * @param event 事件 * @param currentPath 事件发生的当前Path路径 */ void onModify(WatchEvent event, Path currentPath); - + /** * 文件删除时执行的方法 - * @param event 事件 + * + * @param event 事件 * @param currentPath 事件发生的当前Path路径 */ void onDelete(WatchEvent event, Path currentPath); - + /** * 事件丢失或出错时执行的方法 - * @param event 事件 + * + * @param event 事件 * @param currentPath 事件发生的当前Path路径 */ void onOverflow(WatchEvent event, Path currentPath); diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java b/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java index b39c666a7..65dfce76c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java @@ -1,19 +1,7 @@ package cn.hutool.core.map; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; -import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import cn.hutool.core.collection.CollectionUtil; @@ -73,6 +61,21 @@ public class MapUtil { return (null == set) ? Collections.emptyMap() : set; } + /** + * 如果给定Map为空,返回默认Map + * + * @param 集合类型 + * @param 键类型 + * @param 值类型 + * @param map Map + * @param defaultMap 默认Map + * @return 非空(empty)的原Map或默认Map + * @since 4.6.9 + */ + public static , K, V> T defaultIfEmpty(T map, T defaultMap){ + return isEmpty(map) ? defaultMap : map; + } + // ----------------------------------------------------------------------------------------------- new HashMap /** * 新建一个HashMap diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java index 5e23f6c18..5048a956f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java @@ -33,6 +33,19 @@ public class ArrayUtil { return array == null || array.length == 0; } + /** + * 如果给定数组为空,返回默认数组 + * + * @param 数组元素类型 + * @param array 数组 + * @param defaultArray 默认数组 + * @return 非空(empty)的原数组或默认数组 + * @since 4.6.9 + */ + public static T[] defaultIfEmpty(T[] array, T[] defaultArray){ + return isEmpty(array) ? defaultArray : array; + } + /** * 数组是否为空
* 此方法会匹配单一对象,如果此对象为{@code null}则返回true