diff --git a/.github/codeql-analysis.yml b/.github/codeql-analysis.yml new file mode 100644 index 000000000..1f58c7a8f --- /dev/null +++ b/.github/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ v5-dev ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ v5-dev ] + schedule: + - cron: '45 6 * * 1' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'java', 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index dbc471984..edb403142 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,34 @@ ------------------------------------------------------------------------------------------------------------- +# 5.7.15 (2021-10-21) + +### 🐣新特性 +* 【db 】 Db.quietSetAutoCommit增加判空(issue#I4D75B@Gitee) +* 【core 】 增加RingIndexUtil(pr#438@Gitee) +* 【core 】 Assert增加checkBetween重载(pr#436@Gitee) +* 【core 】 ReUtil增加命名分组重载(pr#439@Gitee) +* 【json 】 toString和writer增加Filter(issue#I4DQNQ@Gitee) +* 【core 】 ContentType增加build重载(pr#1898@Github) +* 【bom 】 支持scope=import方式引入(issue#1561@Github) +* 【core 】 新增Hash接口,HashXXX继承此接口 +* 【core 】 ZipUtil增加append方法(pr#441@Gitee) +* 【core 】 CollUtil增加重载(issue#I4E9FS@Gitee) +* 【core 】 CopyOptions新增setFieldValueEditor(issue#I4E08T@Gitee) + +### 🐞Bug修复 +* 【core 】 修复CollUtil.isEqualList两个null返回错误问题(issue#1885@Github) +* 【poi 】 修复ExcelWriter多余调试信息导致的问题(issue#1884@Github) +* 【poi 】 修复TemporalAccessorUtil.toInstant使用DateTimeFormatter导致问题(issue#1891@Github) +* 【poi 】 修复sheet.getRow(y)为null导致的问题(issue#1893@Github) +* 【cache 】 修复LRUCache线程安全问题(issue#1895@Github) +* 【crypto 】 修复KeyUtil异常信息参数丢失问题(issue#1902@Github) +* 【core 】 修复StrUtil.split和splittoArray不一致问题(issue#I4ELU5@Github) +* 【core 】 修复SymmetricCrypto未关闭CipherOutputStream导致的问题(issue#I4EMST@Gitee) +* 【core 】 修复QueryBuilder对/转义问题(issue#1904@Github) + +------------------------------------------------------------------------------------------------------------- + # 5.7.14 (2021-10-09) ### 🐣新特性 diff --git a/README-EN.md b/README-EN.md index 817081b8c..d11d3dfaa 100644 --- a/README-EN.md +++ b/README-EN.md @@ -117,6 +117,24 @@ Each module can be introduced individually, or all modules can be introduced by ------------------------------------------------------------------------------- +## 🪙Support Hutool + +### 💳Donate + +If you think Hutool is good, you can donate to buy the author a pack of chili~, thanks in advance ^_^. + +[Gitee donate](https://gitee.com/dromara/hutool) + +[Dromara donate](https://dromara.gitee.io/donate.html) + +### 👕Shop about Hutool + +We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop: + +👉 [Hutool Shop](https://m.tb.cn/h.fVxoBOm?sm=2756b2) 👈 + +------------------------------------------------------------------------------- + ## 📦Install ### 🍊Maven @@ -124,18 +142,18 @@ Each module can be introduced individually, or all modules can be introduced by cn.hutool hutool-all - 5.7.14 + 5.7.15 ``` ### 🍐Gradle ``` -implementation 'cn.hutool:hutool-all:5.7.14' +implementation 'cn.hutool:hutool-all:5.7.15' ``` ## 📥Download -- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.14/) +- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.15/) > 🔔️note: > Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available. @@ -191,24 +209,6 @@ Hutool welcomes anyone to contribute code to Hutool, but the author suffers from [![Stargazers over time](https://starchart.cc/dromara/hutool.svg)](https://starchart.cc/dromara/hutool) -## 💳Donate - -If you think Hutool is good, you can donate to buy tshe author a pack of chili~, thanks in advance ^_^. - -[Gitee donate](https://gitee.com/dromara/hutool) - -[Dromara donate](https://dromara.gitee.io/donate.html) - -## 👕shop - -We provide the T-Shirt with Hutool Logo, please visit the shop: - -[Hutool T-Shirt](https://m.tb.cn/h.f47W8zc?sm=7d2b95) - -
- -
- ## 📌WeChat Official Account
diff --git a/README.md b/README.md index 62b43a57a..4bfc648e6 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,26 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 ------------------------------------------------------------------------------- +## 🪙支持Hutool + +### 💳捐赠 + +如果你觉得Hutool不错,可以捐赠请维护者吃包辣条~,在此表示感谢^_^。 + +[Gitee上捐赠](https://gitee.com/dromara/hutool) + +[捐赠给Dromara组织](https://dromara.gitee.io/donate.html) + +### 👕周边商店 + +你也可以通过购买Hutool的周边商品来支持Hutool维护哦! + +我们提供了印有Hutool Logo的周边商品,欢迎点击购买支持: + +👉 [Hutool 周边商店](https://m.tb.cn/h.fVxoBOm?sm=2756b2) 👈 + +------------------------------------------------------------------------------- + ## 📦安装 ### 🍊Maven @@ -122,24 +142,24 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.7.14 + 5.7.15 ``` ### 🍐Gradle ``` -implementation 'cn.hutool:hutool-all:5.7.14' +implementation 'cn.hutool:hutool-all:5.7.15' ``` ### 📥下载jar 点击以下链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.14/) +- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.15/) > 🔔️注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 -> 如果你的项目使用JDK7,请使用Hutool 4.x版本 +> 如果你的项目使用JDK7,请使用Hutool 4.x版本(不再更新) ### 🚽编译安装 @@ -198,26 +218,6 @@ Hutool欢迎任何人为Hutool添砖加瓦,贡献代码,不过维护者是 [![Stargazers over time](https://starchart.cc/dromara/hutool.svg)](https://starchart.cc/dromara/hutool) -## 💳捐赠 - -如果你觉得Hutool不错,可以捐赠请维护者吃包辣条~,在此表示感谢^_^。 - -点击以下链接,将页面拉到最下方点击“捐赠”即可。 - -[Gitee上捐赠](https://gitee.com/dromara/hutool) - -[捐赠给Dromara组织](https://dromara.gitee.io/donate.html) - -## 👕周边 - -我们提供了印有Hutool Logo的主题T恤,欢迎点击购买: - -[HutoolT恤商店](https://m.tb.cn/h.f47W8zc?sm=7d2b95) - -
- -
- ## 📌公众号
diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..d0c2c5589 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,15 @@ +# Security Policy + +## Supported Versions(支持的版本) + +| Version | Supported | +| ------- | ------------------ | +| 5.x.x | :white_check_mark: | +| 4.x.x | :x: | +| 3.x.x | :x: | + +## Reporting a Vulnerability(报告漏洞) + +如果你发现有安全问题或漏洞,请发送邮件到`loolly@aliyun.com`。 + +To report any found security issues or vulnerabilities, please send a mail to `loolly@aliyun.com`. \ No newline at end of file diff --git a/bin/version.txt b/bin/version.txt index 2ad2244f6..7b9e0e8bb 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.7.14 +5.7.15 diff --git a/docs/js/version.js b/docs/js/version.js index 71f2db4e9..36f2fde5c 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.7.14' \ No newline at end of file +var version = '5.7.15' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 7466d7fbb..f4cab970a 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 8aeb93af5..bc7be21a4 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index 6d5aca57a..b7ed8a410 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-bloomFilter diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BloomFilter.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BloomFilter.java index e7c9cc9fc..9ab8278b3 100644 --- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BloomFilter.java +++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BloomFilter.java @@ -20,10 +20,10 @@ public interface BloomFilter extends Serializable{ /** * 在boolean的bitMap中增加一个字符串
- * 如果存在就返回false .如果不存在.先增加这个字符串.再返回true + * 如果存在就返回{@code false} .如果不存在.先增加这个字符串.再返回{@code true} * * @param str 字符串 - * @return 是否加入成功,如果存在就返回false .如果不存在返回true + * @return 是否加入成功,如果存在就返回{@code false} .如果不存在返回{@code true} */ boolean add(String str); -} \ No newline at end of file +} diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index efa4b7d42..402341d6c 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-bom @@ -17,101 +17,182 @@ 提供丰富的Java工具方法,此模块为Hutool所有模块汇总,最终形式为拆分开的多个jar包,可以通过exclude方式排除不需要的模块 https://github.com/looly/hutool + + + + cn.hutool + hutool-core + ${project.parent.version} + + + cn.hutool + hutool-aop + ${project.parent.version} + + + cn.hutool + hutool-bloomFilter + ${project.parent.version} + + + cn.hutool + hutool-cache + ${project.parent.version} + + + cn.hutool + hutool-crypto + ${project.parent.version} + + + cn.hutool + hutool-db + ${project.parent.version} + + + cn.hutool + hutool-dfa + ${project.parent.version} + + + cn.hutool + hutool-extra + ${project.parent.version} + + + cn.hutool + hutool-http + ${project.parent.version} + + + cn.hutool + hutool-log + ${project.parent.version} + + + cn.hutool + hutool-script + ${project.parent.version} + + + cn.hutool + hutool-setting + ${project.parent.version} + + + cn.hutool + hutool-system + ${project.parent.version} + + + cn.hutool + hutool-cron + ${project.parent.version} + + + cn.hutool + hutool-json + ${project.parent.version} + + + cn.hutool + hutool-poi + ${project.parent.version} + + + cn.hutool + hutool-captcha + ${project.parent.version} + + + cn.hutool + hutool-socket + ${project.parent.version} + + + cn.hutool + hutool-jwt + ${project.parent.version} + + + + cn.hutool hutool-core - ${project.parent.version} cn.hutool hutool-aop - ${project.parent.version} cn.hutool hutool-bloomFilter - ${project.parent.version} cn.hutool hutool-cache - ${project.parent.version} cn.hutool hutool-crypto - ${project.parent.version} cn.hutool hutool-db - ${project.parent.version} cn.hutool hutool-dfa - ${project.parent.version} cn.hutool hutool-extra - ${project.parent.version} cn.hutool hutool-http - ${project.parent.version} cn.hutool hutool-log - ${project.parent.version} cn.hutool hutool-script - ${project.parent.version} cn.hutool hutool-setting - ${project.parent.version} cn.hutool hutool-system - ${project.parent.version} cn.hutool hutool-cron - ${project.parent.version} cn.hutool hutool-json - ${project.parent.version} cn.hutool hutool-poi - ${project.parent.version} cn.hutool hutool-captcha - ${project.parent.version} cn.hutool hutool-socket - ${project.parent.version} cn.hutool hutool-jwt - ${project.parent.version} diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 42faadbad..5bfe068ca 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-cache diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java index 6999922ee..6bb549689 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java @@ -2,7 +2,6 @@ package cn.hutool.cache.impl; import cn.hutool.cache.Cache; import cn.hutool.cache.CacheListener; -import cn.hutool.core.collection.CopiedIter; import cn.hutool.core.lang.func.Func0; import java.util.Iterator; @@ -12,7 +11,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.LongAdder; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import java.util.concurrent.locks.StampedLock; /** * 超时和限制大小的缓存的默认实现
@@ -31,11 +29,6 @@ public abstract class AbstractCache implements Cache { protected Map> cacheMap; - // 乐观锁,此处使用乐观锁解决读多写少的场景 - // get时乐观读,再检查是否修改,修改则转入悲观读重新读一遍,可以有效解决在写时阻塞大量读操作的情况。 - // see: https://www.cnblogs.com/jiagoushijuzi/p/13721319.html - protected final StampedLock lock = new StampedLock(); - /** * 写的时候每个key一把锁,降低锁的粒度 */ @@ -75,16 +68,6 @@ public abstract class AbstractCache implements Cache { put(key, object, timeout); } - @Override - public void put(K key, V object, long timeout) { - final long stamp = lock.writeLock(); - try { - putWithoutLock(key, object, timeout); - } finally { - lock.unlockWrite(stamp); - } - } - /** * 加入元素,无锁 * @@ -93,7 +76,7 @@ public abstract class AbstractCache implements Cache { * @param timeout 超时时长 * @since 4.5.16 */ - private void putWithoutLock(K key, V object, long timeout) { + protected void putWithoutLock(K key, V object, long timeout) { CacheObj co = new CacheObj<>(key, object, timeout); if (timeout != 0) { existCustomTimeout = true; @@ -106,29 +89,6 @@ public abstract class AbstractCache implements Cache { // ---------------------------------------------------------------- put end // ---------------------------------------------------------------- get start - @Override - public boolean containsKey(K key) { - final long stamp = lock.readLock(); - try { - // 不存在或已移除 - final CacheObj co = cacheMap.get(key); - if (co == null) { - return false; - } - - if (false == co.isExpired()) { - // 命中 - return true; - } - } finally { - lock.unlockRead(stamp); - } - - // 过期 - remove(key, true); - return false; - } - /** * @return 命中数 */ @@ -170,36 +130,6 @@ public abstract class AbstractCache implements Cache { } return v; } - - @Override - public V get(K key, boolean isUpdateLastAccess) { - // 尝试读取缓存,使用乐观读锁 - long stamp = lock.tryOptimisticRead(); - CacheObj co = cacheMap.get(key); - if(false == lock.validate(stamp)){ - // 有写线程修改了此对象,悲观读 - stamp = lock.readLock(); - try { - co = cacheMap.get(key); - } finally { - lock.unlockRead(stamp); - } - } - - // 未命中 - if (null == co) { - missCount.increment(); - return null; - } else if (false == co.isExpired()) { - hitCount.increment(); - return co.get(isUpdateLastAccess); - } - - // 过期,既不算命中也不算非命中 - remove(key, true); - return null; - } - // ---------------------------------------------------------------- get end @Override @@ -207,21 +137,7 @@ public abstract class AbstractCache implements Cache { CacheObjIterator copiedIterator = (CacheObjIterator) this.cacheObjIterator(); return new CacheValuesIterator<>(copiedIterator); } - - @Override - public Iterator> cacheObjIterator() { - CopiedIter> copiedIterator; - final long stamp = lock.readLock(); - try { - copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator()); - } finally { - lock.unlockRead(stamp); - } - return new CacheObjIterator<>(copiedIterator); - } - // ---------------------------------------------------------------- prune start - /** * 清理实现
* 子类实现此方法时无需加锁 @@ -229,16 +145,6 @@ public abstract class AbstractCache implements Cache { * @return 清理数 */ protected abstract int pruneCache(); - - @Override - public final int prune() { - final long stamp = lock.writeLock(); - try { - return pruneCache(); - } finally { - lock.unlockWrite(stamp); - } - } // ---------------------------------------------------------------- prune end // ---------------------------------------------------------------- common start @@ -270,21 +176,6 @@ public abstract class AbstractCache implements Cache { return (capacity > 0) && (cacheMap.size() >= capacity); } - @Override - public void remove(K key) { - remove(key, false); - } - - @Override - public void clear() { - final long stamp = lock.writeLock(); - try { - cacheMap.clear(); - } finally { - lock.unlockWrite(stamp); - } - } - @Override public int size() { return cacheMap.size(); @@ -338,25 +229,6 @@ public abstract class AbstractCache implements Cache { } } - /** - * 移除key对应的对象 - * - * @param key 键 - * @param withMissCount 是否计数丢失数 - */ - private void remove(K key, boolean withMissCount) { - final long stamp = lock.writeLock(); - CacheObj co; - try { - co = removeWithoutLock(key, withMissCount); - } finally { - lock.unlockWrite(stamp); - } - if (null != co) { - onRemove(co.key, co.obj); - } - } - /** * 移除key对应的对象,不加锁 * @@ -364,7 +236,7 @@ public abstract class AbstractCache implements Cache { * @param withMissCount 是否计数丢失数 * @return 移除的对象,无返回null */ - private CacheObj removeWithoutLock(K key, boolean withMissCount) { + protected CacheObj removeWithoutLock(K key, boolean withMissCount) { final CacheObj co = cacheMap.remove(key); if (withMissCount) { // 在丢失计数有效的情况下,移除一般为get时的超时操作,此处应该丢失数+1 diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/FIFOCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/FIFOCache.java index c403ae10c..558fed75f 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/FIFOCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/FIFOCache.java @@ -16,7 +16,7 @@ import java.util.LinkedHashMap; * @param 值类型 * @author Looly */ -public class FIFOCache extends AbstractCache { +public class FIFOCache extends StampedCache { private static final long serialVersionUID = 1L; /** diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java index 32709ed6b..4b1e2608a 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java @@ -15,7 +15,7 @@ import java.util.Iterator; * @param 键类型 * @param 值类型 */ -public class LFUCache extends AbstractCache { +public class LFUCache extends StampedCache { private static final long serialVersionUID = 1L; /** diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/LRUCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/LRUCache.java index b385b57fe..29f83f02f 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/LRUCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/LRUCache.java @@ -16,7 +16,7 @@ import java.util.Iterator; * @param 键类型 * @param 值类型 */ -public class LRUCache extends AbstractCache { +public class LRUCache extends ReentrantCache { private static final long serialVersionUID = 1L; /** diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java new file mode 100644 index 000000000..180bc7785 --- /dev/null +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java @@ -0,0 +1,136 @@ +package cn.hutool.cache.impl; + +import cn.hutool.core.collection.CopiedIter; + +import java.util.Iterator; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 使用{@link ReentrantLock}保护的缓存,读写都使用悲观锁完成,主要避免某些Map无法使用读写锁的问题
+ * 例如使用了LinkedHashMap的缓存,由于get方法也会改变Map的结构,因此读写必须加互斥锁 + * + * @param 键类型 + * @param 值类型 + * @author looly + * @since 5.7.15 + */ +public abstract class ReentrantCache extends AbstractCache { + private static final long serialVersionUID = 1L; + + // 一些特殊缓存,例如使用了LinkedHashMap的缓存,由于get方法也会改变Map的结构,导致无法使用读写锁 + // 最优的解决方案是使用Guava的ConcurrentLinkedHashMap,此处使用简化的互斥锁 + protected final ReentrantLock lock = new ReentrantLock(); + + @Override + public void put(K key, V object, long timeout) { + lock.lock(); + try { + putWithoutLock(key, object, timeout); + } finally { + lock.unlock(); + } + } + + @Override + public boolean containsKey(K key) { + lock.lock(); + try { + // 不存在或已移除 + final CacheObj co = cacheMap.get(key); + if (co == null) { + return false; + } + + if (false == co.isExpired()) { + // 命中 + return true; + } + } finally { + lock.unlock(); + } + + // 过期 + remove(key, true); + return false; + } + + @Override + public V get(K key, boolean isUpdateLastAccess) { + CacheObj co; + lock.lock(); + try { + co = cacheMap.get(key); + } finally { + lock.unlock(); + } + + // 未命中 + if (null == co) { + missCount.increment(); + return null; + } else if (false == co.isExpired()) { + hitCount.increment(); + return co.get(isUpdateLastAccess); + } + + // 过期,既不算命中也不算非命中 + remove(key, true); + return null; + } + + @Override + public Iterator> cacheObjIterator() { + CopiedIter> copiedIterator; + lock.lock(); + try { + copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator()); + } finally { + lock.unlock(); + } + return new CacheObjIterator<>(copiedIterator); + } + + @Override + public final int prune() { + lock.lock(); + try { + return pruneCache(); + } finally { + lock.unlock(); + } + } + + @Override + public void remove(K key) { + remove(key, false); + } + + @Override + public void clear() { + lock.lock(); + try { + cacheMap.clear(); + } finally { + lock.unlock(); + } + } + + /** + * 移除key对应的对象 + * + * @param key 键 + * @param withMissCount 是否计数丢失数 + */ + private void remove(K key, boolean withMissCount) { + lock.lock(); + CacheObj co; + try { + co = removeWithoutLock(key, withMissCount); + } finally { + lock.unlock(); + } + if (null != co) { + onRemove(co.key, co.obj); + } + } +} diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java new file mode 100644 index 000000000..79534f2bf --- /dev/null +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java @@ -0,0 +1,141 @@ +package cn.hutool.cache.impl; + +import cn.hutool.core.collection.CopiedIter; + +import java.util.Iterator; +import java.util.concurrent.locks.StampedLock; + +/** + * 使用{@link StampedLock}保护的缓存,使用读写乐观锁 + * + * @param 键类型 + * @param 值类型 + * @author looly + * @since 5.7.15 + */ +public abstract class StampedCache extends AbstractCache{ + private static final long serialVersionUID = 1L; + + // 乐观锁,此处使用乐观锁解决读多写少的场景 + // get时乐观读,再检查是否修改,修改则转入悲观读重新读一遍,可以有效解决在写时阻塞大量读操作的情况。 + // see: https://www.cnblogs.com/jiagoushijuzi/p/13721319.html + protected final StampedLock lock = new StampedLock(); + + @Override + public void put(K key, V object, long timeout) { + final long stamp = lock.writeLock(); + try { + putWithoutLock(key, object, timeout); + } finally { + lock.unlockWrite(stamp); + } + } + + @Override + public boolean containsKey(K key) { + final long stamp = lock.readLock(); + try { + // 不存在或已移除 + final CacheObj co = cacheMap.get(key); + if (co == null) { + return false; + } + + if (false == co.isExpired()) { + // 命中 + return true; + } + } finally { + lock.unlockRead(stamp); + } + + // 过期 + remove(key, true); + return false; + } + + @Override + public V get(K key, boolean isUpdateLastAccess) { + // 尝试读取缓存,使用乐观读锁 + long stamp = lock.tryOptimisticRead(); + CacheObj co = cacheMap.get(key); + if(false == lock.validate(stamp)){ + // 有写线程修改了此对象,悲观读 + stamp = lock.readLock(); + try { + co = cacheMap.get(key); + } finally { + lock.unlockRead(stamp); + } + } + + // 未命中 + if (null == co) { + missCount.increment(); + return null; + } else if (false == co.isExpired()) { + hitCount.increment(); + return co.get(isUpdateLastAccess); + } + + // 过期,既不算命中也不算非命中 + remove(key, true); + return null; + } + + @Override + public Iterator> cacheObjIterator() { + CopiedIter> copiedIterator; + final long stamp = lock.readLock(); + try { + copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator()); + } finally { + lock.unlockRead(stamp); + } + return new CacheObjIterator<>(copiedIterator); + } + + @Override + public final int prune() { + final long stamp = lock.writeLock(); + try { + return pruneCache(); + } finally { + lock.unlockWrite(stamp); + } + } + + @Override + public void remove(K key) { + remove(key, false); + } + + @Override + public void clear() { + final long stamp = lock.writeLock(); + try { + cacheMap.clear(); + } finally { + lock.unlockWrite(stamp); + } + } + + /** + * 移除key对应的对象 + * + * @param key 键 + * @param withMissCount 是否计数丢失数 + */ + private void remove(K key, boolean withMissCount) { + final long stamp = lock.writeLock(); + CacheObj co; + try { + co = removeWithoutLock(key, withMissCount); + } finally { + lock.unlockWrite(stamp); + } + if (null != co) { + onRemove(co.key, co.obj); + } + } +} diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java index 5e094875d..e03ede728 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java @@ -16,7 +16,7 @@ import java.util.concurrent.ScheduledFuture; * @param 键类型 * @param 值类型 */ -public class TimedCache extends AbstractCache { +public class TimedCache extends StampedCache { private static final long serialVersionUID = 1L; /** 正在执行的定时任务 */ diff --git a/hutool-cache/src/test/java/cn/hutool/cache/LRUCacheTest.java b/hutool-cache/src/test/java/cn/hutool/cache/LRUCacheTest.java new file mode 100644 index 000000000..6ccc2db2f --- /dev/null +++ b/hutool-cache/src/test/java/cn/hutool/cache/LRUCacheTest.java @@ -0,0 +1,52 @@ +package cn.hutool.cache; + +import cn.hutool.cache.impl.LRUCache; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; + +/** + * 见:https://github.com/dromara/hutool/issues/1895
+ * 并发问题测试,在5.7.15前,LRUCache存在并发问题,多线程get后,map结构变更,导致null的位置不确定, + * 并可能引起死锁。 + */ +public class LRUCacheTest { + + @Test + public void readWriteTest() throws InterruptedException { + LRUCache cache = CacheUtil.newLRUCache(10); + for (int i = 0; i < 10; i++) { + cache.put(i, i); + } + + CountDownLatch countDownLatch = new CountDownLatch(10); + // 10个线程分别读0-9 10000次 + for (int i = 0; i < 10; i++) { + int finalI = i; + new Thread(() -> { + for (int j = 0; j < 10000; j++) { + cache.get(finalI); + } + countDownLatch.countDown(); + }).start(); + } + // 等待读线程结束 + countDownLatch.await(); + // 按顺序读0-9 + StringBuilder sb1 = new StringBuilder(); + for (int i = 0; i < 10; i++) { + sb1.append(cache.get(i)); + } + Assert.assertEquals("0123456789", sb1.toString()); + + // 新加11,此时0最久未使用,应该淘汰0 + cache.put(11, 11); + + StringBuilder sb2 = new StringBuilder(); + for (int i = 0; i < 10; i++) { + sb2.append(cache.get(i)); + } + Assert.assertEquals("null123456789", sb2.toString()); + } +} diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 0636c9a62..e1293b7d3 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 817ceed23..5956c0bb2 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-core diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java index 84b99c239..796124648 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java @@ -664,6 +664,32 @@ public class BeanUtil { ).copy(); } + /** + * 对象转Map
+ * 通过自定义{@link CopyOptions} 完成抓换选项,以便实现: + * + *
+	 * 1. 字段筛选,可以去除不需要的字段
+	 * 2. 字段变换,例如实现驼峰转下划线
+	 * 3. 自定义字段前缀或后缀等等
+	 * 4. 字段值处理
+	 * ...
+	 * 
+ * + * @param bean bean对象 + * @param targetMap 目标的Map + * @param copyOptions 拷贝选项 + * @return Map + * @since 5.7.15 + */ + public static Map beanToMap(Object bean, Map targetMap, CopyOptions copyOptions) { + if (null == bean) { + return null; + } + + return BeanCopier.create(bean, targetMap, copyOptions).copy(); + } + // --------------------------------------------------------------------------------------------- copyProperties /** diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java index fc6c3947d..bcfae58a1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java @@ -191,6 +191,10 @@ public class BeanCopier implements Copier, Serializable { if(null != copyOptions.propertiesFilter && false == copyOptions.propertiesFilter.test(prop.getField(), value)) { return; } + + // since 5.7.15 + value = copyOptions.editFieldValue(key, value); + if ((null == value && copyOptions.ignoreNullValue) || bean == value) { // 当允许跳过空时,跳过 //值不能为bean本身,防止循环引用,此类也跳过 @@ -257,6 +261,9 @@ public class BeanCopier implements Copier, Serializable { return; } + // since 5.7.15 + value = copyOptions.editFieldValue(providerKey, value); + if ((null == value && copyOptions.ignoreNullValue) || bean == value) { // 当允许跳过空时,跳过 // 值不能为bean本身,防止循环引用 diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java index 502ea4846..4cab13809 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java @@ -7,6 +7,7 @@ import cn.hutool.core.util.ObjectUtil; import java.io.Serializable; import java.lang.reflect.Field; import java.util.Map; +import java.util.function.BiFunction; import java.util.function.BiPredicate; /** @@ -57,6 +58,10 @@ public class CopyOptions implements Serializable { * 字段属性编辑器,用于自定义属性转换规则,例如驼峰转下划线等 */ protected Editor fieldNameEditor; + /** + * 字段属性值编辑器,用于自定义属性值转换规则,例如null转""等 + */ + protected BiFunction fieldValueEditor; /** * 是否支持transient关键字修饰和@Transient注解,如果支持,被修饰的字段或方法对应的字段将被忽略。 */ @@ -224,6 +229,30 @@ public class CopyOptions implements Serializable { return this; } + /** + * 设置字段属性值编辑器,用于自定义属性值转换规则,例如null转""等
+ * + * @param fieldValueEditor 字段属性值编辑器,用于自定义属性值转换规则,例如null转""等 + * @return CopyOptions + * @since 5.7.15 + */ + public CopyOptions setFieldValueEditor(BiFunction fieldValueEditor) { + this.fieldValueEditor = fieldValueEditor; + return this; + } + + /** + * 转换字段名为编辑后的字段名 + * + * @param fieldName 字段名 + * @return 编辑后的字段名 + * @since 5.7.15 + */ + protected Object editFieldValue(String fieldName, Object fieldValue) { + return (null != this.fieldValueEditor) ? + this.fieldValueEditor.apply(fieldName, fieldValue) : fieldValue; + } + /** * 是否支持transient关键字修饰和@Transient注解,如果支持,被修饰的字段或方法对应的字段将被忽略。 * @@ -251,12 +280,12 @@ public class CopyOptions implements Serializable { * 当非反向,则根据源字段名获取目标字段名,反之根据目标字段名获取源字段名。 * * @param fieldName 字段名 - * @param reversed 是否反向映射 + * @param reversed 是否反向映射 * @return 映射后的字段名 */ - protected String getMappedFieldName(String fieldName, boolean reversed){ + protected String getMappedFieldName(String fieldName, boolean reversed) { Map mapping = reversed ? getReversedMapping() : this.fieldMapping; - if(MapUtil.isEmpty(mapping)){ + if (MapUtil.isEmpty(mapping)) { return fieldName; } return ObjectUtil.defaultIfNull(mapping.get(fieldName), fieldName); @@ -264,11 +293,12 @@ public class CopyOptions implements Serializable { /** * 转换字段名为编辑后的字段名 + * * @param fieldName 字段名 * @return 编辑后的字段名 * @since 5.4.2 */ - protected String editFieldName(String fieldName){ + protected String editFieldName(String fieldName) { return (null != this.fieldNameEditor) ? this.fieldNameEditor.edit(fieldName) : fieldName; } @@ -279,10 +309,10 @@ public class CopyOptions implements Serializable { * @since 4.1.10 */ private Map getReversedMapping() { - if(null == this.fieldMapping){ + if (null == this.fieldMapping) { return null; } - if(null == this.reversedFieldMapping){ + if (null == this.reversedFieldMapping) { reversedFieldMapping = MapUtil.reverse(this.fieldMapping); } return reversedFieldMapping; diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java b/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java index 1b77c2ba6..ec5b018f6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java +++ b/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java @@ -299,7 +299,7 @@ public class Base64 { * base64解码 * * @param base64 被解码的base64字符串 - * @return 被加密后的字符串 + * @return 解码后的bytes */ public static byte[] decode(CharSequence base64) { return Base64Decoder.decode(base64); 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 2083ced3d..94e992aa1 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 @@ -53,6 +53,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.LinkedBlockingDeque; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; /** * 集合相关工具类 @@ -1050,8 +1051,8 @@ public class CollUtil { * @param end 结束位置(不包含) * @param step 步进 * @return 截取后的数组,当开始位置超过最大时,返回空的List - * @since 4.0.6 * @see ListUtil#sub(List, int, int, int) + * @since 4.0.6 */ public static List sub(List list, int start, int end, int step) { return ListUtil.sub(list, start, end, step); @@ -1073,11 +1074,11 @@ public class CollUtil { /** * 截取集合的部分 * - * @param 集合元素类型 - * @param collection 被截取的数组 - * @param start 开始位置(包含) - * @param end 结束位置(不包含) - * @param step 步进 + * @param 集合元素类型 + * @param collection 被截取的数组 + * @param start 开始位置(包含) + * @param end 结束位置(不包含) + * @param step 步进 * @return 截取后的数组,当开始位置超过最大时,返回空集合 * @since 4.0.6 */ @@ -1086,7 +1087,7 @@ public class CollUtil { return ListUtil.empty(); } - final List list = collection instanceof List ? (List)collection : ListUtil.toList(collection); + final List list = collection instanceof List ? (List) collection : ListUtil.toList(collection); return sub(list, start, end, step); } @@ -1575,6 +1576,20 @@ public class CollUtil { return isEmpty(collection) ? defaultCollection : collection; } + /** + * 如果给定集合为空,返回默认集合 + * + * @param 集合类型 + * @param 集合元素类型 + * @param collection 集合 + * @param supplier 默认值懒加载函数 + * @return 非空(empty)的原集合或默认集合 + * @since 5.7.15 + */ + public static , E> T defaultIfEmpty(T collection, Supplier supplier) { + return isEmpty(collection) ? supplier.get() : collection; + } + /** * Iterable是否为空 * @@ -2948,6 +2963,9 @@ public class CollUtil { * @since 5.6.0 */ public static boolean isEqualList(final Collection list1, final Collection list2) { + if (list1 == list2) { + return true; + } if (list1 == null || list2 == null || list1.size() != list2.size()) { return false; } diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java index ef0991fc8..c538e317c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java @@ -819,7 +819,7 @@ public class IterUtil { * @return Iterable对象的元素数量 * @since 5.5.0 */ - public static int size(final Iterable iterable) { + public static int size(Iterable iterable) { if (null == iterable) { return 0; } @@ -838,7 +838,7 @@ public class IterUtil { * @return Iterator对象的元素数量 * @since 5.5.0 */ - public static int size(final Iterator iterator) { + public static int size(Iterator iterator) { int size = 0; if (iterator != null) { while (iterator.hasNext()) { @@ -862,7 +862,7 @@ public class IterUtil { * @return 是否相同 * @since 5.6.0 */ - public static boolean isEqualList(final Iterable list1, final Iterable list2) { + public static boolean isEqualList(Iterable list1, Iterable list2) { if (list1 == list2) { return true; } diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/RingIndexUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/RingIndexUtil.java new file mode 100644 index 000000000..b997b1428 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/collection/RingIndexUtil.java @@ -0,0 +1,79 @@ +package cn.hutool.core.collection; + +import cn.hutool.core.lang.Assert; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * 集合索引环形获取工具类 + * + * @author ZhouChuGang + * @since 5.7.15 + */ +public class RingIndexUtil { + + /** + * 通过cas操作 实现对指定值内的回环累加 + * + * @param object 集合 + *
    + *
  • Collection - the collection size + *
  • Map - the map size + *
  • Array - the array size + *
  • Iterator - the number of elements remaining in the iterator + *
  • Enumeration - the number of elements remaining in the enumeration + *
+ * @param atomicInteger 原子操作类 + * @return 索引位置 + */ + public static int ringNextIntByObj(Object object, AtomicInteger atomicInteger) { + Assert.notNull(object); + int modulo = CollUtil.size(object); + return ringNextInt(modulo, atomicInteger); + } + + /** + * 通过cas操作 实现对指定值内的回环累加 + * + * @param modulo 回环周期值 + * @param atomicInteger 原子操作类 + * @return 索引位置 + */ + public static int ringNextInt(int modulo, AtomicInteger atomicInteger) { + Assert.notNull(atomicInteger); + Assert.isTrue(modulo > 0); + if (modulo <= 1) { + return 0; + } + for (; ; ) { + int current = atomicInteger.get(); + int next = (current + 1) % modulo; + if (atomicInteger.compareAndSet(current, next)) { + return next; + } + } + } + + /** + * 通过cas操作 实现对指定值内的回环累加 + * + * @param modulo 回环周期值 + * @param atomicLong 原子操作类 + * @return 索引位置 + */ + public static long ringNextLong(long modulo, AtomicLong atomicLong) { + Assert.notNull(atomicLong); + Assert.isTrue(modulo > 0); + if (modulo <= 1) { + return 0; + } + for (; ; ) { + long current = atomicLong.get(); + long next = (current + 1) % modulo; + if (atomicLong.compareAndSet(current, next)) { + return next; + } + } + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/compress/ZipCopyVisitor.java b/hutool-core/src/main/java/cn/hutool/core/compress/ZipCopyVisitor.java new file mode 100644 index 000000000..3423097be --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/compress/ZipCopyVisitor.java @@ -0,0 +1,84 @@ +package cn.hutool.core.compress; + +import cn.hutool.core.util.StrUtil; + +import java.io.IOException; +import java.nio.file.CopyOption; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.FileSystem; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +/** + * Zip文件拷贝的FileVisitor实现,zip中追加文件,此类非线程安全
+ * 此类在遍历源目录并复制过程中会自动创建目标目录中不存在的上级目录。 + * + * @author looly + * @since 5.7.15 + */ +public class ZipCopyVisitor extends SimpleFileVisitor { + + /** + * 源Path,或基准路径,用于计算被拷贝文件的相对路径 + */ + private final Path source; + private final FileSystem fileSystem; + private final CopyOption[] copyOptions; + + /** + * 构造 + * + * @param source 源Path,或基准路径,用于计算被拷贝文件的相对路径 + * @param fileSystem 目标Zip文件 + * @param copyOptions 拷贝选项,如跳过已存在等 + */ + public ZipCopyVisitor(Path source, FileSystem fileSystem, CopyOption... copyOptions) { + this.source = source; + this.fileSystem = fileSystem; + this.copyOptions = copyOptions; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + final Path targetDir = resolveTarget(dir); + if(StrUtil.isNotEmpty(targetDir.toString())){ + // 在目标的Zip文件中的相对位置创建目录 + try { + Files.copy(dir, targetDir, copyOptions); + } catch (FileAlreadyExistsException e) { + if (false == Files.isDirectory(targetDir)) { + throw e; + } + } + } + + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + // 如果目标存在,无论目录还是文件都抛出FileAlreadyExistsException异常,此处不做特别处理 + Files.copy(file, resolveTarget(file), copyOptions); + + return FileVisitResult.CONTINUE; + } + + /** + * 根据源文件或目录路径,拼接生成目标的文件或目录路径
+ * 原理是首先截取源路径,得到相对路径,再和目标路径拼接 + * + *

+ * 如:源路径是 /opt/test/,需要拷贝的文件是 /opt/test/a/a.txt,得到相对路径 a/a.txt
+ * 目标路径是/home/,则得到最终目标路径是 /home/a/a.txt + *

+ * + * @param file 需要拷贝的文件或目录Path + * @return 目标Path + */ + private Path resolveTarget(Path file) { + return fileSystem.getPath(source.relativize(file).toString()); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/compress/ZipWriter.java b/hutool-core/src/main/java/cn/hutool/core/compress/ZipWriter.java index 48962fe5f..b27d86338 100755 --- a/hutool-core/src/main/java/cn/hutool/core/compress/ZipWriter.java +++ b/hutool-core/src/main/java/cn/hutool/core/compress/ZipWriter.java @@ -26,22 +26,22 @@ import java.util.zip.ZipOutputStream; public class ZipWriter implements Closeable { /** - * 创建{@link ZipWriter} + * 创建ZipWriter * * @param zipFile 生成的Zip文件 * @param charset 编码 - * @return {@link ZipWriter} + * @return ZipWriter */ public static ZipWriter of(File zipFile, Charset charset) { return new ZipWriter(zipFile, charset); } /** - * 创建{@link ZipWriter} + * 创建ZipWriter * * @param out Zip输出的流,一般为输出文件流 * @param charset 编码 - * @return {@link ZipWriter} + * @return ZipWriter */ public static ZipWriter of(OutputStream out, Charset charset) { return new ZipWriter(out, charset); diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java b/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java index f9b37927f..6758c3768 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java @@ -167,7 +167,7 @@ public class DateTime extends Date { * @since 5.0.5 */ public DateTime(Instant instant, ZoneId zoneId) { - this(instant.toEpochMilli(), TimeZone.getTimeZone(ObjectUtil.defaultIfNull(zoneId, ZoneId.systemDefault()))); + this(instant.toEpochMilli(), ZoneUtil.toTimeZone(zoneId)); } /** @@ -177,7 +177,7 @@ public class DateTime extends Date { * @since 5.0.0 */ public DateTime(TemporalAccessor temporalAccessor) { - this(DateUtil.toInstant(temporalAccessor)); + this(TemporalAccessorUtil.toInstant(temporalAccessor)); } /** @@ -276,7 +276,7 @@ public class DateTime extends Date { * @since 5.0.0 */ public DateTime(CharSequence dateStr, DateTimeFormatter formatter) { - this(Instant.from(formatter.parse(dateStr)), formatter.getZone()); + this(TemporalAccessorUtil.toInstant(formatter.parse(dateStr)), formatter.getZone()); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java index f2918c55e..1d9baef32 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java @@ -138,7 +138,10 @@ public class TemporalAccessorUtil extends TemporalUtil{ // 指定本地时间转换 为Instant,取当天日期 result = ((OffsetTime) temporalAccessor).atDate(LocalDate.now()).toInstant(); } else { - result = Instant.from(temporalAccessor); + // issue#1891@Github + // Instant.from不能完成日期转换 + //result = Instant.from(temporalAccessor); + result = toInstant(LocalDateTimeUtil.of(temporalAccessor)); } return result; diff --git a/hutool-core/src/main/java/cn/hutool/core/date/ZoneUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/ZoneUtil.java new file mode 100644 index 000000000..1b12788da --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/date/ZoneUtil.java @@ -0,0 +1,41 @@ +package cn.hutool.core.date; + +import java.time.ZoneId; +import java.util.TimeZone; + +/** + * {@link ZoneId}和{@link TimeZone}相关封装 + * + * @author looly + * @since 5.7.15 + */ +public class ZoneUtil { + + /** + * {@link ZoneId}转换为{@link TimeZone},{@code null}则返回系统默认值 + * + * @param zoneId {@link ZoneId},{@code null}则返回系统默认值 + * @return {@link TimeZone} + */ + public static TimeZone toTimeZone(ZoneId zoneId) { + if (null == zoneId) { + return TimeZone.getDefault(); + } + + return TimeZone.getTimeZone(zoneId); + } + + /** + * {@link TimeZone}转换为{@link ZoneId},{@code null}则返回系统默认值 + * + * @param timeZone {@link TimeZone},{@code null}则返回系统默认值 + * @return {@link ZoneId} + */ + public static ZoneId toZoneId(TimeZone timeZone) { + if (null == timeZone) { + return ZoneId.systemDefault(); + } + + return timeZone.toZoneId(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java index 3479c17b8..02699d4a4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java @@ -684,7 +684,7 @@ public class IoUtil extends NioUtil { * @return 内容 * @throws IORuntimeException IO异常 */ - public static > T readLines(Reader reader, final T collection) throws IORuntimeException { + public static > T readLines(Reader reader, T collection) throws IORuntimeException { readLines(reader, (LineHandler) collection::add); return collection; } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/FileSystemUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/file/FileSystemUtil.java new file mode 100644 index 000000000..502bf09be --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/FileSystemUtil.java @@ -0,0 +1,84 @@ +package cn.hutool.core.io.file; + +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; + +import java.io.IOException; +import java.net.URI; +import java.nio.charset.Charset; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; + +/** + * {@link FileSystem}相关工具类封装
+ * 参考:https://blog.csdn.net/j16421881/article/details/78858690 + * + * @author looly + * @since 5.7.15 + */ +public class FileSystemUtil { + + /** + * 创建 {@link FileSystem} + * + * @param path 文件路径,可以是目录或Zip文件等 + * @return {@link FileSystem} + */ + public static FileSystem create(String path) { + try { + return FileSystems.newFileSystem( + Paths.get(path).toUri(), + MapUtil.of("create", "true")); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 创建 Zip的{@link FileSystem},默认UTF-8编码 + * + * @param path 文件路径,可以是目录或Zip文件等 + * @return {@link FileSystem} + */ + public static FileSystem createZip(String path) { + return createZip(path, null); + } + + /** + * 创建 Zip的{@link FileSystem} + * + * @param path 文件路径,可以是目录或Zip文件等 + * @param charset 编码 + * @return {@link FileSystem} + */ + public static FileSystem createZip(String path, Charset charset) { + if(null == charset){ + charset = CharsetUtil.CHARSET_UTF_8; + } + final HashMap env = new HashMap<>(); + env.put("create", "true"); + env.put("encoding", charset.name()); + + try { + return FileSystems.newFileSystem( + URI.create("jar:" + Paths.get(path).toUri()), env); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 获取目录的根路径,或Zip文件中的根路径 + * + * @param fileSystem {@link FileSystem} + * @return 根 {@link Path} + */ + public static Path getRoot(FileSystem fileSystem) { + return fileSystem.getPath(StrUtil.SLASH); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java index d9423d126..1c3ed2d92 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java @@ -55,6 +55,19 @@ public class PathUtil { } } + /** + * 递归遍历目录以及子目录中的所有文件
+ * 如果提供path为文件,直接返回过滤结果 + * + * @param path 当前遍历文件或目录 + * @param fileFilter 文件过滤规则对象,选择要保留的文件,只对文件有效,不过滤目录,null表示接收全部文件 + * @return 文件列表 + * @since 5.4.1 + */ + public static List loopFiles(Path path, FileFilter fileFilter) { + return loopFiles(path, -1, fileFilter); + } + /** * 递归遍历目录以及子目录中的所有文件
* 如果提供path为文件,直接返回过滤结果 @@ -643,6 +656,20 @@ public class PathUtil { return mkdir(path.getParent()); } + /** + * 获取{@link Path}文件名 + * + * @param path {@link Path} + * @return 文件名 + * @since 5.7.15 + */ + public static String getName(Path path) { + if (null == path) { + return null; + } + return path.getFileName().toString(); + } + /** * 删除文件或空目录,不追踪软链 * diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/visitor/CopyVisitor.java b/hutool-core/src/main/java/cn/hutool/core/io/file/visitor/CopyVisitor.java index 842dbff1e..b36e8c193 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/visitor/CopyVisitor.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/visitor/CopyVisitor.java @@ -20,20 +20,27 @@ import java.nio.file.attribute.BasicFileAttributes; */ public class CopyVisitor extends SimpleFileVisitor { + /** + * 源Path,或基准路径,用于计算被拷贝文件的相对路径 + */ private final Path source; private final Path target; - private boolean isTargetCreated; private final CopyOption[] copyOptions; + /** + * 标记目标目录是否创建,省略每次判断目标是否存在 + */ + private boolean isTargetCreated; + /** * 构造 * - * @param source 源Path - * @param target 目标Path + * @param source 源Path,或基准路径,用于计算被拷贝文件的相对路径 + * @param target 目标Path * @param copyOptions 拷贝选项,如跳过已存在等 */ public CopyVisitor(Path source, Path target, CopyOption... copyOptions) { - if(PathUtil.exists(target, false) && false == PathUtil.isDirectory(target)){ + if (PathUtil.exists(target, false) && false == PathUtil.isDirectory(target)) { throw new IllegalArgumentException("Target must be a directory"); } this.source = source; @@ -42,16 +49,19 @@ public class CopyVisitor extends SimpleFileVisitor { } @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) - throws IOException { - initTarget(); + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + initTargetDir(); // 将当前目录相对于源路径转换为相对于目标路径 - final Path targetDir = target.resolve(source.relativize(dir)); + final Path targetDir = resolveTarget(dir); + + // 在目录不存在的情况下,copy方法会创建新目录 try { Files.copy(dir, targetDir, copyOptions); } catch (FileAlreadyExistsException e) { - if (false == Files.isDirectory(targetDir)) + if (false == Files.isDirectory(targetDir)) { + // 目标文件存在抛出异常,目录忽略 throw e; + } } return FileVisitResult.CONTINUE; } @@ -59,16 +69,33 @@ public class CopyVisitor extends SimpleFileVisitor { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - initTarget(); - Files.copy(file, target.resolve(source.relativize(file)), copyOptions); + initTargetDir(); + // 如果目标存在,无论目录还是文件都抛出FileAlreadyExistsException异常,此处不做特别处理 + Files.copy(file, resolveTarget(file), copyOptions); return FileVisitResult.CONTINUE; } + /** + * 根据源文件或目录路径,拼接生成目标的文件或目录路径
+ * 原理是首先截取源路径,得到相对路径,再和目标路径拼接 + * + *

+ * 如:源路径是 /opt/test/,需要拷贝的文件是 /opt/test/a/a.txt,得到相对路径 a/a.txt
+ * 目标路径是/home/,则得到最终目标路径是 /home/a/a.txt + *

+ * + * @param file 需要拷贝的文件或目录Path + * @return 目标Path + */ + private Path resolveTarget(Path file) { + return target.resolve(source.relativize(file)); + } + /** * 初始化目标文件或目录 */ - private void initTarget(){ - if(false == this.isTargetCreated){ + private void initTargetDir() { + if (false == this.isTargetCreated) { PathUtil.mkdir(this.target); this.isTargetCreated = true; } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Assert.java b/hutool-core/src/main/java/cn/hutool/core/lang/Assert.java index 1efdd5aef..fd4751d6a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Assert.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Assert.java @@ -16,6 +16,9 @@ import java.util.function.Supplier; */ public class Assert { + private static final String TEMPLATE_VALUE_MUST_BE_BETWEEN_AND = "The value must be between {} and {}."; + + /** * 断言是否为真,如果为 {@code false} 抛出给定的异常
* @@ -834,6 +837,38 @@ public class Assert { return index; } + /** + * 检查值是否在指定范围内 + * + * @param value 值 + * @param min 最小值(包含) + * @param max 最大值(包含) + * @param errorSupplier 错误抛出异常附带的消息生产接口 + * @return 经过检查后的值 + * @throws X if value is out of bound + * @since 5.7.15 + */ + public static int checkBetween(int value, int min, int max, Supplier errorSupplier) throws X { + if (value < min || value > max) { + throw errorSupplier.get(); + } + + return value; + } + + /** + * 检查值是否在指定范围内 + * + * @param value 值 + * @param min 最小值(包含) + * @param max 最大值(包含) + * @return 经过检查后的值 + * @since 5.7.15 + */ + public static int checkBetween(int value, int min, int max, String errorMsgTemplate, Object... params) { + return checkBetween(value, min, max, () -> new IllegalArgumentException(StrUtil.format(errorMsgTemplate, params))); + } + /** * 检查值是否在指定范围内 * @@ -844,12 +879,41 @@ public class Assert { * @since 4.1.10 */ public static int checkBetween(int value, int min, int max) { + return checkBetween(value, min, max, TEMPLATE_VALUE_MUST_BE_BETWEEN_AND, min, max); + } + + /** + * 检查值是否在指定范围内 + * + * @param value 值 + * @param min 最小值(包含) + * @param max 最大值(包含) + * @param errorSupplier 错误抛出异常附带的消息生产接口 + * @return 经过检查后的值 + * @throws X if value is out of bound + * @since 5.7.15 + */ + public static long checkBetween(long value, long min, long max, Supplier errorSupplier) throws X { if (value < min || value > max) { - throw new IllegalArgumentException(StrUtil.format("Length must be between {} and {}.", min, max)); + throw errorSupplier.get(); } + return value; } + /** + * 检查值是否在指定范围内 + * + * @param value 值 + * @param min 最小值(包含) + * @param max 最大值(包含) + * @return 经过检查后的值 + * @since 5.7.15 + */ + public static long checkBetween(long value, long min, long max, String errorMsgTemplate, Object... params) { + return checkBetween(value, min, max, () -> new IllegalArgumentException(StrUtil.format(errorMsgTemplate, params))); + } + /** * 检查值是否在指定范围内 * @@ -860,12 +924,41 @@ public class Assert { * @since 4.1.10 */ public static long checkBetween(long value, long min, long max) { + return checkBetween(value, min, max, TEMPLATE_VALUE_MUST_BE_BETWEEN_AND, min, max); + } + + /** + * 检查值是否在指定范围内 + * + * @param value 值 + * @param min 最小值(包含) + * @param max 最大值(包含) + * @param errorSupplier 错误抛出异常附带的消息生产接口 + * @return 经过检查后的值 + * @throws X if value is out of bound + * @since 5.7.15 + */ + public static double checkBetween(double value, double min, double max, Supplier errorSupplier) throws X { if (value < min || value > max) { - throw new IllegalArgumentException(StrUtil.format("Length must be between {} and {}.", min, max)); + throw errorSupplier.get(); } + return value; } + /** + * 检查值是否在指定范围内 + * + * @param value 值 + * @param min 最小值(包含) + * @param max 最大值(包含) + * @return 经过检查后的值 + * @since 5.7.15 + */ + public static double checkBetween(double value, double min, double max, String errorMsgTemplate, Object... params) { + return checkBetween(value, min, max, () -> new IllegalArgumentException(StrUtil.format(errorMsgTemplate, params))); + } + /** * 检查值是否在指定范围内 * @@ -876,10 +969,7 @@ public class Assert { * @since 4.1.10 */ public static double checkBetween(double value, double min, double max) { - if (value < min || value > max) { - throw new IllegalArgumentException(StrUtil.format("Length must be between {} and {}.", min, max)); - } - return value; + return checkBetween(value, min, max, TEMPLATE_VALUE_MUST_BE_BETWEEN_AND, min, max); } /** @@ -899,7 +989,7 @@ public class Assert { double minDouble = min.doubleValue(); double maxDouble = max.doubleValue(); if (valueDouble < minDouble || valueDouble > maxDouble) { - throw new IllegalArgumentException(StrUtil.format("Length must be between {} and {}.", min, max)); + throw new IllegalArgumentException(StrUtil.format(TEMPLATE_VALUE_MUST_BE_BETWEEN_AND, min, max)); } return value; } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java index 61c9f9ba6..aad2da056 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java @@ -318,7 +318,7 @@ public class CityHash { private static long hashLen0to16(byte[] byteArray) { int len = byteArray.length; if (len >= 8) { - long mul = k2 + len * 2; + long mul = k2 + len * 2L; long a = fetch64(byteArray, 0) + k2; long b = fetch64(byteArray, len - 8); long c = rotate(b, 37) * mul + a; @@ -344,7 +344,7 @@ public class CityHash { // This probably works well for 16-byte strings as well, but it may be overkill in that case. private static long hashLen17to32(byte[] byteArray) { int len = byteArray.length; - long mul = k2 + len * 2; + long mul = k2 + len * 2L; long a = fetch64(byteArray, 0) * k1; long b = fetch64(byteArray, 8); long c = fetch64(byteArray, len - 8) * mul; @@ -355,7 +355,7 @@ public class CityHash { private static long hashLen33to64(byte[] byteArray) { int len = byteArray.length; - long mul = k2 + len * 2; + long mul = k2 + len * 2L; long a = fetch64(byteArray, 0) * k2; long b = fetch64(byteArray, 8); long c = fetch64(byteArray, len - 24); diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash.java new file mode 100644 index 000000000..8ad0a9e35 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash.java @@ -0,0 +1,19 @@ +package cn.hutool.core.lang.hash; + +/** + * Hash计算接口 + * + * @param 被计算hash的对象类型 + * @author looly + * @since 5.7.15 + */ +@FunctionalInterface +public interface Hash { + /** + * 计算Hash值 + * + * @param t 对象 + * @return hash + */ + Number hash(T t); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash128.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash128.java index af5dfc496..b16b4c9ce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash128.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash128.java @@ -8,7 +8,8 @@ package cn.hutool.core.lang.hash; * @since 5.2.5 */ @FunctionalInterface -public interface Hash128 { +public interface Hash128 extends Hash{ + /** * 计算Hash值 * @@ -16,4 +17,9 @@ public interface Hash128 { * @return hash */ Number128 hash128(T t); -} \ No newline at end of file + + @Override + default Number hash(T t){ + return hash128(t); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash32.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash32.java index b0aaa8ced..7a9016697 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash32.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash32.java @@ -8,7 +8,7 @@ package cn.hutool.core.lang.hash; * @since 5.2.5 */ @FunctionalInterface -public interface Hash32 { +public interface Hash32 extends Hash{ /** * 计算Hash值 * @@ -16,4 +16,9 @@ public interface Hash32 { * @return hash */ int hash32(T t); -} \ No newline at end of file + + @Override + default Number hash(T t){ + return hash32(t); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash64.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash64.java index 9feac2969..61a50e4bd 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash64.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash64.java @@ -8,7 +8,7 @@ package cn.hutool.core.lang.hash; * @since 5.2.5 */ @FunctionalInterface -public interface Hash64 { +public interface Hash64 extends Hash{ /** * 计算Hash值 * @@ -16,4 +16,9 @@ public interface Hash64 { * @return hash */ long hash64(T t); -} \ No newline at end of file + + @Override + default Number hash(T t){ + return hash64(t); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java index 9af5bad4d..4f6301095 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java @@ -6,7 +6,8 @@ package cn.hutool.core.lang.hash; * @author hexiufeng * @since 5.2.5 */ -public class Number128 { +public class Number128 extends Number{ + private static final long serialVersionUID = 1L; private long lowValue; private long highValue; @@ -41,4 +42,24 @@ public class Number128 { public long[] getLongArray() { return new long[]{lowValue, highValue}; } + + @Override + public int intValue() { + return (int) longValue(); + } + + @Override + public long longValue() { + return this.lowValue; + } + + @Override + public float floatValue() { + return longValue(); + } + + @Override + public double doubleValue() { + return longValue(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableBool.java b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableBool.java index 2edf65727..1afbfbd18 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableBool.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableBool.java @@ -3,7 +3,7 @@ package cn.hutool.core.lang.mutable; import java.io.Serializable; /** - * 可变 boolean 类型 + * 可变 {@code boolean} 类型 * * @see Boolean * @since 3.0.1 @@ -59,12 +59,12 @@ public class MutableBool implements Comparable, Mutable, S * 相等需同时满足如下条件: *
    *
  1. 非空
  2. - *
  3. 类型为 {@link MutableBool}
  4. + *
  5. 类型为 MutableBool
  6. *
  7. 值相等
  8. *
* * @param obj 比对的对象 - * @return 相同返回true,否则 false + * @return 相同返回true,否则 {@code false} */ @Override public boolean equals(final Object obj) { @@ -83,7 +83,7 @@ public class MutableBool implements Comparable, Mutable, S /** * 比较 * - * @param other 其它 {@link MutableBool} 对象 + * @param other 其它 MutableBool 对象 * @return x==y返回0,x<y返回-1,x>y返回1 */ @Override diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableByte.java b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableByte.java index 0d0755bf8..55d539fc4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableByte.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableByte.java @@ -3,7 +3,7 @@ package cn.hutool.core.lang.mutable; import cn.hutool.core.util.NumberUtil; /** - * 可变 byte 类型 + * 可变 {@code byte} 类型 * * @see Byte * @since 3.0.1 @@ -157,12 +157,12 @@ public class MutableByte extends Number implements Comparable, Muta * 相等需同时满足如下条件: *
    *
  1. 非空
  2. - *
  3. 类型为 {@link MutableByte}
  4. + *
  5. 类型为 MutableByte
  6. *
  7. 值相等
  8. *
* * @param obj 比对的对象 - * @return 相同返回true,否则 false + * @return 相同返回true,否则 {@code false} */ @Override public boolean equals(final Object obj) { @@ -181,7 +181,7 @@ public class MutableByte extends Number implements Comparable, Muta /** * 比较 * - * @param other 其它 {@link MutableByte} 对象 + * @param other 其它 MutableByte 对象 * @return x==y返回0,x<y返回-1,x>y返回1 */ @Override diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableObj.java b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableObj.java index b516c454e..72a490366 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableObj.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableObj.java @@ -3,7 +3,7 @@ package cn.hutool.core.lang.mutable; import java.io.Serializable; /** - * 可变Object + * 可变{@code Object} * * @param 可变的类型 * @since 3.0.1 diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java index 357a215b8..be6d5c94b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -129,7 +129,7 @@ public final class UrlBuilder implements Serializable { */ public static UrlBuilder of(String url, Charset charset) { Assert.notBlank(url, "Url must be not blank!"); - return of(URLUtil.url(url.trim()), charset); + return of(URLUtil.url(StrUtil.trim(url)), charset); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index 3c297b9de..8f882d9f5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -312,7 +312,7 @@ public class UrlQuery { private static String toStr(CharSequence str, Charset charset, boolean isEncode) { String result = StrUtil.str(str); if (isEncode) { - result = URLUtil.encodeAll(result, charset); + result = URLUtil.encodeFragment(result, charset); } return result; } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java index 8c3974915..1a41d7282 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java @@ -1729,16 +1729,14 @@ public class CharSequenceUtil { /** * 切分字符串 * - * @param str 被切分的字符串 + * @param text 被切分的字符串 * @param separator 分隔符字符 * @param limit 限制分片数 * @return 切分后的数组 */ - public static String[] splitToArray(CharSequence str, char separator, int limit) { - if (null == str) { - return new String[]{}; - } - return StrSplitter.splitToArray(str.toString(), separator, limit, false, false); + public static String[] splitToArray(CharSequence text, char separator, int limit) { + Assert.notNull(text, "Text must be not null!"); + return StrSplitter.splitToArray(text.toString(), separator, limit, false, false); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrSplitter.java b/hutool-core/src/main/java/cn/hutool/core/text/StrSplitter.java index ec3d4aa45..d9333e61c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrSplitter.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrSplitter.java @@ -1,5 +1,6 @@ package cn.hutool.core.text; +import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.PatternPool; import cn.hutool.core.text.finder.CharFinder; import cn.hutool.core.text.finder.CharMatcherFinder; @@ -146,7 +147,7 @@ public class StrSplitter { /** * 切分字符串,忽略大小写 * - * @param str 被切分的字符串 + * @param text 被切分的字符串 * @param separator 分隔符字符 * @param limit 限制分片数,-1不限制 * @param isTrim 是否去除切分字符串后每个元素两边的空格 @@ -154,8 +155,8 @@ public class StrSplitter { * @return 切分后的集合 * @since 3.2.1 */ - public static List splitIgnoreCase(CharSequence str, char separator, int limit, boolean isTrim, boolean ignoreEmpty) { - return split(str, separator, limit, isTrim, ignoreEmpty, true); + public static List splitIgnoreCase(CharSequence text, char separator, int limit, boolean isTrim, boolean ignoreEmpty) { + return split(text, separator, limit, isTrim, ignoreEmpty, true); } /** @@ -177,7 +178,7 @@ public class StrSplitter { /** * 切分字符串 * - * @param 切分后的元素类型 + * @param 切分后的元素类型 * @param text 被切分的字符串 * @param separator 分隔符字符 * @param limit 限制分片数,-1不限制 @@ -204,7 +205,7 @@ public class StrSplitter { * @return 切分后的集合 * @since 3.0.8 */ - public static String[] splitToArray(String str, char separator, int limit, boolean isTrim, boolean ignoreEmpty) { + public static String[] splitToArray(CharSequence str, char separator, int limit, boolean isTrim, boolean ignoreEmpty) { return toArray(split(str, separator, limit, isTrim, ignoreEmpty)); } @@ -220,7 +221,7 @@ public class StrSplitter { * @return 切分后的集合 * @since 3.0.8 */ - public static List split(String str, String separator, boolean isTrim, boolean ignoreEmpty) { + public static List split(CharSequence str, String separator, boolean isTrim, boolean ignoreEmpty) { return split(str, separator, -1, isTrim, ignoreEmpty, false); } @@ -233,7 +234,7 @@ public class StrSplitter { * @return 切分后的集合 * @since 3.2.1 */ - public static List splitTrim(String str, String separator, boolean ignoreEmpty) { + public static List splitTrim(CharSequence str, String separator, boolean ignoreEmpty) { return split(str, separator, true, ignoreEmpty); } @@ -248,7 +249,7 @@ public class StrSplitter { * @return 切分后的集合 * @since 3.0.8 */ - public static List split(String str, String separator, int limit, boolean isTrim, boolean ignoreEmpty) { + public static List split(CharSequence str, String separator, int limit, boolean isTrim, boolean ignoreEmpty) { return split(str, separator, limit, isTrim, ignoreEmpty, false); } @@ -262,7 +263,7 @@ public class StrSplitter { * @return 切分后的集合 * @since 3.2.1 */ - public static List splitTrim(String str, String separator, int limit, boolean ignoreEmpty) { + public static List splitTrim(CharSequence str, String separator, int limit, boolean ignoreEmpty) { return split(str, separator, limit, true, ignoreEmpty); } @@ -277,7 +278,7 @@ public class StrSplitter { * @return 切分后的集合 * @since 3.2.1 */ - public static List splitIgnoreCase(String str, String separator, int limit, boolean isTrim, boolean ignoreEmpty) { + public static List splitIgnoreCase(CharSequence str, String separator, int limit, boolean isTrim, boolean ignoreEmpty) { return split(str, separator, limit, isTrim, ignoreEmpty, true); } @@ -291,7 +292,7 @@ public class StrSplitter { * @return 切分后的集合 * @since 3.2.1 */ - public static List splitTrimIgnoreCase(String str, String separator, int limit, boolean ignoreEmpty) { + public static List splitTrimIgnoreCase(CharSequence str, String separator, int limit, boolean ignoreEmpty) { return split(str, separator, limit, true, ignoreEmpty, true); } @@ -307,7 +308,7 @@ public class StrSplitter { * @return 切分后的集合 * @since 3.2.1 */ - public static List split(String text, String separator, int limit, boolean isTrim, boolean ignoreEmpty, boolean ignoreCase) { + public static List split(CharSequence text, String separator, int limit, boolean isTrim, boolean ignoreEmpty, boolean ignoreCase) { final SplitIter splitIter = new SplitIter(text, new StrFinder(separator, ignoreCase), limit, ignoreEmpty); return splitIter.toList(isTrim); } @@ -323,7 +324,7 @@ public class StrSplitter { * @return 切分后的集合 * @since 3.0.8 */ - public static String[] splitToArray(String str, String separator, int limit, boolean isTrim, boolean ignoreEmpty) { + public static String[] splitToArray(CharSequence str, String separator, int limit, boolean isTrim, boolean ignoreEmpty) { return toArray(split(str, separator, limit, isTrim, ignoreEmpty)); } @@ -338,7 +339,8 @@ public class StrSplitter { * @return 切分后的集合 * @since 3.0.8 */ - public static List split(String text, int limit) { + public static List split(CharSequence text, int limit) { + if (StrUtil.isEmpty(text)) { return new ArrayList<>(0); } @@ -362,7 +364,7 @@ public class StrSplitter { /** * 通过正则切分字符串 * - * @param str 字符串 + * @param text 字符串 * @param separatorRegex 分隔符正则 * @param limit 限制分片数 * @param isTrim 是否去除切分字符串后每个元素两边的空格 @@ -370,9 +372,9 @@ public class StrSplitter { * @return 切分后的集合 * @since 3.0.8 */ - public static List splitByRegex(String str, String separatorRegex, int limit, boolean isTrim, boolean ignoreEmpty) { + public static List splitByRegex(String text, String separatorRegex, int limit, boolean isTrim, boolean ignoreEmpty) { final Pattern pattern = PatternPool.get(separatorRegex); - return split(str, pattern, limit, isTrim, ignoreEmpty); + return split(text, pattern, limit, isTrim, ignoreEmpty); } /** @@ -387,7 +389,8 @@ public class StrSplitter { * @since 3.0.8 */ public static List split(String text, Pattern separatorPattern, int limit, boolean isTrim, boolean ignoreEmpty) { - if (StrUtil.isEmpty(text)) { + Assert.notNull(text, "Text must be not null!"); + if (text.length() < 1) { return new ArrayList<>(0); } final SplitIter splitIter = new SplitIter(text, new PatternFinder(separatorPattern), limit, ignoreEmpty); diff --git a/hutool-core/src/main/java/cn/hutool/core/text/split/SplitIter.java b/hutool-core/src/main/java/cn/hutool/core/text/split/SplitIter.java index c6d977696..847a23ce3 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/split/SplitIter.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/split/SplitIter.java @@ -53,7 +53,6 @@ public class SplitIter extends ComputeIter implements Serializable { @Override protected String computeNext() { - Assert.notNull(this.text, "Text to find must be not null!"); // 达到数量上限或末尾,结束 if (count >= limit || offset > text.length()) { return null; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java index aae07f8be..8d77384e7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java @@ -163,6 +163,16 @@ public class RandomUtil { return 0 == randomInt(2); } + /** + * 随机汉字('\u4E00'-'\u9FFF') + * + * @return 随机的汉字字符 + * @since 5.7.15 + */ + public static char randomChinese() { + return (char) randomInt('\u4E00', '\u9FFF'); + } + /** * 获得指定范围内的随机数 * diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index fcad035a5..e9212367f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -3,18 +3,23 @@ package cn.hutool.core.util; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Holder; import cn.hutool.core.lang.PatternPool; import cn.hutool.core.lang.RegexPool; import cn.hutool.core.lang.Validator; import cn.hutool.core.lang.func.Func1; +import cn.hutool.core.lang.mutable.MutableObj; +import cn.hutool.core.map.MapUtil; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.TreeSet; +import java.util.function.Consumer; import java.util.regex.MatchResult; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -78,11 +83,27 @@ public class ReUtil { return null; } - // Pattern pattern = Pattern.compile(regex, Pattern.DOTALL); final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL); return get(pattern, content, groupIndex); } + /** + * 获得匹配的字符串 + * + * @param regex 匹配的正则 + * @param content 被匹配的内容 + * @param groupName 匹配正则的分组名称 + * @return 匹配后得到的字符串,未匹配返回null + */ + public static String get(String regex, CharSequence content, String groupName) { + if (null == content || null == regex) { + return null; + } + + final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL); + return get(pattern, content, groupName); + } + /** * 获得匹配的字符串,,获得正则中分组0的内容 * @@ -120,11 +141,47 @@ public class ReUtil { return null; } - final Matcher matcher = pattern.matcher(content); - if (matcher.find()) { - return matcher.group(groupIndex); + final MutableObj result = new MutableObj<>(); + get(pattern, content, matcher -> result.set(matcher.group(groupIndex))); + return result.get(); + } + + /** + * 获得匹配的字符串 + * + * @param pattern 匹配的正则 + * @param content 被匹配的内容 + * @param groupName 匹配正则的分组名称 + * @return 匹配后得到的字符串,未匹配返回null + * @since 5.7.15 + */ + public static String get(Pattern pattern, CharSequence content, String groupName) { + if (null == content || null == pattern || null == groupName) { + return null; + } + + final MutableObj result = new MutableObj<>(); + get(pattern, content, matcher -> result.set(matcher.group(groupName))); + return result.get(); + } + + /** + * 在给定字符串中查找给定规则的字符,如果找到则使用{@link Consumer}处理之
+ * 如果内容中有多个匹配项,则只处理找到的第一个结果。 + * + * @param pattern 匹配的正则 + * @param content 被匹配的内容 + * @param consumer 匹配到的内容处理器 + * @since 5.7.15 + */ + public static void get(Pattern pattern, CharSequence content, Consumer consumer) { + if (null == content || null == pattern || null == consumer) { + return; + } + final Matcher m = pattern.matcher(content); + if (m.find()) { + consumer.accept(m); } - return null; } /** @@ -165,6 +222,33 @@ public class ReUtil { return result; } + /** + * 根据给定正则查找字符串中的匹配项,返回所有匹配的分组名对应分组值
+ *
+	 * pattern: (?<year>\\d+)-(?<month>\\d+)-(?<day>\\d+)
+	 * content: 2021-10-11
+	 * result : year: 2021, month: 10, day: 11
+	 * 
+ * + * @param pattern 匹配的正则 + * @param content 被匹配的内容 + * @return 命名捕获组,key为分组名,value为对应值 + * @since 5.7.15 + */ + public static Map getAllGroupNames(Pattern pattern, CharSequence content) { + if (null == content || null == pattern) { + return null; + } + final Matcher m = pattern.matcher(content); + final Map result = MapUtil.newHashMap(m.groupCount()); + if (m.find()) { + // 通过反射获取 namedGroups 方法 + final Map map = ReflectUtil.invoke(pattern, "namedGroups"); + map.forEach((key, value) -> result.put(key, m.group(value))); + } + return result; + } + /** * 从content中匹配出多个值并根据template生成新的字符串
* 例如:
@@ -213,7 +297,6 @@ public class ReUtil { return null; } - // Pattern pattern = Pattern.compile(regex, Pattern.DOTALL); final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL); return extractMulti(pattern, content, template); } @@ -264,7 +347,6 @@ public class ReUtil { return null; } - // Pattern pattern = Pattern.compile(regex, Pattern.DOTALL); final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL); return extractMultiAndDelPre(pattern, contentHolder, template); } @@ -281,7 +363,6 @@ public class ReUtil { return StrUtil.str(content); } - // Pattern pattern = Pattern.compile(regex, Pattern.DOTALL); final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL); return delFirst(pattern, content); } @@ -300,8 +381,8 @@ public class ReUtil { /** * 替换匹配的第一个内容 * - * @param pattern 正则 - * @param content 被匹配的内容 + * @param pattern 正则 + * @param content 被匹配的内容 * @param replacement 替换的内容 * @return 替换后剩余的内容 * @since 5.6.5 @@ -342,7 +423,7 @@ public class ReUtil { public static String delLast(Pattern pattern, CharSequence str) { if (null != pattern && StrUtil.isNotEmpty(str)) { final MatchResult matchResult = lastIndexOf(pattern, str); - if(null != matchResult){ + if (null != matchResult) { return StrUtil.subPre(str, matchResult.start()) + StrUtil.subSuf(str, matchResult.end()); } } @@ -362,7 +443,6 @@ public class ReUtil { return StrUtil.str(content); } - // Pattern pattern = Pattern.compile(regex, Pattern.DOTALL); final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL); return delAll(pattern, content); } @@ -394,9 +474,23 @@ public class ReUtil { return StrUtil.str(content); } - // Pattern pattern = Pattern.compile(regex, Pattern.DOTALL); final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL); - Matcher matcher = pattern.matcher(content); + return delPre(pattern, content); + } + + /** + * 删除正则匹配到的内容之前的字符 如果没有找到,则返回原文 + * + * @param pattern 定位正则模式 + * @param content 被查找的内容 + * @return 删除前缀后的新内容 + */ + public static String delPre(Pattern pattern, CharSequence content) { + if (null == content || null == pattern) { + return StrUtil.str(content); + } + + final Matcher matcher = pattern.matcher(content); if (matcher.find()) { return StrUtil.sub(content, matcher.end(), content.length()); } @@ -455,7 +549,7 @@ public class ReUtil { return collection; } - return findAll(Pattern.compile(regex, Pattern.DOTALL), content, group, collection); + return findAll(PatternPool.get(regex, Pattern.DOTALL), content, group, collection); } /** @@ -509,16 +603,29 @@ public class ReUtil { if (null == pattern || null == content) { return null; } + Assert.notNull(collection, "Collection must be not null !"); - if (null == collection) { - throw new NullPointerException("Null collection param provided!"); + findAll(pattern, content, (matcher) -> collection.add(matcher.group(group))); + return collection; + } + + /** + * 取得内容中匹配的所有结果,使用{@link Consumer}完成匹配结果处理 + * + * @param pattern 编译后的正则模式 + * @param content 被查找的内容 + * @param consumer 匹配结果处理函数 + * @since 5.7.15 + */ + public static void findAll(Pattern pattern, CharSequence content, Consumer consumer) { + if (null == pattern || null == content) { + return; } final Matcher matcher = pattern.matcher(content); while (matcher.find()) { - collection.add(matcher.group(group)); + consumer.accept(matcher); } - return collection; } /** @@ -533,7 +640,6 @@ public class ReUtil { return 0; } - // Pattern pattern = Pattern.compile(regex, Pattern.DOTALL); final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL); return count(pattern, content); } @@ -594,12 +700,12 @@ public class ReUtil { /** * 找到指定正则匹配到字符串的开始位置 * - * @param regex 正则 + * @param regex 正则 * @param content 字符串 * @return 位置,{@code null}表示未找到 * @since 5.6.5 */ - public static MatchResult indexOf(String regex, CharSequence content){ + public static MatchResult indexOf(String regex, CharSequence content) { if (null == regex || null == content) { return null; } @@ -616,10 +722,10 @@ public class ReUtil { * @return 位置,{@code null}表示未找到 * @since 5.6.5 */ - public static MatchResult indexOf(Pattern pattern, CharSequence content){ - if(null != pattern && null != content){ + public static MatchResult indexOf(Pattern pattern, CharSequence content) { + if (null != pattern && null != content) { final Matcher matcher = pattern.matcher(content); - if(matcher.find()){ + if (matcher.find()) { return matcher.toMatchResult(); } } @@ -630,12 +736,12 @@ public class ReUtil { /** * 找到指定正则匹配到第一个字符串的位置 * - * @param regex 正则 + * @param regex 正则 * @param content 字符串 * @return 位置,{@code null}表示未找到 * @since 5.6.5 */ - public static MatchResult lastIndexOf(String regex, CharSequence content){ + public static MatchResult lastIndexOf(String regex, CharSequence content) { if (null == regex || null == content) { return null; } @@ -652,11 +758,11 @@ public class ReUtil { * @return 位置,{@code null}表示未找到 * @since 5.6.5 */ - public static MatchResult lastIndexOf(Pattern pattern, CharSequence content){ + public static MatchResult lastIndexOf(Pattern pattern, CharSequence content) { MatchResult result = null; - if(null != pattern && null != content){ + if (null != pattern && null != content) { final Matcher matcher = pattern.matcher(content); - while(matcher.find()){ + while (matcher.find()) { result = matcher.toMatchResult(); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index c40ded1cd..a813242bf 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -2,6 +2,7 @@ package cn.hutool.core.util; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; +import cn.hutool.core.compress.ZipCopyVisitor; import cn.hutool.core.compress.ZipReader; import cn.hutool.core.compress.ZipWriter; import cn.hutool.core.exceptions.UtilException; @@ -9,6 +10,8 @@ import cn.hutool.core.io.FastByteArrayOutputStream; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.file.FileSystemUtil; +import cn.hutool.core.io.file.PathUtil; import cn.hutool.core.io.resource.Resource; import java.io.BufferedInputStream; @@ -20,6 +23,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; +import java.nio.file.CopyOption; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -32,8 +40,8 @@ import java.util.zip.ZipOutputStream; /** * 压缩工具类 * - * @see cn.hutool.core.compress.ZipWriter * @author Looly + * @see cn.hutool.core.compress.ZipWriter */ public class ZipUtil { @@ -75,6 +83,33 @@ public class ZipUtil { } } + /** + * 在zip文件中添加新文件或目录
+ * 新文件添加在zip根目录,文件夹包括其本身和内容
+ * 如果待添加文件夹是系统根路径(如/或c:/),则只复制文件夹下的内容 + * + * @param zipPath zip文件的Path + * @param appendFilePath 待添加文件Path(可以是文件夹) + * @param options 拷贝选项,可选是否覆盖等 + * @since 5.7.15 + */ + public static void append(Path zipPath, Path appendFilePath, CopyOption... options) throws IOException { + try (FileSystem zipFileSystem = FileSystemUtil.createZip(zipPath.toString())) { + if (Files.isDirectory(appendFilePath)) { + Path source = appendFilePath.getParent(); + if (null == source) { + // 如果用户提供的是根路径,则不复制目录,直接复制目录下的内容 + source = appendFilePath; + } + Files.walkFileTree(appendFilePath, new ZipCopyVisitor(source, zipFileSystem, options)); + } else { + Files.copy(appendFilePath, zipFileSystem.getPath(PathUtil.getName(appendFilePath)), options); + } + } catch (FileAlreadyExistsException ignored) { + // 不覆盖情况下,文件已存在, 跳过 + } + } + /** * 打包到当前目录,使用默认编码UTF-8 * @@ -240,7 +275,7 @@ public class ZipUtil { */ @Deprecated public static void zip(ZipOutputStream zipOutputStream, boolean withSrcDir, FileFilter filter, File... srcFiles) throws IORuntimeException { - try(final ZipWriter zipWriter = new ZipWriter(zipOutputStream)){ + try (final ZipWriter zipWriter = new ZipWriter(zipOutputStream)) { zipWriter.add(withSrcDir, filter, srcFiles); } } @@ -339,7 +374,7 @@ public class ZipUtil { throw new IllegalArgumentException("Paths length is not equals to ins length !"); } - try(final ZipWriter zipWriter = ZipWriter.of(zipFile, charset)){ + try (final ZipWriter zipWriter = ZipWriter.of(zipFile, charset)) { for (int i = 0; i < paths.length; i++) { zipWriter.add(paths[i], ins[i]); } @@ -364,7 +399,7 @@ public class ZipUtil { throw new IllegalArgumentException("Paths length is not equals to ins length !"); } - try(final ZipWriter zipWriter = ZipWriter.of(out, DEFAULT_CHARSET)){ + try (final ZipWriter zipWriter = ZipWriter.of(out, DEFAULT_CHARSET)) { for (int i = 0; i < paths.length; i++) { zipWriter.add(paths[i], ins[i]); } @@ -388,7 +423,7 @@ public class ZipUtil { throw new IllegalArgumentException("Paths length is not equals to ins length !"); } - try(final ZipWriter zipWriter = new ZipWriter(zipOutputStream)){ + try (final ZipWriter zipWriter = new ZipWriter(zipOutputStream)) { for (int i = 0; i < paths.length; i++) { zipWriter.add(paths[i], ins[i]); } @@ -528,7 +563,7 @@ public class ZipUtil { StrUtil.format("Target path [{}] exist!", outFile.getAbsolutePath())); } - try(final ZipReader reader = new ZipReader(zipFile)){ + try (final ZipReader reader = new ZipReader(zipFile)) { reader.readTo(outFile); } return outFile; @@ -571,7 +606,7 @@ public class ZipUtil { * @since 5.5.2 */ public static void read(ZipFile zipFile, Consumer consumer) { - try(final ZipReader reader = new ZipReader(zipFile)){ + try (final ZipReader reader = new ZipReader(zipFile)) { reader.read(consumer); } } @@ -605,7 +640,7 @@ public class ZipUtil { * @since 4.5.8 */ public static File unzip(ZipInputStream zipStream, File outFile) throws UtilException { - try(final ZipReader reader = new ZipReader(zipStream)){ + try (final ZipReader reader = new ZipReader(zipStream)) { reader.readTo(outFile); } return outFile; @@ -619,7 +654,7 @@ public class ZipUtil { * @since 5.5.2 */ public static void read(ZipInputStream zipStream, Consumer consumer) { - try(final ZipReader reader = new ZipReader(zipStream)){ + try (final ZipReader reader = new ZipReader(zipStream)) { reader.read(consumer); } } @@ -671,7 +706,7 @@ public class ZipUtil { * @since 4.1.8 */ public static byte[] unzipFileBytes(File zipFile, Charset charset, String name) { - try(final ZipReader reader = ZipReader.of(zipFile, charset)){ + try (final ZipReader reader = ZipReader.of(zipFile, charset)) { return IoUtil.readBytes(reader.get(name)); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java index 526e8bbca..d05bc13b0 100644 --- a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java @@ -197,6 +197,19 @@ public class BeanUtilTest { Assert.assertEquals("sub名字", map.get("sub_name")); } + @Test + public void beanToMapWithValueEditTest() { + SubPerson person = new SubPerson(); + person.setAge(14); + person.setOpenid("11213232"); + person.setName("测试A11"); + person.setSubName("sub名字"); + + Map map = BeanUtil.beanToMap(person, new LinkedHashMap<>(), + CopyOptions.create().setFieldValueEditor((key, value) -> key + "_" + value)); + Assert.assertEquals("subName_sub名字", map.get("subName")); + } + @Test public void beanToMapWithAliasTest() { SubPersonWithAlias person = new SubPersonWithAlias(); diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/RingIndexUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/RingIndexUtilTest.java new file mode 100644 index 000000000..4ff70cdd1 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/collection/RingIndexUtilTest.java @@ -0,0 +1,34 @@ +package cn.hutool.core.collection; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.thread.ThreadUtil; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 集合索引环形获取工具类测试类 + * + * @author ZhouChuGang + */ +public class RingIndexUtilTest { + + private final List strList = Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"); + + /** + * 观察输出的打印为不重复的 + */ + @Test + public void ringNextIntByObjTest() { + final AtomicInteger atomicInteger = new AtomicInteger(); + // 开启并发测试,每个线程获取到的元素都是唯一的 + ThreadUtil.concurrencyTest(strList.size(), () -> { + final int index = RingIndexUtil.ringNextIntByObj(strList, atomicInteger); + final String s = strList.get(index); + Assert.notNull(s); + }); + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/compress/ZipWriterTest.java b/hutool-core/src/test/java/cn/hutool/core/compress/ZipWriterTest.java index 97bb6af52..c2a2f5bd4 100755 --- a/hutool-core/src/test/java/cn/hutool/core/compress/ZipWriterTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/compress/ZipWriterTest.java @@ -1,5 +1,8 @@ package cn.hutool.core.compress; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.resource.FileResource; +import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ZipUtil; import org.junit.Ignore; import org.junit.Test; @@ -13,4 +16,12 @@ public class ZipWriterTest { public void zipDirTest() { ZipUtil.zip(new File("d:/test")); } + + @Test + @Ignore + public void addTest(){ + final ZipWriter writer = ZipWriter.of(FileUtil.file("d:/test/test.zip"), CharsetUtil.CHARSET_UTF_8); + writer.add(new FileResource("d:/test/qr_c.png")); + writer.close(); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index 05c674cf5..738995533 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -989,4 +989,10 @@ public class DateUtilTest { Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); } + + @Test + public void parseByDateTimeFormatterTest(){ + final DateTime parse = DateUtil.parse("2021-12-01", DatePattern.NORM_DATE_FORMATTER); + Assert.assertEquals("2021-12-01 00:00:00", parse.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/io/file/FileSystemUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/file/FileSystemUtilTest.java new file mode 100644 index 000000000..130b400f8 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/io/file/FileSystemUtilTest.java @@ -0,0 +1,31 @@ +package cn.hutool.core.io.file; + +import cn.hutool.core.lang.Console; +import cn.hutool.core.util.CharsetUtil; +import org.junit.Ignore; +import org.junit.Test; + +import java.nio.file.FileSystem; +import java.nio.file.FileVisitResult; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +public class FileSystemUtilTest { + + @Test + @Ignore + public void listTest(){ + final FileSystem fileSystem = FileSystemUtil.createZip("d:/test/test.zip", + CharsetUtil.CHARSET_GBK); + final Path root = FileSystemUtil.getRoot(fileSystem); + PathUtil.walkFiles(root, new SimpleFileVisitor() { + + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { + Console.log(path); + return FileVisitResult.CONTINUE; + } + }); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java index f53085fc6..78e4d93ee 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java @@ -192,9 +192,9 @@ public class UrlBuilderTest { "&sn=1044c0d19723f74f04f4c1da34eefa35" + "&chksm=6cbda3a25bca2ab4516410db6ce6e125badaac2f8c5548ea6e18eab6dc3c5422cb8cbe1095f7"; final UrlBuilder builder = UrlBuilder.ofHttp(urlStr, CharsetUtil.CHARSET_UTF_8); - // 原URL中的&替换为&,value中的=被编码为%3D + // 原URL中的&替换为& Assert.assertEquals("https://mp.weixin.qq.com/s?" + - "__biz=MzI5NjkyNTIxMg%3D%3D" + + "__biz=MzI5NjkyNTIxMg==" + "&mid=100000465&idx=1" + "&sn=1044c0d19723f74f04f4c1da34eefa35" + "&chksm=6cbda3a25bca2ab4516410db6ce6e125badaac2f8c5548ea6e18eab6dc3c5422cb8cbe1095f7", @@ -240,7 +240,7 @@ public class UrlBuilderTest { public void testEncodeInQuery() { String webUrl = "http://exmple.com/patha/pathb?a=123&b=4?6&c=789"; // b=4?6 参数中有未编码的? final UrlBuilder urlBuilder = UrlBuilder.of(webUrl, StandardCharsets.UTF_8); - Assert.assertEquals("a=123&b=4%3F6&c=789", urlBuilder.getQueryStr()); + Assert.assertEquals("a=123&b=4?6&c=789", urlBuilder.getQueryStr()); } @Test @@ -271,4 +271,14 @@ public class UrlBuilderTest { urlBuilder = UrlBuilder.ofHttp(urlBuilder.toString()); Assert.assertEquals(urlBuilder.toString(), urlBuilder.toString()); } + + @Test + public void slashEncodeTest(){ + // https://github.com/dromara/hutool/issues/1904 + // 在query中,"/"是不可转义字符 + // 见:https://www.rfc-editor.org/rfc/rfc3986.html#section-3.4 + String url = "https://invoice.maycur.com/2b27a802-8423-4d41-86f5-63a6b259f61e.xlsx?download/2b27a802-8423-4d41-86f5-63a6b259f61e.xlsx&e=1630491088"; + final UrlBuilder urlBuilder = UrlBuilder.ofHttp(url); + Assert.assertEquals(url, urlBuilder.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 1a5e2df3e..2ce2eee8c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -34,7 +34,7 @@ public class StrJoinerTest { public void joinMultiArrayTest(){ final StrJoiner append = StrJoiner.of(","); append.append(new Object[]{ListUtil.of("1", "2"), - CollUtil.newHashSet("3", "4") + CollUtil.newLinkedHashSet("3", "4") }); Assert.assertEquals("1,2,3,4", append.toString()); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java index 42ba46299..978fcd2ed 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java @@ -53,4 +53,10 @@ public class RandomUtilTest { final byte[] c = RandomUtil.randomBytes(10); Assert.assertNotNull(c); } + + @Test + public void randomChineseTest(){ + char c = RandomUtil.randomChinese(); + Assert.assertTrue(c > 0); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReUtilTest.java index 4c392edc0..0b26e6241 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReUtilTest.java @@ -8,6 +8,7 @@ import org.junit.Test; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.regex.Pattern; public class ReUtilTest { @@ -163,4 +164,26 @@ public class ReUtilTest { "(.+?)省(.+?)市(.+?)区", "广东省深圳市南山区"); Console.log(match); } + + @Test + public void getByGroupNameTest() { + String content = "2021-10-11"; + String regex = "(?\\d+)-(?\\d+)-(?\\d+)"; + String year = ReUtil.get(regex, content, "year"); + Assert.assertEquals("2021", year); + String month = ReUtil.get(regex, content, "month"); + Assert.assertEquals("10", month); + String day = ReUtil.get(regex, content, "day"); + Assert.assertEquals("11", day); + } + + @Test + public void getAllGroupNamesTest() { + String content = "2021-10-11"; + String regex = "(?\\d+)-(?\\d+)-(?\\d+)"; + Map map = ReUtil.getAllGroupNames(PatternPool.get(regex, Pattern.DOTALL), content); + Assert.assertEquals(map.get("year"), "2021"); + Assert.assertEquals(map.get("month"), "10"); + Assert.assertEquals(map.get("day"), "11"); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java index 73d8e6e7f..b0aca478c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java @@ -81,6 +81,16 @@ public class StrUtilTest { Assert.assertEquals("", split.get(2)); } + @Test(expected = IllegalArgumentException.class) + public void splitNullTest() { + StrUtil.split(null, '.'); + } + + @Test(expected = IllegalArgumentException.class) + public void splitToArrayNullTest() { + StrUtil.splitToArray(null, '.'); + } + @Test public void splitToLongTest() { String str = "1,2,3,4, 5"; diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 0246f6688..a98972e67 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.compress.ZipReader; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.lang.Console; @@ -12,6 +13,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; /** * {@link ZipUtil}单元测试 @@ -20,6 +23,54 @@ import java.nio.charset.Charset; */ public class ZipUtilTest { + @Test + public void appendTest() throws IOException { + File appendFile = FileUtil.file("test-zip/addFile.txt"); + File zipFile = FileUtil.file("test-zip/test.zip"); + + // 用于测试完成后将被测试文件恢复 + File tempZipFile = FileUtil.createTempFile(FileUtil.file("test-zip")); + tempZipFile.deleteOnExit(); + FileUtil.copy(zipFile, tempZipFile, true); + + // test file add + List beforeNames = zipEntryNames(tempZipFile); + ZipUtil.append(tempZipFile.toPath(), appendFile.toPath()); + List afterNames = zipEntryNames(tempZipFile); + + // 确认增加了文件 + Assert.assertEquals(beforeNames.size() + 1, afterNames.size()); + Assert.assertTrue(afterNames.containsAll(beforeNames)); + Assert.assertTrue(afterNames.contains(appendFile.getName())); + + // test dir add + beforeNames = zipEntryNames(tempZipFile); + File addDirFile = FileUtil.file("test-zip/test-add"); + ZipUtil.append(tempZipFile.toPath(), addDirFile.toPath()); + afterNames = zipEntryNames(tempZipFile); + + // 确认增加了文件和目录,增加目录和目录下一个文件,故此处+2 + Assert.assertEquals(beforeNames.size() + 2, afterNames.size()); + Assert.assertTrue(afterNames.containsAll(beforeNames)); + Assert.assertTrue(afterNames.contains(appendFile.getName())); + + // rollback + Assert.assertTrue(String.format("delete temp file %s failed", tempZipFile.getCanonicalPath()), tempZipFile.delete()); + } + + /** + * 获取zip文件中所有一级文件/文件夹的name + * + * @param zipFile 待测试的zip文件 + * @return zip文件中一级目录下的所有文件/文件夹名 + */ + private List zipEntryNames(File zipFile) { + List fileNames = new ArrayList<>(); + ZipReader reader = ZipReader.of(zipFile, CharsetUtil.CHARSET_UTF_8); + reader.read(zipEntry -> fileNames.add(zipEntry.getName())); + reader.close(); + return fileNames; + } @Test @Ignore @@ -109,4 +160,24 @@ public class ZipUtilTest { } } + @Test + @Ignore + public void zipStreamTest2(){ + // https://github.com/dromara/hutool/issues/944 + String file1 = "d:/test/a.txt"; + String file2 = "d:/test/a.txt"; + String file3 = "d:/test/asn1.key"; + + String zip = "d:/test/test2.zip"; + try (OutputStream out = new FileOutputStream(zip)){ + //实际应用中, out 为 HttpServletResponse.getOutputStream + ZipUtil.zip(out, Charset.defaultCharset(), false, null, + new File(file1), + new File(file2), + new File(file3) + ); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } } diff --git a/hutool-core/src/test/resources/test-zip/addFile.txt b/hutool-core/src/test/resources/test-zip/addFile.txt new file mode 100644 index 000000000..8d1c2fee6 --- /dev/null +++ b/hutool-core/src/test/resources/test-zip/addFile.txt @@ -0,0 +1,2 @@ +this file will be used to add into the test.zip +before the add action, the test.zip won't have this file. diff --git a/hutool-core/src/test/resources/test-zip/test-add/test.txt b/hutool-core/src/test/resources/test-zip/test-add/test.txt new file mode 100644 index 000000000..56a6051ca --- /dev/null +++ b/hutool-core/src/test/resources/test-zip/test-add/test.txt @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/hutool-core/src/test/resources/test-zip/test.zip b/hutool-core/src/test/resources/test-zip/test.zip new file mode 100644 index 000000000..86126d555 Binary files /dev/null and b/hutool-core/src/test/resources/test-zip/test.zip differ diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 2f6d5502c..62dc9962c 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index b35241605..3a89f935d 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index b9df3e891..90ef70540 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -180,7 +180,7 @@ public class KeyUtil { */ public static SecretKey generateDESKey(String algorithm, byte[] key) { if (StrUtil.isBlank(algorithm) || false == algorithm.startsWith("DES")) { - throw new CryptoException("Algorithm [{}] is not a DES algorithm!"); + throw new CryptoException("Algorithm [{}] is not a DES algorithm!", algorithm); } SecretKey secretKey; @@ -212,7 +212,7 @@ public class KeyUtil { */ public static SecretKey generatePBEKey(String algorithm, char[] key) { if (StrUtil.isBlank(algorithm) || false == algorithm.startsWith("PBE")) { - throw new CryptoException("Algorithm [{}] is not a PBE algorithm!"); + throw new CryptoException("Algorithm [{}] is not a PBE algorithm!", algorithm); } if (null == key) { diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java index abc31d182..ba92ab553 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java @@ -300,9 +300,11 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, throw new CryptoException(e); } finally { lock.unlock(); + // issue#I4EMST@Gitee + // CipherOutputStream必须关闭,才能完全写出 + IoUtil.close(cipherOutputStream); if (isClose) { IoUtil.close(data); - IoUtil.close(cipherOutputStream); } } } @@ -351,9 +353,11 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, throw new CryptoException(e); } finally { lock.unlock(); + // issue#I4EMST@Gitee + // CipherOutputStream必须关闭,才能完全写出 + IoUtil.close(cipherInputStream); if (isClose) { IoUtil.close(data); - IoUtil.close(cipherInputStream); } } } diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SmTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/SmTest.java index 80c7995fe..c98dd21ee 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SmTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/SmTest.java @@ -81,4 +81,6 @@ public class SmTest { String digest = hMac.digestHex(content); Assert.assertEquals("493e3f9a1896b43075fbe54658076727960d69632ac6b6ed932195857a6840c6", digest); } + + } diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/Sm4StreamTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/Sm4StreamTest.java new file mode 100644 index 000000000..87ec2675c --- /dev/null +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/Sm4StreamTest.java @@ -0,0 +1,51 @@ +package cn.hutool.crypto.test.symmetric; + +import cn.hutool.crypto.symmetric.SM4; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * https://gitee.com/dromara/hutool/issues/I4EMST + */ +public class Sm4StreamTest { + + private static final SM4 sm4 = new SM4(); + + private static final boolean IS_CLOSE = false; + + @Test + @Ignore + public void sm4Test(){ + String source = "d:/test/sm4_1.txt"; + String target = "d:/test/sm4_2.data"; + String target2 = "d:/test/sm4_3.txt"; + encrypt(source, target); + decrypt(target, target2); + } + + public static void encrypt(String source, String target) { + try (InputStream input = new FileInputStream(source); + OutputStream out = new FileOutputStream(target)) { + sm4.encrypt(input, out, IS_CLOSE); + System.out.println("============encrypt end"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void decrypt(String source, String target) { + try (InputStream input = new FileInputStream(source); + OutputStream out = new FileOutputStream(target)) { + sm4.decrypt(input, out, IS_CLOSE); + System.out.println("============decrypt end"); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 2d0c46b64..dcfd6d332 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-db @@ -143,13 +143,13 @@ mysql mysql-connector-java - 8.0.26 + 8.0.27 test org.postgresql postgresql - 42.2.23.jre7 + 42.3.0 test diff --git a/hutool-db/src/main/java/cn/hutool/db/Db.java b/hutool-db/src/main/java/cn/hutool/db/Db.java index 4af1b1dcb..f1b3f23aa 100644 --- a/hutool-db/src/main/java/cn/hutool/db/Db.java +++ b/hutool-db/src/main/java/cn/hutool/db/Db.java @@ -1,10 +1,5 @@ package cn.hutool.db; -import java.sql.Connection; -import java.sql.SQLException; - -import javax.sql.DataSource; - import cn.hutool.core.lang.func.VoidFunc1; import cn.hutool.db.dialect.Dialect; import cn.hutool.db.dialect.DialectFactory; @@ -13,10 +8,14 @@ import cn.hutool.db.sql.Wrapper; import cn.hutool.db.transaction.TransactionLevel; import cn.hutool.log.StaticLog; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + /** * 数据库操作类
* 通过给定的数据源执行给定SQL或者给定数据源和方言,执行相应的CRUD操作
- * + * * @author Looly * @since 4.1.2 */ @@ -26,7 +25,7 @@ public class Db extends AbstractDb { /** * 创建Db
* 使用默认数据源,自动探测数据库连接池 - * + * * @return Db */ public static Db use() { @@ -36,7 +35,7 @@ public class Db extends AbstractDb { /** * 创建Db
* 使用默认数据源,自动探测数据库连接池 - * + * * @param group 数据源分组 * @return Db */ @@ -47,7 +46,7 @@ public class Db extends AbstractDb { /** * 创建Db
* 会根据数据源连接的元信息识别目标数据库类型,进而使用合适的数据源 - * + * * @param ds 数据源 * @return Db */ @@ -57,7 +56,7 @@ public class Db extends AbstractDb { /** * 创建Db - * + * * @param ds 数据源 * @param dialect 方言 * @return Db @@ -68,7 +67,7 @@ public class Db extends AbstractDb { /** * 创建Db - * + * * @param ds 数据源 * @param driverClassName 数据库连接驱动类名 * @return Db @@ -80,7 +79,7 @@ public class Db extends AbstractDb { // ---------------------------------------------------------------------------- Constructor start /** * 构造,从DataSource中识别方言 - * + * * @param ds 数据源 */ public Db(DataSource ds) { @@ -89,7 +88,7 @@ public class Db extends AbstractDb { /** * 构造 - * + * * @param ds 数据源 * @param driverClassName 数据库连接驱动类名,用于识别方言 */ @@ -99,7 +98,7 @@ public class Db extends AbstractDb { /** * 构造 - * + * * @param ds 数据源 * @param dialect 方言 */ @@ -118,7 +117,7 @@ public class Db extends AbstractDb { public Db setWrapper(Wrapper wrapper) { return (Db) super.setWrapper(wrapper); } - + @Override public Db disableWrapper() { return (Db)super.disableWrapper(); @@ -147,7 +146,7 @@ public class Db extends AbstractDb { /** * 执行事务,使用默认的事务级别
* 在同一事务中,所有对数据库操作都是原子的,同时提交或者同时回滚 - * + * * @param func 事务函数,所有操作应在同一函数下执行,确保在同一事务中 * @return this * @throws SQLException SQL异常 @@ -159,7 +158,7 @@ public class Db extends AbstractDb { /** * 执行事务
* 在同一事务中,所有对数据库操作都是原子的,同时提交或者同时回滚 - * + * * @param transactionLevel 事务级别枚举,null表示使用JDBC默认事务 * @param func 事务函数,所有操作应在同一函数下执行,确保在同一事务中 * @return this @@ -208,7 +207,7 @@ public class Db extends AbstractDb { // ---------------------------------------------------------------------------- Private method start /** * 静默回滚事务 - * + * * @param conn Connection */ private void quietRollback(Connection conn) { @@ -223,12 +222,12 @@ public class Db extends AbstractDb { /** * 静默设置自动提交 - * + * * @param conn Connection * @param autoCommit 是否自动提交 */ private void quietSetAutoCommit(Connection conn, Boolean autoCommit) { - if (null != autoCommit) { + if (null != conn && null != autoCommit) { try { conn.setAutoCommit(autoCommit); } catch (Exception e) { @@ -237,4 +236,4 @@ public class Db extends AbstractDb { } } // ---------------------------------------------------------------------------- Private method end -} \ No newline at end of file +} diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index a2a5ad72d..65d431f9e 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 141c2fd16..9d7626fcf 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-extra @@ -30,7 +30,7 @@ 3.7.2 5.1.1 4.0.1 - 2.5.5 + 2.5.6 3.3.0 @@ -241,7 +241,7 @@ org.apache.lucene lucene-analyzers-smartcn - 8.10.0 + 8.10.1 true @@ -326,7 +326,7 @@ com.github.houbb pinyin - 0.2.1 + 0.2.2 true @@ -418,7 +418,7 @@ org.mvel mvel2 - 2.4.12.Final + 2.4.13.Final compile true @@ -432,7 +432,7 @@ org.springframework spring-expression - 5.3.10 + 5.3.12 compile true diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index a74af7073..81f74df28 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -470,9 +470,9 @@ public class Ftp extends AbstractFtp { * 上传文件到指定目录,可选: * *
-	 * 1. path为null或""上传到当前路径
-	 * 2. path为相对路径则相对于当前路径的子路径
-	 * 3. path为绝对路径则上传到此路径
+	 * 1. destPath为null或""上传到当前路径
+	 * 2. destPath为相对路径则相对于当前路径的子路径
+	 * 3. destPath为绝对路径则上传到此路径
 	 * 
* * @param destPath 服务端路径,可以为{@code null} 或者相对路径或绝对路径 @@ -495,14 +495,14 @@ public class Ftp extends AbstractFtp { * * * @param file 文件 - * @param path 服务端路径,可以为{@code null} 或者相对路径或绝对路径 + * @param destPath 服务端路径,可以为{@code null} 或者相对路径或绝对路径 * @param fileName 自定义在服务端保存的文件名 * @return 是否上传成功 * @throws IORuntimeException IO异常 */ - public boolean upload(String path, String fileName, File file) throws IORuntimeException { + public boolean upload(String destPath, String fileName, File file) throws IORuntimeException { try (InputStream in = FileUtil.getInputStream(file)) { - return upload(path, fileName, in); + return upload(destPath, fileName, in); } catch (IOException e) { throw new IORuntimeException(e); } @@ -517,13 +517,13 @@ public class Ftp extends AbstractFtp { * 3. path为绝对路径则上传到此路径 * * - * @param path 服务端路径,可以为{@code null} 或者相对路径或绝对路径 + * @param destPath 服务端路径,可以为{@code null} 或者相对路径或绝对路径 * @param fileName 文件名 * @param fileStream 文件流 * @return 是否上传成功 * @throws IORuntimeException IO异常 */ - public boolean upload(String path, String fileName, InputStream fileStream) throws IORuntimeException { + public boolean upload(String destPath, String fileName, InputStream fileStream) throws IORuntimeException { try { client.setFileType(FTPClient.BINARY_FILE_TYPE); } catch (IOException e) { @@ -535,10 +535,10 @@ public class Ftp extends AbstractFtp { pwd = pwd(); } - if (StrUtil.isNotBlank(path)) { - mkDirs(path); - if (false == isDir(path)) { - throw new FtpException("Change dir to [{}] error, maybe dir not exist!", path); + if (StrUtil.isNotBlank(destPath)) { + mkDirs(destPath); + if (false == isDir(destPath)) { + throw new FtpException("Change dir to [{}] error, maybe dir not exist!", destPath); } } diff --git a/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java b/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java index 6265b6ca0..1cba5c565 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java @@ -7,8 +7,6 @@ import cn.hutool.extra.ssh.Sftp; import org.junit.Ignore; import org.junit.Test; -import java.util.List; - public class FtpTest { @Test @@ -25,12 +23,9 @@ public class FtpTest { @Test @Ignore public void uploadTest() { - Ftp ftp = new Ftp("looly.centos"); + Ftp ftp = new Ftp("localhost"); - List ls = ftp.ls("/file"); - Console.log(ls); - - boolean upload = ftp.upload("/file/aaa", FileUtil.file("E:/qrcodeWithLogo.jpg")); + boolean upload = ftp.upload("/temp", FileUtil.file("d:/test/test.zip")); Console.log(upload); IoUtil.close(ftp); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 5778eff37..d8c8d1f83 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/ContentType.java b/hutool-http/src/main/java/cn/hutool/http/ContentType.java index d6d998267..29064428c 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ContentType.java +++ b/hutool-http/src/main/java/cn/hutool/http/ContentType.java @@ -45,6 +45,7 @@ public enum ContentType { /** * 构造 + * * @param value ContentType值 */ ContentType(String value) { @@ -141,4 +142,16 @@ public enum ContentType { public static String build(String contentType, Charset charset) { return StrUtil.format("{};charset={}", contentType, charset.name()); } + + /** + * 输出Content-Type字符串,附带编码信息 + * + * @param contentType Content-Type 枚举类型 + * @param charset 编码 + * @return Content-Type字符串 + * @since 5.7.15 + */ + public static String build(ContentType contentType, Charset charset) { + return build(contentType.getValue(), charset); + } } diff --git a/hutool-http/src/main/java/cn/hutool/http/useragent/Engine.java b/hutool-http/src/main/java/cn/hutool/http/useragent/Engine.java index ed6e57b2d..358a8e272 100644 --- a/hutool-http/src/main/java/cn/hutool/http/useragent/Engine.java +++ b/hutool-http/src/main/java/cn/hutool/http/useragent/Engine.java @@ -21,16 +21,16 @@ public class Engine extends UserAgentInfo { /** * 支持的引擎类型 */ - public static final List engines = CollUtil.newArrayList(// - new Engine("Trident", "trident"), // - new Engine("Webkit", "webkit"), // - new Engine("Chrome", "chrome"), // - new Engine("Opera", "opera"), // - new Engine("Presto", "presto"), // - new Engine("Gecko", "gecko"), // - new Engine("KHTML", "khtml"), // - new Engine("Konqeror", "konqueror"), // - new Engine("MIDP", "MIDP")// + public static final List engines = CollUtil.newArrayList( + new Engine("Trident", "trident"), + new Engine("Webkit", "webkit"), + new Engine("Chrome", "chrome"), + new Engine("Opera", "opera"), + new Engine("Presto", "presto"), + new Engine("Gecko", "gecko"), + new Engine("KHTML", "khtml"), + new Engine("Konqueror", "konqueror"), + new Engine("MIDP", "MIDP") ); private final Pattern versionPattern; diff --git a/hutool-http/src/test/java/cn/hutool/http/ContentTypeTest.java b/hutool-http/src/test/java/cn/hutool/http/ContentTypeTest.java new file mode 100644 index 000000000..1d03c44eb --- /dev/null +++ b/hutool-http/src/test/java/cn/hutool/http/ContentTypeTest.java @@ -0,0 +1,19 @@ +package cn.hutool.http; + +import cn.hutool.core.util.CharsetUtil; +import org.junit.Assert; +import org.junit.Test; + +/** + * ContentType 单元测试 + * + * + */ +public class ContentTypeTest { + + @Test + public void testBuild() { + String result = ContentType.build(ContentType.JSON, CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("application/json;charset=UTF-8", result); + } +} diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index ed5f98168..96e3feb61 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index fdfafabc7..3cdea5a83 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -3,6 +3,8 @@ package cn.hutool.json; import cn.hutool.core.bean.BeanPath; import cn.hutool.core.collection.ArrayIter; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Filter; +import cn.hutool.core.lang.Pair; import cn.hutool.core.text.StrJoiner; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -12,6 +14,7 @@ import cn.hutool.json.serialize.GlobalSerializeMapping; import cn.hutool.json.serialize.JSONSerializer; import cn.hutool.json.serialize.JSONWriter; +import java.io.StringWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Collection; @@ -529,11 +532,48 @@ public class JSONArray implements JSON, JSONGetter, List, Rando return this.toJSONString(0); } + /** + * 返回JSON字符串
+ * 支持过滤器,即选择哪些字段或值不写出 + * + * @param indentFactor 每层缩进空格数 + * @param filter 键值对过滤器 + * @return JSON字符串 + * @since 5.7.15 + */ + public String toJSONString(int indentFactor, Filter> filter){ + final StringWriter sw = new StringWriter(); + synchronized (sw.getBuffer()) { + return this.write(sw, indentFactor, 0, filter).toString(); + } + } + @Override public Writer write(Writer writer, int indentFactor, int indent) throws JSONException { + return write(writer, indentFactor, indent, null); + } + + /** + * 将JSON内容写入Writer
+ * 支持过滤器,即选择哪些字段或值不写出 + * + * @param writer writer + * @param indentFactor 缩进因子,定义每一级别增加的缩进量 + * @param indent 本级别缩进量 + * @param filter 过滤器 + * @return Writer + * @throws JSONException JSON相关异常 + * @since 5.7.15 + */ + public Writer write(Writer writer, int indentFactor, int indent, Filter> filter) throws JSONException { final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginArray(); - this.forEach(jsonWriter::writeValue); + + CollUtil.forEach(this, (value, index)->{ + if (null == filter || filter.accept(new Pair<>(index, value))) { + jsonWriter.writeValue(value); + } + }); jsonWriter.end(); // 此处不关闭Writer,考虑writer后续还需要填内容 return writer; diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index 841d043c3..7358cedec 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -6,6 +6,8 @@ import cn.hutool.core.bean.copier.BeanCopier; import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Filter; +import cn.hutool.core.lang.Pair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; import cn.hutool.core.map.MapUtil; @@ -18,6 +20,7 @@ import cn.hutool.json.serialize.JSONObjectSerializer; import cn.hutool.json.serialize.JSONSerializer; import cn.hutool.json.serialize.JSONWriter; +import java.io.StringWriter; import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; @@ -552,17 +555,54 @@ public class JSONObject implements JSON, JSONGetter, Map return this.toJSONString(0); } + /** + * 返回JSON字符串
+ * 支持过滤器,即选择哪些字段或值不写出 + * + * @param indentFactor 每层缩进空格数 + * @param filter 键值对过滤器 + * @return JSON字符串 + * @since 5.7.15 + */ + public String toJSONString(int indentFactor, Filter> filter){ + final StringWriter sw = new StringWriter(); + synchronized (sw.getBuffer()) { + return this.write(sw, indentFactor, 0, filter).toString(); + } + } + @Override public Writer write(Writer writer, int indentFactor, int indent) throws JSONException { + return write(writer, indentFactor, indent, null); + } + + /** + * 将JSON内容写入Writer
+ * 支持过滤器,即选择哪些字段或值不写出 + * + * @param writer writer + * @param indentFactor 缩进因子,定义每一级别增加的缩进量 + * @param indent 本级别缩进量 + * @param filter 过滤器 + * @return Writer + * @throws JSONException JSON相关异常 + * @since 5.7.15 + */ + public Writer write(Writer writer, int indentFactor, int indent, Filter> filter) throws JSONException { final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); - this.forEach(jsonWriter::writeField); + this.forEach((key, value) -> { + if (null == filter || filter.accept(new Pair<>(key, value))) { + jsonWriter.writeField(key, value); + } + }); jsonWriter.end(); - + // 此处不关闭Writer,考虑writer后续还需要填内容 return writer; } // ------------------------------------------------------------------------------------------------- Private method start + /** * Bean对象转Map * diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/JSONWriter.java b/hutool-json/src/main/java/cn/hutool/json/serialize/JSONWriter.java index f08548ee5..c57ef2488 100755 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/JSONWriter.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/JSONWriter.java @@ -62,13 +62,13 @@ public class JSONWriter extends Writer { private boolean arrayMode; /** - * 创建{@link JSONWriter} + * 创建JSONWriter * * @param writer {@link Writer} * @param indentFactor 缩进因子,定义每一级别增加的缩进量 * @param indent 本级别缩进量 * @param config JSON选项 - * @return {@link JSONWriter} + * @return JSONWriter */ public static JSONWriter of(Writer writer, int indentFactor, int indent, JSONConfig config) { return new JSONWriter(writer, indentFactor, indent, config); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java index c4f8c6b6a..b6e83e0d8 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java @@ -239,4 +239,36 @@ public class JSONArrayTest { private Integer id; private String name; } + + @Test + public void filterIncludeTest(){ + JSONArray json1 = JSONUtil.createArray() + .set("value1") + .set("value2") + .set("value3") + .set(true); + + final String s = json1.toJSONString(0, (pair) -> pair.getValue().equals("value2")); + Assert.assertEquals("[\"value2\"]", s); + } + + @Test + public void filterExcludeTest(){ + JSONArray json1 = JSONUtil.createArray() + .set("value1") + .set("value2") + .set("value3") + .set(true); + + final String s = json1.toJSONString(0, (pair) -> false == pair.getValue().equals("value2")); + Assert.assertEquals("[\"value1\",\"value3\",true]", s); + } + + @Test + public void putNullTest(){ + final JSONArray array = JSONUtil.createArray(JSONConfig.create().setIgnoreNullValue(false)); + array.set(null); + + Assert.assertEquals("[null]", array.toString()); + } } diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 03ce35541..f2983b859 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -609,4 +609,28 @@ public class JSONObjectTest { class BigDecimalBean{ private BigDecimal orderId; } + + @Test + public void filterIncludeTest(){ + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("a", "value1") + .set("b", "value2") + .set("c", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> pair.getKey().equals("b")); + Assert.assertEquals("{\"b\":\"value2\"}", s); + } + + @Test + public void filterExcludeTest(){ + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("a", "value1") + .set("b", "value2") + .set("c", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> false == pair.getKey().equals("b")); + Assert.assertEquals("{\"a\":\"value1\",\"c\":\"value3\",\"d\":true}", s); + } } diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index c3c8e1a66..d50aaa75f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-jwt diff --git a/hutool-jwt/src/test/java/cn/hutool/jwt/JWTValidatorTest.java b/hutool-jwt/src/test/java/cn/hutool/jwt/JWTValidatorTest.java index f2aa714e8..b3df4faff 100644 --- a/hutool-jwt/src/test/java/cn/hutool/jwt/JWTValidatorTest.java +++ b/hutool-jwt/src/test/java/cn/hutool/jwt/JWTValidatorTest.java @@ -69,4 +69,14 @@ public class JWTValidatorTest { boolean validate = JWT.of(token).setKey(key).validate(0); Assert.assertFalse(validate); } + + @Test(expected = ValidateException.class) + public void validateDateTest(){ + final JWT jwt = JWT.create() + .setPayload("id", 123) + .setPayload("username", "hutool") + .setExpiresAt(DateUtil.parse("2021-10-13 09:59:00")); + + JWTValidator.of(jwt).validateDate(DateUtil.date()); + } } diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 6f0a6fb6a..ba5ea9f7e 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 931bc7d42..e1cb315c7 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-poi @@ -45,7 +45,7 @@ org.ofdrw ofdrw-full - 1.15.5 + 1.16.0 compile true diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 80dfb4e4d..301d39309 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -281,11 +281,13 @@ public class ExcelReader extends ExcelBase { short columnSize; for (int y = startRowIndex; y <= endRowIndex; y++) { row = this.sheet.getRow(y); - columnSize = row.getLastCellNum(); - Cell cell; - for (short x = 0; x < columnSize; x++) { - cell = row.getCell(x); - cellHandler.handle(cell, CellUtil.getCellValue(cell)); + if(null != row){ + columnSize = row.getLastCellNum(); + Cell cell; + for (short x = 0; x < columnSize; x++) { + cell = row.getCell(x); + cellHandler.handle(cell, CellUtil.getCellValue(cell)); + } } } } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java index e6a11c2d8..02663d60b 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java @@ -7,7 +7,6 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; -import cn.hutool.core.lang.Console; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.IdUtil; @@ -765,7 +764,6 @@ public class ExcelWriter extends ExcelBase { if (null != content) { final Cell cell = getOrCreateCell(firstColumn, firstRow); CellUtil.setCellValue(cell, content, cellStyle); - Console.log("{} {} {}", firstColumn, firstRow, cell.getStringCellValue()); } return this; } diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/CellEditorTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/CellEditorTest.java new file mode 100644 index 000000000..9046324f4 --- /dev/null +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/CellEditorTest.java @@ -0,0 +1,46 @@ +package cn.hutool.poi.excel; + +import cn.hutool.poi.excel.cell.CellEditor; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.apache.poi.ss.usermodel.Cell; +import org.junit.Assert; + +import java.io.Serializable; +import java.util.List; + +public class CellEditorTest { + + @org.junit.Test + public void readTest(){ + ExcelReader excelReader= ExcelUtil.getReader("cell_editor_test.xlsx"); + excelReader.setCellEditor(new ExcelHandler()); + List excelReaderObjects=excelReader.readAll(Test.class); + + Assert.assertEquals("0", excelReaderObjects.get(0).getTest1()); + Assert.assertEquals("b", excelReaderObjects.get(0).getTest2()); + Assert.assertEquals("0", excelReaderObjects.get(1).getTest1()); + Assert.assertEquals("b1", excelReaderObjects.get(1).getTest2()); + Assert.assertEquals("0", excelReaderObjects.get(2).getTest1()); + Assert.assertEquals("c2", excelReaderObjects.get(2).getTest2()); + } + + @AllArgsConstructor + @Data + public static class Test implements Serializable { + private static final long serialVersionUID = 1L; + + private String test1; + private String test2; + } + + public static class ExcelHandler implements CellEditor { + @ Override + public Object edit(Cell cell, Object o) { + if (cell.getColumnIndex()==0 && cell.getRowIndex() != 0){ + o="0"; + } + return o; + } + } +} diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelReadTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelReadTest.java index 4326a8608..ebe04bf0f 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelReadTest.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelReadTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.lang.Console; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.poi.excel.cell.CellHandler; import lombok.Data; import org.junit.Assert; import org.junit.Ignore; @@ -225,4 +226,11 @@ public class ExcelReadTest { final List> maps = reader.readAll(); Console.log(maps); } + + @Test + @Ignore + public void readNullRowTest(){ + final ExcelReader reader = ExcelUtil.getReader("d:/test/1.-.xls"); + reader.read((CellHandler) Console::log); + } } diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelWriteTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelWriteTest.java index ad5c3c5fc..d33fc890b 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelWriteTest.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelWriteTest.java @@ -783,4 +783,15 @@ public class ExcelWriteTest { writer.write(ListUtil.of(hyperlink)); writer.close(); } + + @Test + @Ignore + public void mergeNumberTest(){ + File tempFile=new File("d:/test/mergeNumber.xlsx"); + FileUtil.del(tempFile); + + BigExcelWriter writer= new BigExcelWriter(tempFile); + writer.merge(0,1,2,2,3.99,false); + writer.close(); + } } diff --git a/hutool-poi/src/test/resources/cell_editor_test.xlsx b/hutool-poi/src/test/resources/cell_editor_test.xlsx new file mode 100644 index 000000000..af1e0326e Binary files /dev/null and b/hutool-poi/src/test/resources/cell_editor_test.xlsx differ diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index d76f4aa4c..56c4429c6 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index a79515e51..81491727d 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index dce985061..afc15da80 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 9d9645c60..f89f373b9 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool-system diff --git a/pom.xml b/pom.xml index e73b78ebf..7ef421831 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.14 + 5.7.15 hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool