diff --git a/CHANGELOG.md b/CHANGELOG.md index f0c56fb08..1608dfbca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,64 @@ ------------------------------------------------------------------------------------------------------------- +# 5.4.4 (2020-09-23) + +### 新特性 +* 【core 】 ServiceLoaderUtil改为使用contextClassLoader(pr#183@Gitee) +* 【core 】 NetUtil增加getLocalHostName(pr#1103@Github) +* 【extra 】 FTP增加stat方法(issue#I1W346@Gitee) +* 【core 】 Convert.toNumber支持类似12.2F这种形式字符串转换(issue#I1VYLJ@Gitee) +* 【core 】 使用静态变量替换999等(issue#I1W8IB@Gitee) +* 【core 】 URLUtil自动trim(issue#I1W803@Gitee) +* 【crypto 】 RC4增加ecrypt(pr#1108@Github) +* 【core 】 CharUtil and StrUtil增加@(pr#1106@Github) +* 【extra 】 优化EMOJ查询逻辑(pr#1112@Github) +* 【extra 】 优化CollUtil交并集结果集合设置初始化大小,避免扩容成本(pr#1110@Github) +* 【core 】 优化PageUtil彩虹算法(issue#1110@Github) +* 【core 】 IoUtil增加readUtf8方法 +* 【core 】 优化全局邮箱账户初始化逻辑(pr#1114@Github) + +### Bug修复 +* 【crypto 】 修复SM2验签后无法解密问题(issue#I1W0VP@Gitee) +* 【core 】 修复新建默认TreeSet没有默认比较器导致的问题(issue#1101@Github) +* 【core 】 修复Linux下使用Windows路径分隔符导致的解压错误(issue#I1MW0E@Gitee) +* 【core 】 修复Word07Writer写出map问题(issue#I1W49R@Gitee) + +------------------------------------------------------------------------------------------------------------- + +# 5.4.3 (2020-09-16) + +### 新特性 +* 【core 】 使用静态的of方法来new对象(pr#177@Gitee) +* 【setting】 Setting增加store无参方法(issue#1072@Github) +* 【setting】 StatementUtil增加null缓存(pr#1076@Github) +* 【core 】 扩充Console功能,支持可变参数(issue#1077@Github) +* 【crypto 】 增加ECKeyUtil(issue#I1UOF5@Gitee) +* 【core 】 增加TransXXX(issue#I1TU1Y@Gitee) +* 【core 】 增加Generator +* 【db 】 Column增加是否主键、保留位数等字段 +* 【cache 】 Cache接口增加get重载(issue#1080@Github) +* 【core 】 增加Interner和InternUtil(issue#I1TU1Y@Gitee) +* 【core 】 增加Calculator(issue#1090@Github) +* 【core 】 IdcardUtil增加getIdcardInfo方法(issue#1092@Github) +* 【core 】 改进ObjectUtil.equal,支持BigDecimal判断 +* 【core 】 ArrayConverter增加可选是否忽略错误(issue#I1VNYQ@Gitee) +* 【db 】 增加ConditionBuilder +* 【setting】 Setting和Props增加create方法 +* 【log 】 增加TinyLog2支持(issue#1094@Github) + +### Bug修复 +* 【core 】 修复Dict.of错误(issue#I1UUO5@Gitee) +* 【core 】 修复UrlBuilder地址参数问题(issue#I1UWCA@Gitee) +* 【core 】 修复StrUtil.toSymbolCase转换问题(issue#1075@Github) +* 【log 】 修复打印null对象显示{msg}异常问题(issue#1084@Github) +* 【extra 】 修复ServletUtil.getReader中未关闭的问题 +* 【extra 】 修复QrCodeUtil在新版本zxing报错问题(issue#1088@Github) +* 【core 】 修复LocalDateTimeUtil.parse无法解析yyyyMMddHHmmssSSS的bug(issue#1082@Github) +* 【core 】 修复VersionComparator.equals递归调用问题(issue#1093@Github) + +------------------------------------------------------------------------------------------------------------- + # 5.4.2 (2020-09-09) ### 新特性 diff --git a/README-EN.md b/README-EN.md index a52d4dbc3..c3ce780e2 100644 --- a/README-EN.md +++ b/README-EN.md @@ -43,8 +43,7 @@

- - +

------------------------------------------------------------------------------- @@ -121,19 +120,19 @@ Each module can be introduced individually, or all modules can be introduced by cn.hutool hutool-all - 5.4.2 + 5.4.4 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:5.4.2' +compile 'cn.hutool:hutool-all:5.4.4' ``` ## Download -- [Maven1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.2/) -- [Maven2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.4.2/) +- [Maven1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.4/) +- [Maven2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.4.4/) > 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. diff --git a/README.md b/README.md index cb75a9441..949c6a321 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,6 @@

-

@@ -120,21 +119,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.4.2 + 5.4.4 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:5.4.2' +compile 'cn.hutool:hutool-all:5.4.4' ``` ### 非Maven项目 点击以下任一链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.2/) -- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.4.2/) +- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.4/) +- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.4.4/) > 注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index 8ae03c119..426c1c179 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.4.2 +5.4.4 diff --git a/docs/js/version.js b/docs/js/version.js index 857a3c16d..876d3bc42 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.4.2' \ No newline at end of file +var version = '5.4.4' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 872ea1f64..9a7e395bd 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index c266dc889..204c892b4 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-aop @@ -19,7 +19,7 @@ 3.3.0 - 5.2.7.RELEASE + 5.2.9.RELEASE diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index c5e466c07..7f219864b 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 98a42f3de..47f6c2d47 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 4d5c05eff..9fbc93e04 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-cache diff --git a/hutool-cache/src/main/java/cn/hutool/cache/Cache.java b/hutool-cache/src/main/java/cn/hutool/cache/Cache.java index 52bc97a83..1478b192e 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/Cache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/Cache.java @@ -1,39 +1,38 @@ package cn.hutool.cache; -import java.io.Serializable; -import java.util.Iterator; - import cn.hutool.cache.impl.CacheObj; import cn.hutool.core.lang.func.Func0; +import java.io.Serializable; +import java.util.Iterator; + /** * 缓存接口 - * - * @author Looly,jodd * * @param 键类型 * @param 值类型 + * @author Looly, jodd */ public interface Cache extends Iterable, Serializable { /** * 返回缓存容量,0表示无大小限制 - * + * * @return 返回缓存容量,0表示无大小限制 */ int capacity(); /** * 缓存失效时长, 0 表示没有设置,单位毫秒 - * + * * @return 缓存失效时长, 0 表示没有设置,单位毫秒 */ long timeout(); /** * 将对象加入到缓存,使用默认失效时长 - * - * @param key 键 + * + * @param key 键 * @param object 缓存的对象 * @see Cache#put(Object, Object, long) */ @@ -42,9 +41,9 @@ public interface Cache extends Iterable, Serializable { /** * 将对象加入到缓存,使用指定失效时长
* 如果缓存空间满了,{@link #prune()} 将被调用以获得空间来存放新对象 - * - * @param key 键 - * @param object 缓存的对象 + * + * @param key 键 + * @param object 缓存的对象 * @param timeout 失效时长,单位毫秒 * @see Cache#put(Object, Object, long) */ @@ -56,28 +55,50 @@ public interface Cache extends Iterable, Serializable { * 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回null,否则返回值。 *

* 每次调用此方法会刷新最后访问时间,也就是说会重新计算超时时间。 - * + * * @param key 键 * @return 键对应的对象 * @see #get(Object, boolean) */ - V get(K key); - + default V get(K key) { + return get(key, true); + } + /** * 从缓存中获得对象,当对象不在缓存中或已经过期返回Func0回调产生的对象 - * - * @param key 键 + *

+ * 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回null,否则返回值。 + *

+ * 每次调用此方法会刷新最后访问时间,也就是说会重新计算超时时间。 + * + * @param key 键 * @param supplier 如果不存在回调方法,用于生产值对象 * @return 值对象 */ - V get(K key, Func0 supplier); + default V get(K key, Func0 supplier) { + return get(key, true, supplier); + } + + /** + * 从缓存中获得对象,当对象不在缓存中或已经过期返回Func0回调产生的对象 + *

+ * 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回null,否则返回值。 + *

+ * 每次调用此方法会刷新最后访问时间,也就是说会重新计算超时时间。 + * + * @param key 键 + * @param isUpdateLastAccess 是否更新最后访问时间,即重新计算超时时间。 + * @param supplier 如果不存在回调方法,用于生产值对象 + * @return 值对象 + */ + V get(K key, boolean isUpdateLastAccess, Func0 supplier); /** * 从缓存中获得对象,当对象不在缓存中或已经过期返回null *

* 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回null,否则返回值。 - * - * @param key 键 + * + * @param key 键 * @param isUpdateLastAccess 是否更新最后访问时间,即重新计算超时时间。 * @return 键对应的对象 */ @@ -85,7 +106,7 @@ public interface Cache extends Iterable, Serializable { /** * 返回包含键和值得迭代器 - * + * * @return 缓存对象迭代器 * @since 4.0.10 */ @@ -93,21 +114,21 @@ public interface Cache extends Iterable, Serializable { /** * 从缓存中清理过期对象,清理策略取决于具体实现 - * + * * @return 清理的缓存对象个数 */ int prune(); /** * 缓存是否已满,仅用于有空间限制的缓存对象 - * + * * @return 缓存是否已满,仅用于有空间限制的缓存对象 */ boolean isFull(); /** * 从缓存中移除对象 - * + * * @param key 键 */ void remove(K key); @@ -119,21 +140,21 @@ public interface Cache extends Iterable, Serializable { /** * 缓存的对象数量 - * + * * @return 缓存的对象数量 */ int size(); /** * 缓存是否为空 - * + * * @return 缓存是否为空 */ boolean isEmpty(); /** * 是否包含key - * + * * @param key KEY * @return 是否包含key */ 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 464ab44b7..f46009980 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 @@ -125,13 +125,8 @@ public abstract class AbstractCache implements Cache { } @Override - public V get(K key) { - return get(key, true); - } - - @Override - public V get(K key, Func0 supplier) { - V v = get(key); + public V get(K key, boolean isUpdateLastAccess, Func0 supplier) { + V v = get(key, isUpdateLastAccess); if (null == v && null != supplier) { final long stamp = lock.writeLock(); try { diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/NoCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/NoCache.java index 55d6ec896..7814d9679 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/NoCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/NoCache.java @@ -52,6 +52,11 @@ public class NoCache implements Cache { @Override public V get(K key, Func0 supplier) { + return get(key, true, supplier); + } + + @Override + public V get(K key, boolean isUpdateLastAccess, Func0 supplier) { try { return (null == supplier) ? null : supplier.call(); } catch (Exception e) { diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 9380a0242..36af6c3e6 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-captcha diff --git a/hutool-captcha/src/main/java/cn/hutool/captcha/generator/MathGenerator.java b/hutool-captcha/src/main/java/cn/hutool/captcha/generator/MathGenerator.java index 11820e6fd..1482c9f60 100644 --- a/hutool-captcha/src/main/java/cn/hutool/captcha/generator/MathGenerator.java +++ b/hutool-captcha/src/main/java/cn/hutool/captcha/generator/MathGenerator.java @@ -1,5 +1,6 @@ package cn.hutool.captcha.generator; +import cn.hutool.core.math.Calculator; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; @@ -59,20 +60,8 @@ public class MathGenerator implements CodeGenerator { return false; } - final int a = Integer.parseInt(StrUtil.sub(code, 0, this.numberLength).trim()); - final char operator = code.charAt(this.numberLength); - final int b = Integer.parseInt(StrUtil.sub(code, this.numberLength + 1, this.numberLength + 1 + this.numberLength).trim()); - - switch (operator) { - case '+': - return (a + b) == result; - case '-': - return (a - b) == result; - case '*': - return (a * b) == result; - default: - return false; - } + final int calculateResult = (int) Calculator.conversion(code); + return result == calculateResult; } /** diff --git a/hutool-captcha/src/test/java/cn/hutool/captcha/GeneratorTest.java b/hutool-captcha/src/test/java/cn/hutool/captcha/GeneratorTest.java new file mode 100644 index 000000000..383ae48f1 --- /dev/null +++ b/hutool-captcha/src/test/java/cn/hutool/captcha/GeneratorTest.java @@ -0,0 +1,14 @@ +package cn.hutool.captcha; + +import cn.hutool.captcha.generator.MathGenerator; +import org.junit.Test; + +public class GeneratorTest { + @Test + public void mathGeneratorTest(){ + final MathGenerator mathGenerator = new MathGenerator(); + for (int i = 0; i < 1000; i++) { + mathGenerator.verify(mathGenerator.generate(), "0"); + } + } +} diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 532e627c7..c4d57ae21 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-core 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 d9c5c1f7a..4708a22e6 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 @@ -109,6 +109,7 @@ public class BeanCopier implements Copier, Serializable { } } } + return this.dest; } 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 48915ed4d..52e1d8151 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 @@ -1,6 +1,7 @@ package cn.hutool.core.collection; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.comparator.CompareUtil; import cn.hutool.core.comparator.PinyinComparator; import cn.hutool.core.comparator.PropertyComparator; import cn.hutool.core.convert.Convert; @@ -103,22 +104,22 @@ public class CollUtil { * @return 并集的集合,返回 {@link ArrayList} */ public static Collection union(Collection coll1, Collection coll2) { - final ArrayList list = new ArrayList<>(); if (isEmpty(coll1)) { - list.addAll(coll2); + return new ArrayList<>(coll2); } else if (isEmpty(coll2)) { - list.addAll(coll1); - } else { - final Map map1 = countMap(coll1); - final Map map2 = countMap(coll2); - final Set elts = newHashSet(coll2); - elts.addAll(coll1); - int m; - for (T t : elts) { - m = Math.max(Convert.toInt(map1.get(t), 0), Convert.toInt(map2.get(t), 0)); - for (int i = 0; i < m; i++) { - list.add(t); - } + return new ArrayList<>(coll1); + } + + final ArrayList list = new ArrayList<>(Math.max(coll1.size(), coll2.size())); + final Map map1 = countMap(coll1); + final Map map2 = countMap(coll2); + final Set elts = newHashSet(coll2); + elts.addAll(coll1); + int m; + for (T t : elts) { + m = Math.max(Convert.toInt(map1.get(t), 0), Convert.toInt(map2.get(t), 0)); + for (int i = 0; i < m; i++) { + list.add(t); } } return list; @@ -225,8 +226,8 @@ public class CollUtil { * @return 交集的集合,返回 {@link ArrayList} */ public static Collection intersection(Collection coll1, Collection coll2) { - final ArrayList list = new ArrayList<>(); if (isNotEmpty(coll1) && isNotEmpty(coll2)) { + final ArrayList list = new ArrayList<>(Math.min(coll1.size(), coll2.size())); final Map map1 = countMap(coll1); final Map map2 = countMap(coll2); final Set elts = newHashSet(coll2); @@ -237,8 +238,10 @@ public class CollUtil { list.add(t); } } + return list; } - return list; + + return new ArrayList<>(); } /** @@ -414,9 +417,9 @@ public class CollUtil { /** * 自定义函数判断集合是否包含某类值 * - * @param collection 集合 + * @param collection 集合 * @param containFunc 自定义判断函数 - * @param 值类型 + * @param 值类型 * @return 是否包含自定义规则的值 */ public static boolean contains(Collection collection, Predicate containFunc) { @@ -992,8 +995,13 @@ public class CollUtil { } else if (collectionType.isAssignableFrom(LinkedHashSet.class)) { list = new LinkedHashSet<>(); } else if (collectionType.isAssignableFrom(TreeSet.class)) { - //noinspection SortedCollectionWithNonComparableKeys - list = new TreeSet<>(); + list = new TreeSet<>((o1, o2) -> { + // 优先按照对象本身比较,如果没有实现比较接口,默认按照toString内容比较 + if (o1 instanceof Comparable) { + return ((Comparable) o1).compareTo(o2); + } + return CompareUtil.compare(o1.toString(), o2.toString()); + }); } else if (collectionType.isAssignableFrom(EnumSet.class)) { list = (Collection) EnumSet.noneOf((Class) ClassUtil.getTypeArgument(collectionType)); } @@ -2837,6 +2845,20 @@ public class CollUtil { } } + /** + * 使用给定的转换函数,转换源集合为新类型的集合 + * + * @param 源元素类型 + * @param 目标元素类型 + * @param collection 集合 + * @param function 转换函数 + * @return 新类型的集合 + * @since 5.4.3 + */ + public static Collection trans(Collection collection, Function function) { + return new TransCollection<>(collection, function); + } + // ---------------------------------------------------------------------------------------------- Interface start /** 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 53b15b249..3a5881dd0 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 @@ -807,4 +807,18 @@ public class IterUtil { public static Iterator empty() { return Collections.emptyIterator(); } + + /** + * 按照给定函数,转换{@link Iterator}为另一种类型的{@link Iterator} + * + * @param 源元素类型 + * @param 目标元素类型 + * @param iterator 源{@link Iterator} + * @param function 转换函数 + * @return 转换后的{@link Iterator} + * @since 5.4.3 + */ + public static Iterator trans(Iterator iterator, Function function) { + return new TransIter<>(iterator, function); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java index a02638d7c..fef0bfac6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java @@ -149,6 +149,23 @@ public class ListUtil { return (LinkedList) list(true, values); } + /** + * 数组转为一个不可变List
+ * 类似于Java9中的List.of + * + * @param ts 对象 + * @param 对象类型 + * @return 不可修改List + * @since 5.4.3 + */ + @SafeVarargs + public static List of(T... ts) { + if (ArrayUtil.isEmpty(ts)) { + return Collections.emptyList(); + } + return Collections.unmodifiableList(toList(ts)); + } + /** * 新建一个CopyOnWriteArrayList * @@ -236,7 +253,7 @@ public class ListUtil { } } - if((pageNo * pageSize) > resultSize){ + if ((pageNo * pageSize) > resultSize) { // 越界直接返回空 return new ArrayList<>(0); } @@ -461,6 +478,9 @@ public class ListUtil { * @since 5.2.6 */ public static List unmodifiable(List list) { + if(null == list){ + return null; + } return Collections.unmodifiableList(list); } diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/SpliteratorUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/SpliteratorUtil.java new file mode 100644 index 000000000..e2055ff0c --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/collection/SpliteratorUtil.java @@ -0,0 +1,26 @@ +package cn.hutool.core.collection; + +import java.util.Spliterator; +import java.util.function.Function; + +/** + * {@link Spliterator}相关工具类 + * + * @author looly + * @since 5.4.3 + */ +public class SpliteratorUtil { + + /** + * 使用给定的转换函数,转换源{@link Spliterator}为新类型的{@link Spliterator} + * + * @param 源元素类型 + * @param 目标元素类型 + * @param fromSpliterator 源{@link Spliterator} + * @param function 转换函数 + * @return 新类型的{@link Spliterator} + */ + public static Spliterator trans(Spliterator fromSpliterator, Function function) { + return new TransSpliterator<>(fromSpliterator, function); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/TransCollection.java b/hutool-core/src/main/java/cn/hutool/core/collection/TransCollection.java new file mode 100644 index 000000000..84c08fadb --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/collection/TransCollection.java @@ -0,0 +1,73 @@ +package cn.hutool.core.collection; + +import cn.hutool.core.lang.Assert; + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Iterator; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * 使用给定的转换函数,转换源集合为新类型的集合 + * + * @param 源元素类型 + * @param 目标元素类型 + * @author looly + * @since 5.4.3 + */ +public class TransCollection extends AbstractCollection { + + private final Collection fromCollection; + private final Function function; + + /** + * 构造 + * + * @param fromCollection 源集合 + * @param function 转换函数 + */ + public TransCollection(Collection fromCollection, Function function) { + this.fromCollection = Assert.notNull(fromCollection); + this.function = Assert.notNull(function); + } + + @Override + public Iterator iterator() { + return IterUtil.trans(fromCollection.iterator(), function); + } + + @Override + public void clear() { + fromCollection.clear(); + } + + @Override + public boolean isEmpty() { + return fromCollection.isEmpty(); + } + + @Override + public void forEach(Consumer action) { + Assert.notNull(action); + fromCollection.forEach((f) -> action.accept(function.apply(f))); + } + + @Override + public boolean removeIf(Predicate filter) { + Assert.notNull(filter); + return fromCollection.removeIf(element -> filter.test(function.apply(element))); + } + + @Override + public Spliterator spliterator() { + return SpliteratorUtil.trans(fromCollection.spliterator(), function); + } + + @Override + public int size() { + return fromCollection.size(); + } +} \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/TransIter.java b/hutool-core/src/main/java/cn/hutool/core/collection/TransIter.java new file mode 100644 index 000000000..4c0c8dd59 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/collection/TransIter.java @@ -0,0 +1,46 @@ +package cn.hutool.core.collection; + +import cn.hutool.core.lang.Assert; + +import java.util.Iterator; +import java.util.function.Function; + +/** + * 使用给定的转换函数,转换源{@link Iterator}为新类型的{@link Iterator} + * + * @param 源元素类型 + * @param 目标元素类型 + * @author looly + * @since 5.4.3 + */ +public class TransIter implements Iterator { + + private final Iterator backingIterator; + private final Function func; + + /** + * 构造 + * + * @param backingIterator 源{@link Iterator} + * @param func 转换函数 + */ + public TransIter(Iterator backingIterator, Function func) { + this.backingIterator = Assert.notNull(backingIterator); + this.func = Assert.notNull(func); + } + + @Override + public final boolean hasNext() { + return backingIterator.hasNext(); + } + + @Override + public final T next() { + return func.apply(backingIterator.next()); + } + + @Override + public final void remove() { + backingIterator.remove(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/TransSpliterator.java b/hutool-core/src/main/java/cn/hutool/core/collection/TransSpliterator.java new file mode 100644 index 000000000..78b9b8a19 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/collection/TransSpliterator.java @@ -0,0 +1,51 @@ +package cn.hutool.core.collection; + +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * 使用给定的转换函数,转换源{@link Spliterator}为新类型的{@link Spliterator} + * + * @param 源元素类型 + * @param 目标元素类型 + * @author looly + * @since 5.4.3 + */ +public class TransSpliterator implements Spliterator { + private final Spliterator fromSpliterator; + private final Function function; + + public TransSpliterator(Spliterator fromSpliterator, Function function) { + this.fromSpliterator = fromSpliterator; + this.function = function; + } + + @Override + public boolean tryAdvance(Consumer action) { + return fromSpliterator.tryAdvance( + fromElement -> action.accept(function.apply(fromElement))); + } + + @Override + public void forEachRemaining(Consumer action) { + fromSpliterator.forEachRemaining(fromElement -> action.accept(function.apply(fromElement))); + } + + @Override + public Spliterator trySplit() { + Spliterator fromSplit = fromSpliterator.trySplit(); + return (fromSplit != null) ? new TransSpliterator<>(fromSplit, function) : null; + } + + @Override + public long estimateSize() { + return fromSpliterator.estimateSize(); + } + + @Override + public int characteristics() { + return fromSpliterator.characteristics() + & ~(Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.SORTED); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/ComparatorChain.java b/hutool-core/src/main/java/cn/hutool/core/comparator/ComparatorChain.java index 44fee28f0..bf8af18a5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/comparator/ComparatorChain.java +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/ComparatorChain.java @@ -4,6 +4,7 @@ import cn.hutool.core.lang.Chain; import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.BitSet; import java.util.Comparator; import java.util.Iterator; @@ -14,20 +15,92 @@ import java.util.Objects; * 比较器链。此链包装了多个比较器,最终比较结果按照比较器顺序综合多个比较器结果。
* 按照比较器链的顺序分别比较,如果比较出相等则转向下一个比较器,否则直接返回
* 此类copy from Apache-commons-collections - * + * * @author looly * @since 3.0.7 */ public class ComparatorChain implements Chain, ComparatorChain>, Comparator, Serializable { private static final long serialVersionUID = -2426725788913962429L; - - /** 比较器链. */ + + /** + * 比较器链. + */ private final List> chain; - /** 对应比较器位置是否反序. */ + /** + * 对应比较器位置是否反序. + */ private final BitSet orderingBits; - /** 比较器是否被锁定。锁定的比较器链不能再添加新的比较器。比较器会在开始比较时开始加锁。 */ + /** + * 比较器是否被锁定。锁定的比较器链不能再添加新的比较器。比较器会在开始比较时开始加锁。 + */ private boolean lock = false; + //------------------------------------------------------------------------------------- Static method start + + /** + * 构建 {@link ComparatorChain} + * + * @param 被比较对象类型 + * @param comparator 比较器 + * @return {@link ComparatorChain} + * @since 5.4.3 + */ + public static ComparatorChain of(Comparator comparator) { + return of(comparator, false); + } + + /** + * 构建 {@link ComparatorChain} + * + * @param 被比较对象类型 + * @param comparator 比较器 + * @param reverse 是否反向 + * @return {@link ComparatorChain} + * @since 5.4.3 + */ + public static ComparatorChain of(Comparator comparator, boolean reverse) { + return new ComparatorChain<>(comparator, reverse); + } + + /** + * 构建 {@link ComparatorChain} + * + * @param 被比较对象类型 + * @param comparators 比较器数组 + * @return {@link ComparatorChain} + * @since 5.4.3 + */ + @SafeVarargs + public static ComparatorChain of(Comparator... comparators) { + return of(Arrays.asList(comparators)); + } + + /** + * 构建 {@link ComparatorChain} + * + * @param 被比较对象类型 + * @param comparators 比较器列表 + * @return {@link ComparatorChain} + * @since 5.4.3 + */ + public static ComparatorChain of(List> comparators) { + return new ComparatorChain<>(comparators); + } + + /** + * 构建 {@link ComparatorChain} + * + * @param 被比较对象类型 + * @param comparators 比较器列表 + * @param bits {@link Comparator} 列表对应的排序boolean值,true表示正序,false反序 + * @return {@link ComparatorChain} + * @since 5.4.3 + */ + public static ComparatorChain of(List> comparators, BitSet bits) { + return new ComparatorChain<>(comparators, bits); + } + //------------------------------------------------------------------------------------- Static method start + /** * 构造空的比较器链,必须至少有一个比较器,否则会在compare时抛出{@link UnsupportedOperationException} */ @@ -36,7 +109,7 @@ public class ComparatorChain implements Chain, ComparatorChain< } /** - *构造,初始化单一比较器。比较器为正序 + * 构造,初始化单一比较器。比较器为正序 * * @param comparator 在比较器链中的第一个比较器 */ @@ -48,7 +121,7 @@ public class ComparatorChain implements Chain, ComparatorChain< * 构造,初始化单一比较器。自定义正序还是反序 * * @param comparator 在比较器链中的第一个比较器 - * @param reverse 是否反序,true表示反序,false正序 + * @param reverse 是否反序,true表示反序,false正序 */ public ComparatorChain(final Comparator comparator, final boolean reverse) { chain = new ArrayList<>(1); @@ -61,9 +134,9 @@ public class ComparatorChain implements Chain, ComparatorChain< /** * 构造,使用已有的比较器列表 - * + * * @param list 比较器列表 - * @see #ComparatorChain(List,BitSet) + * @see #ComparatorChain(List, BitSet) */ public ComparatorChain(final List> list) { this(list, new BitSet(list.size())); @@ -81,7 +154,6 @@ public class ComparatorChain implements Chain, ComparatorChain< orderingBits = bits; } - // ----------------------------------------------------------------------- /** * 在链的尾部添加比较器,使用正向排序 * @@ -96,7 +168,7 @@ public class ComparatorChain implements Chain, ComparatorChain< * 在链的尾部添加比较器,使用给定排序方式 * * @param comparator {@link Comparator} 比较器 - * @param reverse 是否反序,true表示正序,false反序 + * @param reverse 是否反序,true表示正序,false反序 * @return this */ public ComparatorChain addComparator(final Comparator comparator, final boolean reverse) { @@ -112,10 +184,10 @@ public class ComparatorChain implements Chain, ComparatorChain< /** * 替换指定位置的比较器,保持原排序方式 * - * @param index 位置 + * @param index 位置 * @param comparator {@link Comparator} * @return this - * @exception IndexOutOfBoundsException if index < 0 or index >= size() + * @throws IndexOutOfBoundsException if index < 0 or index >= size() */ public ComparatorChain setComparator(final int index, final Comparator comparator) throws IndexOutOfBoundsException { return setComparator(index, comparator, false); @@ -124,9 +196,9 @@ public class ComparatorChain implements Chain, ComparatorChain< /** * 替换指定位置的比较器,替换指定排序方式 * - * @param index 位置 + * @param index 位置 * @param comparator {@link Comparator} - * @param reverse 是否反序,true表示正序,false反序 + * @param reverse 是否反序,true表示正序,false反序 * @return this */ public ComparatorChain setComparator(final int index, final Comparator comparator, final boolean reverse) { @@ -176,12 +248,13 @@ public class ComparatorChain implements Chain, ComparatorChain< /** * 是否已经被锁定。当开始比较时(调用compare方法)此值为true + * * @return true = ComparatorChain cannot be modified; false = ComparatorChain can still be modified. */ public boolean isLocked() { return lock; } - + @Override public Iterator> iterator() { return this.chain.iterator(); @@ -191,7 +264,7 @@ public class ComparatorChain implements Chain, ComparatorChain< public ComparatorChain addChain(Comparator element) { return this.addComparator(element); } - + /** * 执行比较
* 按照比较器链的顺序分别比较,如果比较出相等则转向下一个比较器,否则直接返回 @@ -207,7 +280,7 @@ public class ComparatorChain implements Chain, ComparatorChain< checkChainIntegrity(); lock = true; } - + final Iterator> comparators = chain.iterator(); Comparator comparator; int retval; @@ -257,6 +330,7 @@ public class ComparatorChain implements Chain, ComparatorChain< } //------------------------------------------------------------------------------------------------------------------------------- Private method start + /** * 被锁定时抛出异常 * @@ -270,7 +344,7 @@ public class ComparatorChain implements Chain, ComparatorChain< /** * 检查比较器链是否为空,为空抛出异常 - * + * * @throws UnsupportedOperationException 为空抛出此异常 */ private void checkChainIntegrity() { diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java b/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java index c592e7a30..a8551a5ef 100644 --- a/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java @@ -1,6 +1,8 @@ package cn.hutool.core.comparator; import java.util.Comparator; +import java.util.Objects; +import java.util.function.Function; /** * 比较工具类 @@ -108,4 +110,34 @@ public class CompareUtil { return result; } + + /** + * 中文比较器 + * + * @param keyExtractor 从对象中提取中文(参与比较的内容) + * @param 对象类型 + * @return 中文比较器 + * @since 5.4.3 + */ + public static Comparator comparingPinyin(Function keyExtractor) { + return comparingPinyin(keyExtractor, false); + } + + /** + * 中文比较器 + * + * @param keyExtractor 从对象中提取中文(参与比较的内容) + * @param reverse 是否反序 + * @param 对象类型 + * @return 中文比较器 + * @since 5.4.3 + */ + public static Comparator comparingPinyin(Function keyExtractor, boolean reverse) { + Objects.requireNonNull(keyExtractor); + PinyinComparator pinyinComparator = new PinyinComparator(); + if (reverse) { + return (o1, o2) -> pinyinComparator.compare(keyExtractor.apply(o2), keyExtractor.apply(o1)); + } + return (o1, o2) -> pinyinComparator.compare(keyExtractor.apply(o1), keyExtractor.apply(o2)); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/VersionComparator.java b/hutool-core/src/main/java/cn/hutool/core/comparator/VersionComparator.java index 36355749e..6c741bf46 100644 --- a/hutool-core/src/main/java/cn/hutool/core/comparator/VersionComparator.java +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/VersionComparator.java @@ -1,13 +1,13 @@ package cn.hutool.core.comparator; -import java.io.Serializable; -import java.util.Comparator; -import java.util.List; - import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import java.io.Serializable; +import java.util.Comparator; +import java.util.List; + /** * 版本比较器
* 比较两个版本的大小
@@ -85,19 +85,4 @@ public class VersionComparator implements Comparator, Serializable { // 如果已经分出大小,则直接返回,如果未分出大小,则再比较位数,有子版本的为大; return (diff != 0) ? diff : v1s.size() - v2s.size(); } - - @Override - public boolean equals(final Object object) { - if (this == object) { - return true; - } - if (null == object) { - return false; - } - if (object.getClass().equals(this.getClass())) { - final VersionComparator other = (VersionComparator) object; - return this.equals(other); - } - return false; - } } diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java b/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java index c84c5609f..3fa034c86 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java @@ -106,9 +106,9 @@ public class ConverterRegistry implements Serializable { } /** - * 获得单例的 {@link ConverterRegistry} + * 获得单例的 ConverterRegistry * - * @return {@link ConverterRegistry} + * @return ConverterRegistry */ public static ConverterRegistry getInstance() { return SingletonHolder.INSTANCE; @@ -140,7 +140,7 @@ public class ConverterRegistry implements Serializable { * * @param type 转换的目标类型 * @param converterClass 转换器类,必须有默认构造方法 - * @return {@link ConverterRegistry} + * @return ConverterRegistry */ public ConverterRegistry putCustom(Type type, Class> converterClass) { return putCustom(type, ReflectUtil.newInstance(converterClass)); @@ -151,7 +151,7 @@ public class ConverterRegistry implements Serializable { * * @param type 转换的目标类型 * @param converter 转换器 - * @return {@link ConverterRegistry} + * @return ConverterRegistry */ public ConverterRegistry putCustom(Type type, Converter converter) { if (null == customConverterMap) { @@ -257,6 +257,7 @@ public class ConverterRegistry implements Serializable { } } + // 特殊类型转换,包括Collection、Map、强转、Array等 final T result = convertSpecial(type, rowType, value, defaultValue); if (null != result) { @@ -269,7 +270,7 @@ public class ConverterRegistry implements Serializable { } // 无法转换 - throw new ConvertException("No Converter for type [{}]", rowType.getName()); + throw new ConvertException("Can not Converter from [{}] to [{}]", value.getClass().getName(), type.getTypeName()); } /** @@ -350,11 +351,7 @@ public class ConverterRegistry implements Serializable { // 数组转换 if (rowType.isArray()) { final ArrayConverter arrayConverter = new ArrayConverter(rowType); - try { - return (T) arrayConverter.convert(value, defaultValue); - } catch (Exception e) { - // 数组转换失败进行下一步 - } + return (T) arrayConverter.convert(value, defaultValue); } // 表示非需要特殊转换的对象 diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/ArrayConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/ArrayConverter.java index 533b95bb9..8653f095f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/ArrayConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/ArrayConverter.java @@ -2,7 +2,7 @@ package cn.hutool.core.convert.impl; import cn.hutool.core.collection.IterUtil; import cn.hutool.core.convert.AbstractConverter; -import cn.hutool.core.convert.ConverterRegistry; +import cn.hutool.core.convert.Convert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; @@ -15,35 +15,54 @@ import java.util.List; /** * 数组转换器,包括原始类型数组 - * + * * @author Looly */ public class ArrayConverter extends AbstractConverter { private static final long serialVersionUID = 1L; private final Class targetType; - /** 目标元素类型 */ + /** + * 目标元素类型 + */ private final Class targetComponentType; + /** + * 是否忽略元素转换错误 + */ + private boolean ignoreElementError; + /** * 构造 - * + * * @param targetType 目标数组类型 */ public ArrayConverter(Class targetType) { + this(targetType, false); + } + + /** + * 构造 + * + * @param targetType 目标数组类型 + * @param ignoreElementError 是否忽略元素转换错误 + */ + public ArrayConverter(Class targetType, boolean ignoreElementError) { if (null == targetType) { // 默认Object数组 targetType = Object[].class; } - - if(targetType.isArray()) { + + if (targetType.isArray()) { this.targetType = targetType; this.targetComponentType = targetType.getComponentType(); - }else { + } else { //用户传入类为非数组时,按照数组元素类型对待 this.targetComponentType = targetType; this.targetType = ArrayUtil.getArrayType(targetType); } + + this.ignoreElementError = ignoreElementError; } @Override @@ -51,16 +70,27 @@ public class ArrayConverter extends AbstractConverter { return value.getClass().isArray() ? convertArrayToArray(value) : convertObjectToArray(value); } - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({"unchecked", "rawtypes"}) @Override public Class getTargetType() { return this.targetType; } + /** + * 设置是否忽略元素转换错误 + * + * @param ignoreElementError 是否忽略元素转换错误 + * @since 5.4.3 + */ + public void setIgnoreElementError(boolean ignoreElementError) { + this.ignoreElementError = ignoreElementError; + } + // -------------------------------------------------------------------------------------- Private method start + /** * 数组对数组转换 - * + * * @param array 被转换的数组值 * @return 转换后的数组 */ @@ -74,16 +104,15 @@ public class ArrayConverter extends AbstractConverter { final int len = ArrayUtil.length(array); final Object result = Array.newInstance(targetComponentType, len); - final ConverterRegistry converter = ConverterRegistry.getInstance(); for (int i = 0; i < len; i++) { - Array.set(result, i, converter.convert(targetComponentType, Array.get(array, i))); + Array.set(result, i, convertComponentType(Array.get(array, i))); } return result; } /** * 非数组对数组转换 - * + * * @param value 被转换值 * @return 转换后的数组 */ @@ -98,14 +127,13 @@ public class ArrayConverter extends AbstractConverter { return convertArrayToArray(strings); } - final ConverterRegistry converter = ConverterRegistry.getInstance(); Object result; if (value instanceof List) { // List转数组 final List list = (List) value; result = Array.newInstance(targetComponentType, list.size()); for (int i = 0; i < list.size(); i++) { - Array.set(result, i, converter.convert(targetComponentType, list.get(i))); + Array.set(result, i, convertComponentType(list.get(i))); } } else if (value instanceof Collection) { // 集合转数组 @@ -114,7 +142,7 @@ public class ArrayConverter extends AbstractConverter { int i = 0; for (Object element : collection) { - Array.set(result, i, converter.convert(targetComponentType, element)); + Array.set(result, i, convertComponentType(element)); i++; } } else if (value instanceof Iterable) { @@ -122,16 +150,16 @@ public class ArrayConverter extends AbstractConverter { final List list = IterUtil.toList((Iterable) value); result = Array.newInstance(targetComponentType, list.size()); for (int i = 0; i < list.size(); i++) { - Array.set(result, i, converter.convert(targetComponentType, list.get(i))); + Array.set(result, i, convertComponentType(list.get(i))); } } else if (value instanceof Iterator) { // 可循环对象转数组,可循环对象无法获取长度,因此先转为List后转为数组 final List list = IterUtil.toList((Iterator) value); result = Array.newInstance(targetComponentType, list.size()); for (int i = 0; i < list.size(); i++) { - Array.set(result, i, converter.convert(targetComponentType, list.get(i))); + Array.set(result, i, convertComponentType(list.get(i))); } - }else if (value instanceof Serializable && byte.class == targetComponentType) { + } else if (value instanceof Serializable && byte.class == targetComponentType) { // 用户可能想序列化指定对象 result = ObjectUtil.serialize(value); } else { @@ -144,14 +172,25 @@ public class ArrayConverter extends AbstractConverter { /** * 单元素数组 - * + * * @param value 被转换的值 * @return 数组,只包含一个元素 */ private Object[] convertToSingleElementArray(Object value) { final Object[] singleElementArray = ArrayUtil.newArray(targetComponentType, 1); - singleElementArray[0] = ConverterRegistry.getInstance().convert(targetComponentType, value); + singleElementArray[0] = convertComponentType(value); return singleElementArray; } + + /** + * 转换元素类型 + * + * @param value 值 + * @return 转换后的值,转换失败若{@link #ignoreElementError}为true,返回null,否则抛出异常 + * @since 5.4.3 + */ + private Object convertComponentType(Object value) { + return Convert.convertWithCheck(this.targetComponentType, value, null, this.ignoreElementError); + } // -------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/CollectionConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/CollectionConverter.java index 6dd82da13..a2633e86d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/CollectionConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/CollectionConverter.java @@ -2,6 +2,7 @@ package cn.hutool.core.convert.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Converter; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.TypeUtil; import java.lang.reflect.Type; @@ -60,13 +61,8 @@ public class CollectionConverter implements Converter> { @Override public Collection convert(Object value, Collection defaultValue) throws IllegalArgumentException { - Collection result; - try { - result = convertInternal(value); - } catch (RuntimeException e) { - return defaultValue; - } - return ((null == result) ? defaultValue : result); + final Collection result = convertInternal(value); + return ObjectUtil.defaultIfNull(result, defaultValue); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java index 97fca587f..3054e76a8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java @@ -207,7 +207,16 @@ public class NumberConverter extends AbstractConverter { @Override protected String convertToStr(Object value) { - return StrUtil.trim(super.convertToStr(value)); + String result = StrUtil.trim(super.convertToStr(value)); + if(StrUtil.isNotEmpty(result)){ + final char c = Character.toUpperCase(result.charAt(result.length() - 1)); + if(c == 'D' || c == 'L' || c == 'F'){ + // 类型标识形式(例如123.6D) + return StrUtil.subPre(result, -1); + } + } + + return result; } @Override diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java index 460583fae..5b8df51ed 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java @@ -214,9 +214,14 @@ public class DateUtil extends CalendarUtil { /** * 获得指定日期是所在年份的第几周
+ * 此方法返回值与一周的第一天有关,比如:
+ * 2016年1月3日为周日,如果一周的第一天为周日,那这天是第二周(返回2)
+ * 如果一周的第一天为周一,那这天是第一周(返回1)
+ * 跨年的那个星期得到的结果总是1 * * @param date 日期 * @return 周 + * @see DateTime#setFirstDayOfWeek(Week) */ public static int weekOfYear(Date date) { return DateTime.of(date).weekOfYear(); @@ -1383,30 +1388,6 @@ public class DateUtil extends CalendarUtil { return new DateBetween(beginDate, endDate).betweenMonth(isReset); } - /** - * 获取两个日期之间所有的月份 - * @param start 开始时间 - * @param end 结束时间 - * @return List 格式为yyyMM格式的月份列表 包含收尾 - * @since 5.4.4 - */ - public static List getBetweenMonths(Date start, Date end) { - List result = new ArrayList<>(); - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM"); - Calendar tempStart = Calendar.getInstance(); - tempStart.setTime(start); - // 加一个月,保证开始和结束同步时返回当月 - tempStart.add(Calendar.MONTH, 1); - Calendar tempEnd = Calendar.getInstance(); - tempEnd.setTime(end); - result.add(sdf.format(start)); - while (tempStart.before(tempEnd) || tempStart.equals(tempEnd)) { - result.add(sdf.format(tempStart.getTime())); - tempStart.add(Calendar.MONTH, 1); - } - return result; - } - /** * 计算两个日期相差年数
* 在非重置情况下,如果起始日期的月小于结束日期的月,年数要少算1(不足1年) diff --git a/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java index 4b5c29eb1..a1745440f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java @@ -1,6 +1,8 @@ package cn.hutool.core.date; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReUtil; +import cn.hutool.core.util.StrUtil; import java.time.Duration; import java.time.Instant; @@ -10,6 +12,7 @@ import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalUnit; @@ -245,7 +248,29 @@ public class LocalDateTimeUtil { if (null == text) { return null; } - return parse(text, DateTimeFormatter.ofPattern(format)); + + DateTimeFormatter formatter = null; + if(StrUtil.isNotBlank(format)){ + // 修复yyyyMMddHHmmssSSS格式不能解析的问题 + // fix issue#1082 + //see https://stackoverflow.com/questions/22588051/is-java-time-failing-to-parse-fraction-of-second + // jdk8 bug at: https://bugs.openjdk.java.net/browse/JDK-8031085 + if(StrUtil.startWithIgnoreEquals(format, DatePattern.PURE_DATETIME_PATTERN)){ + final String fraction = StrUtil.removePrefix(format, DatePattern.PURE_DATETIME_PATTERN); + if(ReUtil.isMatch("[S]{1,2}", fraction)){ + //将yyyyMMddHHmmssS、yyyyMMddHHmmssSS的日期统一替换为yyyyMMddHHmmssSSS格式,用0补 + text += StrUtil.repeat('0', 3-fraction.length()); + } + formatter = new DateTimeFormatterBuilder() + .appendPattern(DatePattern.PURE_DATETIME_PATTERN) + .appendValue(ChronoField.MILLI_OF_SECOND, 3) + .toFormatter(); + } else{ + formatter = DateTimeFormatter.ofPattern(format); + } + } + + return parse(text, formatter); } /** @@ -403,7 +428,7 @@ public class LocalDateTimeUtil { * @return 一天的开始时间 */ public static LocalDateTime beginOfDay(LocalDateTime time) { - return time.with(LocalTime.of(0, 0, 0, 0)); + return time.with(LocalTime.MIN); } /** @@ -413,7 +438,7 @@ public class LocalDateTimeUtil { * @return 一天的结束时间 */ public static LocalDateTime endOfDay(LocalDateTime time) { - return time.with(LocalTime.of(23, 59, 59, 999_999_999)); + return time.with(LocalTime.MAX); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/date/chinese/ChineseMonth.java b/hutool-core/src/main/java/cn/hutool/core/date/chinese/ChineseMonth.java index 65faa8022..600d9e1f8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/chinese/ChineseMonth.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/chinese/ChineseMonth.java @@ -14,10 +14,12 @@ public class ChineseMonth { /** * 当前农历月份是否为闰月 * + * @param year 农历年 + * @param month 农历月 * @return 是否为闰月 * @since 5.4.2 */ - public static boolean isLeapMonth(int year, int month){ + public static boolean isLeapMonth(int year, int month) { return month == LunarInfo.leapMonth(year); } @@ -26,7 +28,7 @@ public class ChineseMonth { * 当为传统表示时,表示为二月,腊月,或者润正月等 * 当为非传统表示时,二月,十二月,或者润一月等 * - * @param isLeapMonth 是否闰月 + * @param isLeapMonth 是否闰月 * @param month 月份,从1开始 * @param isTraditional 是否传统表示,例如一月传统表示为正月 * @return 返回农历月份称呼 diff --git a/hutool-core/src/main/java/cn/hutool/core/date/chinese/LunarFestival.java b/hutool-core/src/main/java/cn/hutool/core/date/chinese/LunarFestival.java index b3771b963..4dbeca2ee 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/chinese/LunarFestival.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/chinese/LunarFestival.java @@ -22,7 +22,7 @@ public class LunarFestival { lFtv.put(new Pair<>(1, 3), "猪日"); lFtv.put(new Pair<>(1, 4), "羊日"); lFtv.put(new Pair<>(1, 5), "牛日 破五日"); - lFtv.put(new Pair<>(1, 6), "马日,送穷日"); + lFtv.put(new Pair<>(1, 6), "马日 送穷日"); lFtv.put(new Pair<>(1, 7), "人日 人胜节"); lFtv.put(new Pair<>(1, 8), "谷日 八仙日"); lFtv.put(new Pair<>(1, 9), "天日 九皇会"); diff --git a/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java b/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java index 5419cfba5..fe107ad31 100644 --- a/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java @@ -392,35 +392,36 @@ public class ImgUtil { int srcWidth = srcImage.getWidth(null); // 源图宽度 int srcHeight = srcImage.getHeight(null); // 源图高度 - try { - if (srcWidth > destWidth && srcHeight > destHeight) { - int cols; // 切片横向数量 - int rows; // 切片纵向数量 - // 计算切片的横向和纵向数量 - if (srcWidth % destWidth == 0) { - cols = srcWidth / destWidth; - } else { - cols = (int) Math.floor((double) srcWidth / destWidth) + 1; - } - if (srcHeight % destHeight == 0) { - rows = srcHeight / destHeight; - } else { - rows = (int) Math.floor((double) srcHeight / destHeight) + 1; - } - // 循环建立切片 - Image tag; - for (int i = 0; i < rows; i++) { - for (int j = 0; j < cols; j++) { - // 四个参数分别为图像起点坐标和宽高 - // 即: CropImageFilter(int x,int y,int width,int height) - tag = cut(srcImage, new Rectangle(j * destWidth, i * destHeight, destWidth, destHeight)); - // 输出为文件 - ImageIO.write(toRenderedImage(tag), IMAGE_TYPE_JPEG, new File(descDir, "_r" + i + "_c" + j + ".jpg")); - } - } + if(srcWidth < destWidth){ + destWidth = srcWidth; + } + if(srcHeight < destHeight){ + destHeight = srcHeight; + } + + int cols; // 切片横向数量 + int rows; // 切片纵向数量 + // 计算切片的横向和纵向数量 + if (srcWidth % destWidth == 0) { + cols = srcWidth / destWidth; + } else { + cols = (int) Math.floor((double) srcWidth / destWidth) + 1; + } + if (srcHeight % destHeight == 0) { + rows = srcHeight / destHeight; + } else { + rows = (int) Math.floor((double) srcHeight / destHeight) + 1; + } + // 循环建立切片 + Image tag; + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + // 四个参数分别为图像起点坐标和宽高 + // 即: CropImageFilter(int x,int y,int width,int height) + tag = cut(srcImage, new Rectangle(j * destWidth, i * destHeight, destWidth, destHeight)); + // 输出为文件 + write(tag, FileUtil.file(descDir, "_r" + i + "_c" + j + ".jpg")); } - } catch (IOException e) { - throw new IORuntimeException(e); } } @@ -463,9 +464,9 @@ public class ImgUtil { cols = 2; // 切片列数 } // 读取源图像 - final Image bi = toBufferedImage(srcImage); - int srcWidth = bi.getWidth(null); // 源图宽度 - int srcHeight = bi.getHeight(null); // 源图高度 + final BufferedImage bi = toBufferedImage(srcImage); + int srcWidth = bi.getWidth(); // 源图宽度 + int srcHeight = bi.getHeight(); // 源图高度 int destWidth = NumberUtil.partValue(srcWidth, cols); // 每张切片的宽度 int destHeight = NumberUtil.partValue(srcHeight, rows); // 每张切片的高度 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 953b445de..6b3db398a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -1005,20 +1005,14 @@ public class FileUtil extends PathUtil { } /** - * 修改文件或目录的文件名,不变更路径,只是简单修改文件名
- * 重命名有两种模式:
- * 1、isRetainExt为true时,保留原扩展名: + * 修改文件或目录的文件名,不变更路径,只是简单修改文件名,不保留扩展名。
* *
-	 * FileUtil.rename(file, "aaa", true) xx/xx.png =》xx/aaa.png
-	 * 
- * - *
-	 * FileUtil.rename(file, "aaa.jpg", false) xx/xx.png =》xx/aaa.jpg
+	 * FileUtil.rename(file, "aaa.png", true) xx/xx.png =》xx/aaa.png
 	 * 
* * @param file 被修改的文件 - * @param newName 新的文件名,包括扩展名 + * @param newName 新的文件名,如需扩展名,需自行在此参数加上,原文件名的扩展名不会被保留 * @param isOverride 是否覆盖目标文件 * @return 目标文件 * @since 5.3.6 @@ -1035,6 +1029,7 @@ public class FileUtil extends PathUtil { *
 	 * FileUtil.rename(file, "aaa", true) xx/xx.png =》xx/aaa.png
 	 * 
+ * *

* 2、isRetainExt为false时,不保留原扩展名,需要在newName中 * @@ -2441,6 +2436,20 @@ public class FileUtil extends PathUtil { return new PrintWriter(getWriter(file, charset, isAppend)); } + /** + * 获得一个打印写入对象,可以有print + * + * @param file 文件 + * @param charset 字符集 + * @param isAppend 是否追加 + * @return 打印对象 + * @throws IORuntimeException IO异常 + * @since 5.4.3 + */ + public static PrintWriter getPrintWriter(File file, Charset charset, boolean isAppend) throws IORuntimeException { + return new PrintWriter(getWriter(file, charset, isAppend)); + } + /** * 获取当前系统的换行分隔符 * 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 6700bb29b..134f377fa 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 @@ -421,6 +421,18 @@ public class IoUtil { // -------------------------------------------------------------------------------------- read start + /** + * 从流中读取UTF8编码的内容 + * + * @param in 输入流 + * @return 内容 + * @throws IORuntimeException IO异常 + * @since 5.4.4 + */ + public static String readUtf8(InputStream in) throws IORuntimeException { + return read(in, CharsetUtil.CHARSET_UTF_8); + } + /** * 从流中读取内容 * @@ -1251,7 +1263,7 @@ public class IoUtil { * @throws IORuntimeException IO异常 * @since 5.4.0 */ - public static long checksumValue(InputStream in, Checksum checksum){ + public static long checksumValue(InputStream in, Checksum checksum) { return checksum(in, checksum).getValue(); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Console.java b/hutool-core/src/main/java/cn/hutool/core/lang/Console.java index a50f4edb2..d436b27c4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Console.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Console.java @@ -1,5 +1,6 @@ package cn.hutool.core.lang; +import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.StrUtil; @@ -11,14 +12,16 @@ import static java.lang.System.out; /** * 命令行(控制台)工具方法类
* 此类主要针对{@link System#out} 和 {@link System#err} 做封装。 - * - * @author Looly * + * @author Looly */ public class Console { + private static final String TEMPLATE_VAR = "{}"; + // --------------------------------------------------------------------------------- Log + /** * 同 System.out.println()方法,打印控制台日志 */ @@ -29,79 +32,55 @@ public class Console { /** * 同 System.out.println()方法,打印控制台日志
* 如果传入打印对象为{@link Throwable}对象,那么同时打印堆栈 - * + * * @param obj 要打印的对象 */ public static void log(Object obj) { if (obj instanceof Throwable) { - Throwable e = (Throwable) obj; + final Throwable e = (Throwable) obj; log(e, e.getMessage()); } else { - log("{}", obj); + log(TEMPLATE_VAR, obj); } } /** - * 同 System.out.print()方法,打印控制台日志 - * - * @param obj 要打印的对象 - * @since 3.3.1 + * 同 System.out.println()方法,打印控制台日志
+ * 如果传入打印对象为{@link Throwable}对象,那么同时打印堆栈 + * + * @param obj1 第一个要打印的对象 + * @param otherObjs 其它要打印的对象 + * @since 5.4.3 */ - public static void print(Object obj) { - print("{}", obj); + public static void log(Object obj1, Object... otherObjs) { + if(ArrayUtil.isEmpty(otherObjs)){ + log(obj1); + } else{ + log(buildTemplateSplitBySpace(otherObjs.length + 1), ArrayUtil.insert(otherObjs, 0, obj1)); + } } /** - * 打印进度条 - * - * @param showChar 进度条提示字符,例如“#” - * @param len 打印长度 - * @since 4.5.6 - */ - public static void printProgress(char showChar, int len) { - print("{}{}", CharUtil.CR, StrUtil.repeat(showChar, len)); - } - - /** - * 打印进度条 - * - * @param showChar 进度条提示字符,例如“#” - * @param totalLen 总长度 - * @param rate 总长度所占比取值0~1 - * @since 4.5.6 - */ - public static void printProgress(char showChar, int totalLen, double rate) { - Assert.isTrue(rate >= 0 && rate <= 1, "Rate must between 0 and 1 (both include)"); - printProgress(showChar, (int) (totalLen * rate)); - } - - /** - * 同 System.out.println()方法,打印控制台日志 - * + * 同 System.out.println()方法,打印控制台日志
+ * 当传入template无"{}"时,被认为非模板,直接打印多个参数以空格分隔 + * * @param template 文本模板,被替换的部分用 {} 表示 - * @param values 值 + * @param values 值 */ public static void log(String template, Object... values) { - log(null, template, values); - } - - /** - * 同 System.out.print()方法,打印控制台日志 - * - * @param template 文本模板,被替换的部分用 {} 表示 - * @param values 值 - * @since 3.3.1 - */ - public static void print(String template, Object... values) { - out.print(StrUtil.format(template, values)); + if (ArrayUtil.isEmpty(values) || StrUtil.contains(template, TEMPLATE_VAR)) { + logInternal(template, values); + } else { + logInternal(buildTemplateSplitBySpace(values.length + 1), ArrayUtil.insert(values, 0, template)); + } } /** * 同 System.out.println()方法,打印控制台日志 - * - * @param t 异常对象 + * + * @param t 异常对象 * @param template 文本模板,被替换的部分用 {} 表示 - * @param values 值 + * @param values 值 */ public static void log(Throwable t, String template, Object... values) { out.println(StrUtil.format(template, values)); @@ -111,7 +90,96 @@ public class Console { } } + /** + * 同 System.out.println()方法,打印控制台日志 + * + * @param template 文本模板,被替换的部分用 {} 表示 + * @param values 值 + * @since 5.4.3 + */ + private static void logInternal(String template, Object... values){ + log(null, template, values); + } + + // --------------------------------------------------------------------------------- print + /** + * 同 System.out.print()方法,打印控制台日志 + * + * @param obj 要打印的对象 + * @since 3.3.1 + */ + public static void print(Object obj) { + print(TEMPLATE_VAR, obj); + } + + /** + * 同 System.out.println()方法,打印控制台日志
+ * 如果传入打印对象为{@link Throwable}对象,那么同时打印堆栈 + * + * @param obj1 第一个要打印的对象 + * @param otherObjs 其它要打印的对象 + * @since 5.4.3 + */ + public static void print(Object obj1, Object... otherObjs) { + if(ArrayUtil.isEmpty(otherObjs)){ + print(obj1); + } else{ + print(buildTemplateSplitBySpace(otherObjs.length + 1), ArrayUtil.insert(otherObjs, 0, obj1)); + } + } + + /** + * 同 System.out.print()方法,打印控制台日志 + * + * @param template 文本模板,被替换的部分用 {} 表示 + * @param values 值 + * @since 3.3.1 + */ + public static void print(String template, Object... values) { + if (ArrayUtil.isEmpty(values) || StrUtil.contains(template, TEMPLATE_VAR)) { + printInternal(template, values); + } else { + printInternal(buildTemplateSplitBySpace(values.length + 1), ArrayUtil.insert(values, 0, template)); + } + } + + /** + * 打印进度条 + * + * @param showChar 进度条提示字符,例如“#” + * @param len 打印长度 + * @since 4.5.6 + */ + public static void printProgress(char showChar, int len) { + print("{}{}", CharUtil.CR, StrUtil.repeat(showChar, len)); + } + + /** + * 打印进度条 + * + * @param showChar 进度条提示字符,例如“#” + * @param totalLen 总长度 + * @param rate 总长度所占比取值0~1 + * @since 4.5.6 + */ + public static void printProgress(char showChar, int totalLen, double rate) { + Assert.isTrue(rate >= 0 && rate <= 1, "Rate must between 0 and 1 (both include)"); + printProgress(showChar, (int) (totalLen * rate)); + } + + /** + * 同 System.out.println()方法,打印控制台日志 + * + * @param template 文本模板,被替换的部分用 {} 表示 + * @param values 值 + * @since 5.4.3 + */ + private static void printInternal(String template, Object... values){ + out.print(StrUtil.format(template, values)); + } + // --------------------------------------------------------------------------------- Error + /** * 同 System.err.println()方法,打印控制台日志 */ @@ -121,7 +189,7 @@ public class Console { /** * 同 System.err.println()方法,打印控制台日志 - * + * * @param obj 要打印的对象 */ public static void error(Object obj) { @@ -129,26 +197,46 @@ public class Console { Throwable e = (Throwable) obj; error(e, e.getMessage()); } else { - error("{}", obj); + error(TEMPLATE_VAR, obj); + } + } + + /** + * 同 System.out.println()方法,打印控制台日志
+ * 如果传入打印对象为{@link Throwable}对象,那么同时打印堆栈 + * + * @param obj1 第一个要打印的对象 + * @param otherObjs 其它要打印的对象 + * @since 5.4.3 + */ + public static void error(Object obj1, Object... otherObjs) { + if(ArrayUtil.isEmpty(otherObjs)){ + error(obj1); + } else{ + error(buildTemplateSplitBySpace(otherObjs.length + 1), ArrayUtil.insert(otherObjs, 0, obj1)); } } /** * 同 System.err.println()方法,打印控制台日志 - * + * * @param template 文本模板,被替换的部分用 {} 表示 - * @param values 值 + * @param values 值 */ public static void error(String template, Object... values) { - error(null, template, values); + if (ArrayUtil.isEmpty(values) || StrUtil.contains(template, TEMPLATE_VAR)) { + errorInternal(template, values); + } else { + errorInternal(buildTemplateSplitBySpace(values.length + 1), ArrayUtil.insert(values, 0, template)); + } } /** * 同 System.err.println()方法,打印控制台日志 - * - * @param t 异常对象 + * + * @param t 异常对象 * @param template 文本模板,被替换的部分用 {} 表示 - * @param values 值 + * @param values 值 */ public static void error(Throwable t, String template, Object... values) { err.println(StrUtil.format(template, values)); @@ -158,10 +246,21 @@ public class Console { } } + /** + * 同 System.err.println()方法,打印控制台日志 + * + * @param template 文本模板,被替换的部分用 {} 表示 + * @param values 值 + */ + private static void errorInternal(String template, Object... values) { + error(null, template, values); + } + // --------------------------------------------------------------------------------- in + /** * 创建从控制台读取内容的{@link Scanner} - * + * * @return {@link Scanner} * @since 3.3.1 */ @@ -171,7 +270,7 @@ public class Console { /** * 读取用户输入的内容(在控制台敲回车前的内容) - * + * * @return 用户输入的内容 * @since 3.3.1 */ @@ -180,6 +279,7 @@ public class Console { } // --------------------------------------------------------------------------------- console lineNumber + /** * 返回当前位置+行号 (不支持Lambda、内部类、递归内使用) * @@ -193,7 +293,7 @@ public class Console { final String methodName = stackTraceElement.getMethodName(); final String fileName = stackTraceElement.getFileName(); final Integer lineNumber = stackTraceElement.getLineNumber(); - return String.format("%s.%s(%s:%s)", className,methodName,fileName,lineNumber); + return String.format("%s.%s(%s:%s)", className, methodName, fileName, lineNumber); } /** @@ -206,4 +306,14 @@ public class Console { return new Throwable().getStackTrace()[1].getLineNumber(); } + /** + * 构建空格分隔的模板,类似于"{} {} {} {}" + * + * @param count 变量数量 + * @return 模板 + */ + private static String buildTemplateSplitBySpace(int count){ + return StrUtil.repeatAndJoin(TEMPLATE_VAR, count, StrUtil.SPACE); + } + } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Dict.java b/hutool-core/src/main/java/cn/hutool/core/lang/Dict.java index 3f617fd84..425cf1225 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Dict.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Dict.java @@ -93,9 +93,9 @@ public class Dict extends LinkedHashMap implements BasicTypeGett String key = null; for(int i = 0; i < keysAndValues.length; i++){ if(i % 2 == 0){ - dict.put(key, keysAndValues[i]); - } else{ key = Convert.toStr(keysAndValues[i]); + } else{ + dict.put(key, keysAndValues[i]); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java b/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java index fa9510594..7e0af8387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java @@ -7,23 +7,36 @@ import java.util.Objects; /** * 键值对对象,只能在构造时传入键值 - * - * @author looly * * @param 键类型 * @param 值类型 + * @author looly * @since 4.1.5 */ -public class Pair extends CloneSupport> implements Serializable{ +public class Pair extends CloneSupport> implements Serializable { private static final long serialVersionUID = 1L; - + private final K key; private final V value; + /** + * 构建{@link Pair}对象 + * + * @param 键类型 + * @param 值类型 + * @param key 键 + * @param value 值 + * @return {@link Pair} + * @since 5.4.3 + */ + public static Pair of(K key, V value) { + return new Pair<>(key, value); + } + /** * 构造 - * - * @param key 键 + * + * @param key 键 * @param value 值 */ public Pair(K key, V value) { @@ -33,6 +46,7 @@ public class Pair extends CloneSupport> implements Serializable /** * 获取键 + * * @return 键 */ public K getKey() { @@ -41,6 +55,7 @@ public class Pair extends CloneSupport> implements Serializable /** * 获取值 + * * @return 值 */ public V getValue() { diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/generator/Generator.java b/hutool-core/src/main/java/cn/hutool/core/lang/generator/Generator.java new file mode 100644 index 000000000..ce05812de --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/generator/Generator.java @@ -0,0 +1,18 @@ +package cn.hutool.core.lang.generator; + +/** + * 生成器泛型接口
+ * 通过实现此接口可以自定义生成对象的策略 + * + * @param 生成对象类型 + * @since 5.4.3 + */ +public interface Generator { + + /** + * 生成新的对象 + * + * @return 新的对象 + */ + T next(); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/generator/ObjectGenerator.java b/hutool-core/src/main/java/cn/hutool/core/lang/generator/ObjectGenerator.java new file mode 100644 index 000000000..4af05e354 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/generator/ObjectGenerator.java @@ -0,0 +1,28 @@ +package cn.hutool.core.lang.generator; + +import cn.hutool.core.util.ReflectUtil; + +/** + * 对象生成器,通过指定对象的Class类型,调用next方法时生成新的对象。 + * + * @param 对象类型 + * @author looly + * @since 5.4.3 + */ +public class ObjectGenerator implements Generator { + + private final Class clazz; + + /** + * 构造 + * @param clazz 对象类型 + */ + public ObjectGenerator(Class clazz) { + this.clazz = clazz; + } + + @Override + public T next() { + return ReflectUtil.newInstanceIfPossible(this.clazz); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/generator/ObjectIdGenerator.java b/hutool-core/src/main/java/cn/hutool/core/lang/generator/ObjectIdGenerator.java new file mode 100644 index 000000000..27c6c9b5f --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/generator/ObjectIdGenerator.java @@ -0,0 +1,16 @@ +package cn.hutool.core.lang.generator; + +import cn.hutool.core.lang.ObjectId; + +/** + * ObjectId生成器 + * + * @author looly + * @since 5.4.3 + */ +public class ObjectIdGenerator implements Generator { + @Override + public String next() { + return ObjectId.next(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/generator/SnowflakeGenerator.java b/hutool-core/src/main/java/cn/hutool/core/lang/generator/SnowflakeGenerator.java new file mode 100644 index 000000000..b4260fa58 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/generator/SnowflakeGenerator.java @@ -0,0 +1,38 @@ +package cn.hutool.core.lang.generator; + +import cn.hutool.core.lang.Snowflake; + +/** + * Snowflake生成器
+ * 注意,默认此生成器必须单例使用,否则会有重复
+ * 默认构造的终端ID和数据中心ID都为0,不适用于分布式环境。 + * + * @author looly + * @since 5.4.3 + */ +public class SnowflakeGenerator implements Generator { + + private final Snowflake snowflake; + + /** + * 构造 + */ + public SnowflakeGenerator() { + this(0, 0); + } + + /** + * 构造 + * + * @param workerId 终端ID + * @param dataCenterId 数据中心ID + */ + public SnowflakeGenerator(long workerId, long dataCenterId) { + snowflake = new Snowflake(workerId, dataCenterId); + } + + @Override + public Long next() { + return this.snowflake.nextId(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/generator/UUIDGenerator.java b/hutool-core/src/main/java/cn/hutool/core/lang/generator/UUIDGenerator.java new file mode 100644 index 000000000..301b5c0a0 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/generator/UUIDGenerator.java @@ -0,0 +1,16 @@ +package cn.hutool.core.lang.generator; + +import cn.hutool.core.util.IdUtil; + +/** + * UUID生成器 + * + * @author looly + * @since 5.4.3 + */ +public class UUIDGenerator implements Generator { + @Override + public String next() { + return IdUtil.fastUUID(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/generator/package-info.java b/hutool-core/src/main/java/cn/hutool/core/lang/generator/package-info.java new file mode 100644 index 000000000..59c06ffb5 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/generator/package-info.java @@ -0,0 +1,7 @@ +/** + * 提供生成器接口及相关封装 + * + * @author looly + * + */ +package cn.hutool.core.lang.generator; \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/intern/InternUtil.java b/hutool-core/src/main/java/cn/hutool/core/lang/intern/InternUtil.java new file mode 100644 index 000000000..7c31ac20c --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/intern/InternUtil.java @@ -0,0 +1,40 @@ +package cn.hutool.core.lang.intern; + +/** + * 规范化对象生成工具 + * + * @author looly + * @since 5.4.3 + */ +public class InternUtil { + + /** + * 创建WeakHshMap实现的字符串规范化器 + * + * @param 规范对象的类型 + * @return {@link Interner} + */ + public static Interner createWeakInterner(){ + return new WeakInterner<>(); + } + + /** + * 创建JDK默认实现的字符串规范化器 + * + * @return {@link Interner} + * @see String#intern() + */ + public static Interner createJdkInterner(){ + return new JdkStringInterner(); + } + + /** + * 创建字符串规范化器 + * + * @param isWeak 是否创建使用WeakHashMap实现的Interner + * @return {@link Interner} + */ + public static Interner createStringInterner(boolean isWeak){ + return isWeak ? createWeakInterner() : createJdkInterner(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/intern/Interner.java b/hutool-core/src/main/java/cn/hutool/core/lang/intern/Interner.java new file mode 100644 index 000000000..0cb0b3b57 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/intern/Interner.java @@ -0,0 +1,21 @@ +package cn.hutool.core.lang.intern; + +/** + * 规范化表示形式封装
+ * 所谓规范化,即当两个对象equals时,规范化的对象则可以实现==
+ * 此包中的相关封装类似于 {@link String#intern()} + * + * @param 规范化的对象类型 + * @author looly + * @since 5.4.3 + */ +public interface Interner { + + /** + * 返回指定对象对应的规范化对象,sample对象可能有多个,但是这些对象如果都equals,则返回的是同一个对象 + * + * @param sample 对象 + * @return 样例对象 + */ + T intern(T sample); +} \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/intern/JdkStringInterner.java b/hutool-core/src/main/java/cn/hutool/core/lang/intern/JdkStringInterner.java new file mode 100644 index 000000000..8cd179019 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/intern/JdkStringInterner.java @@ -0,0 +1,17 @@ +package cn.hutool.core.lang.intern; + +/** + * JDK中默认的字符串规范化实现 + * + * @author looly + * @since 5.4.3 + */ +public class JdkStringInterner implements Interner{ + @Override + public String intern(String sample) { + if(null == sample){ + return null; + } + return sample.intern(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/intern/WeakInterner.java b/hutool-core/src/main/java/cn/hutool/core/lang/intern/WeakInterner.java new file mode 100644 index 000000000..662084346 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/intern/WeakInterner.java @@ -0,0 +1,22 @@ +package cn.hutool.core.lang.intern; + +import cn.hutool.core.lang.SimpleCache; + +/** + * 使用WeakHashMap(线程安全)存储对象的规范化对象,注意此对象需单例使用!
+ * + * @author looly + * @since 5.4.3 + */ +public class WeakInterner implements Interner{ + + private final SimpleCache cache = new SimpleCache<>(); + + @Override + public T intern(T sample) { + if(null == sample){ + return null; + } + return cache.get(sample, ()->sample); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/intern/package-info.java b/hutool-core/src/main/java/cn/hutool/core/lang/intern/package-info.java new file mode 100644 index 000000000..e6d811dc9 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/intern/package-info.java @@ -0,0 +1,8 @@ +/** + * 规范化表示形式封装
+ * 所谓规范化,即当两个对象equals时,规范化的对象则可以实现==
+ * 此包中的相关封装类似于 String#intern() + * + * @author looly + */ +package cn.hutool.core.lang.intern; \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/math/Calculator.java b/hutool-core/src/main/java/cn/hutool/core/math/Calculator.java new file mode 100644 index 000000000..0fb845ea7 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/math/Calculator.java @@ -0,0 +1,193 @@ +package cn.hutool.core.math; + +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.StrUtil; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.Stack; + +/** + * 数学表达式计算工具类
+ * 见:https://github.com/looly/hutool/issues/1090#issuecomment-693750140 + * + * @author trainliang, looly + * @since 5.4.3 + */ +public class Calculator { + private final Stack postfixStack = new Stack<>();// 后缀式栈 + private final Stack opStack = new Stack<>();// 运算符栈 + private final int[] operatPriority = new int[]{0, 3, 2, 1, -1, 1, 0, 2};// 运用运算符ASCII码-40做索引的运算符优先级 + + /** + * 计算表达式的值 + * + * @param expression 表达式 + * @return 计算结果 + */ + public static double conversion(String expression) { + final Calculator cal = new Calculator(); + expression = transform(expression); + return cal.calculate(expression); + } + + /** + * 将表达式中负数的符号更改 + * + * @param expression 例如-2+-1*(-3E-2)-(-1) 被转为 ~2+~1*(~3E~2)-(~1) + * @return 转换后的字符串 + */ + private static String transform(String expression) { + expression = StrUtil.cleanBlank(expression); + expression = StrUtil.removeSuffix(expression, "="); + final char[] arr = expression.toCharArray(); + for (int i = 0; i < arr.length; i++) { + if (arr[i] == '-') { + if (i == 0) { + arr[i] = '~'; + } else { + char c = arr[i - 1]; + if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == 'E' || c == 'e') { + arr[i] = '~'; + } + } + } + } + if (arr[0] == '~' || arr[1] == '(') { + arr[0] = '-'; + return "0" + new String(arr); + } else { + return new String(arr); + } + } + + /** + * 按照给定的表达式计算 + * + * @param expression 要计算的表达式例如:5+12*(3+5)/7 + * @return 计算结果 + */ + public double calculate(String expression) { + Stack resultStack = new Stack<>(); + prepare(expression); + Collections.reverse(postfixStack);// 将后缀式栈反转 + String firstValue, secondValue, currentValue;// 参与计算的第一个值,第二个值和算术运算符 + while (false == postfixStack.isEmpty()) { + currentValue = postfixStack.pop(); + if (false == isOperator(currentValue.charAt(0))) {// 如果不是运算符则存入操作数栈中 + currentValue = currentValue.replace("~", "-"); + resultStack.push(currentValue); + } else {// 如果是运算符则从操作数栈中取两个值和该数值一起参与运算 + secondValue = resultStack.pop(); + firstValue = resultStack.pop(); + + // 将负数标记符改为负号 + firstValue = firstValue.replace("~", "-"); + secondValue = secondValue.replace("~", "-"); + + BigDecimal tempResult = calculate(firstValue, secondValue, currentValue.charAt(0)); + resultStack.push(tempResult.toString()); + } + } + return Double.parseDouble(resultStack.pop()); + } + + /** + * 数据准备阶段将表达式转换成为后缀式栈 + * + * @param expression 表达式 + */ + private void prepare(String expression) { + opStack.push(',');// 运算符放入栈底元素逗号,此符号优先级最低 + char[] arr = expression.toCharArray(); + int currentIndex = 0;// 当前字符的位置 + int count = 0;// 上次算术运算符到本次算术运算符的字符的长度便于或者之间的数值 + char currentOp, peekOp;// 当前操作符和栈顶操作符 + for (int i = 0; i < arr.length; i++) { + currentOp = arr[i]; + if (isOperator(currentOp)) {// 如果当前字符是运算符 + if (count > 0) { + postfixStack.push(new String(arr, currentIndex, count));// 取两个运算符之间的数字 + } + peekOp = opStack.peek(); + if (currentOp == ')') {// 遇到反括号则将运算符栈中的元素移除到后缀式栈中直到遇到左括号 + while (opStack.peek() != '(') { + postfixStack.push(String.valueOf(opStack.pop())); + } + opStack.pop(); + } else { + while (currentOp != '(' && peekOp != ',' && compare(currentOp, peekOp)) { + postfixStack.push(String.valueOf(opStack.pop())); + peekOp = opStack.peek(); + } + opStack.push(currentOp); + } + count = 0; + currentIndex = i + 1; + } else { + count++; + } + } + if (count > 1 || (count == 1 && !isOperator(arr[currentIndex]))) {// 最后一个字符不是括号或者其他运算符的则加入后缀式栈中 + postfixStack.push(new String(arr, currentIndex, count)); + } + + while (opStack.peek() != ',') { + postfixStack.push(String.valueOf(opStack.pop()));// 将操作符栈中的剩余的元素添加到后缀式栈中 + } + } + + /** + * 判断是否为算术符号 + * + * @param c 字符 + * @return 是否为算术符号 + */ + private boolean isOperator(char c) { + return c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')'; + } + + /** + * 利用ASCII码-40做下标去算术符号优先级 + * + * @param cur 下标 + * @param peek peek + * @return 优先级 + */ + public boolean compare(char cur, char peek) {// 如果是peek优先级高于cur,返回true,默认都是peek优先级要低 + boolean result = false; + if (operatPriority[(peek) - 40] >= operatPriority[(cur) - 40]) { + result = true; + } + return result; + } + + /** + * 按照给定的算术运算符做计算 + * + * @param firstValue 第一个值 + * @param secondValue 第二个值 + * @param currentOp 算数符,只支持'+'、'-'、'*'、'/' + * @return 结果 + */ + private BigDecimal calculate(String firstValue, String secondValue, char currentOp) { + BigDecimal result; + switch (currentOp) { + case '+': + result = NumberUtil.add(firstValue, secondValue); + break; + case '-': + result = NumberUtil.sub(firstValue, secondValue); + break; + case '*': + result = NumberUtil.mul(firstValue, secondValue); + break; + case '/': + result = NumberUtil.div(firstValue, secondValue); + break; + default: + throw new IllegalStateException("Unexpected value: " + currentOp); + } + return result; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java index 829d7697b..a27af3716 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java @@ -42,7 +42,8 @@ import java.util.TreeSet; public class NetUtil { public final static String LOCAL_IP = "127.0.0.1"; - public static String LOCAL_HOSTNAME = ""; + + public static String localhostName; /** * 默认最小端口,1024 @@ -535,20 +536,26 @@ public class NetUtil { } /** - * 获取主机名称 + * 获取主机名称,一次获取会缓存名称 + * * @return 主机名称 * @since 5.4.4 */ public static String getLocalHostName() { - try { - if (StrUtil.isNotBlank(LOCAL_HOSTNAME)) { - return LOCAL_HOSTNAME; - } - LOCAL_HOSTNAME = InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException e) { - LOCAL_HOSTNAME = getLocalhostStr(); + if (StrUtil.isNotBlank(localhostName)) { + return localhostName; } - return LOCAL_HOSTNAME; + + final InetAddress localhost = getLocalhost(); + if(null != localhost){ + String name = localhost.getHostName(); + if(StrUtil.isEmpty(name)){ + name = localhost.getHostAddress(); + } + localhostName = name; + } + + return localhostName; } /** 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 95549f74f..69e3e6a23 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 @@ -70,6 +70,18 @@ public final class UrlBuilder implements Serializable { return of(uri.getScheme(), uri.getHost(), uri.getPort(), uri.getPath(), uri.getRawQuery(), uri.getFragment(), charset); } + /** + * 使用URL字符串构建UrlBuilder,当传入的URL没有协议时,按照http协议对待
+ * 此方法不对URL编码 + * + * @param httpUrl URL字符串 + * @return UrlBuilder + * @since 5.4.3 + */ + public static UrlBuilder ofHttpWithoutEncode(String httpUrl) { + return ofHttp(httpUrl, null); + } + /** * 使用URL字符串构建UrlBuilder,当传入的URL没有协议时,按照http协议对待。 * 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 74e4119b0..e6c4d05f2 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 @@ -195,10 +195,10 @@ public class UrlQuery { } key = entry.getKey(); if (StrUtil.isNotEmpty(key)) { - sb.append(URLUtil.encodeAll(StrUtil.str(key), charset)).append("="); + sb.append(URLUtil.encodeAll(StrUtil.str(key), charset)); value = entry.getValue(); - if (StrUtil.isNotEmpty(value)) { - sb.append(URLUtil.encodeAll(StrUtil.str(value), charset)); + if (null != value) { + sb.append("=").append(URLUtil.encodeAll(StrUtil.str(value), charset)); } } } @@ -246,8 +246,8 @@ public class UrlQuery { final String actualKey = URLUtil.decode(key, charset); this.query.put(actualKey, StrUtil.nullToEmpty(URLUtil.decode(value, charset))); } else if (null != value) { - // name为空,value作为name,value赋值"" - this.query.put(URLUtil.decode(value, charset), StrUtil.EMPTY); + // name为空,value作为name,value赋值null + this.query.put(URLUtil.decode(value, charset), null); } } } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrBuilder.java b/hutool-core/src/main/java/cn/hutool/core/text/StrBuilder.java index 12267f3f3..91535be4f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrBuilder.java @@ -462,6 +462,9 @@ public class StrBuilder implements CharSequence, Appendable, Serializable { @Override public char charAt(int index) { + if(index < 0){ + index = this.position + index; + } if ((index < 0) || (index > this.position)) { throw new StringIndexOutOfBoundsException(index); } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/escape/Html4Escape.java b/hutool-core/src/main/java/cn/hutool/core/text/escape/Html4Escape.java index 24a73b636..74312c2f7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/escape/Html4Escape.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/escape/Html4Escape.java @@ -5,6 +5,8 @@ import cn.hutool.core.text.replacer.ReplacerChain; /** * HTML4的ESCAPE + * 参考:Commons Lang3 + * * @author looly * */ diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java index e88a02322..a8616a78b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java @@ -29,6 +29,7 @@ public class CharUtil { public static final char DOUBLE_QUOTES = '"'; public static final char SINGLE_QUOTE = '\''; public static final char AMP = '&'; + public static final char AT = '@'; /** * 是否为ASCII字符,ASCII字符位于0~127之间 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java index 561f4902f..0dc707e64 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java @@ -7,6 +7,7 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.PatternPool; import cn.hutool.core.lang.Validator; +import java.io.Serializable; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -194,29 +195,29 @@ public class IdcardUtil { *

  • 通过上面得知如果余数是2,就会在身份证的第18位数字上出现罗马数字的Ⅹ。如果余数是10,身份证的最后一位号码就是2
  • * * - * @param idCard 待验证的身份证 + * @param idcard 待验证的身份证 * @return 是否有效的18位身份证 */ - public static boolean isValidCard18(String idCard) { - if (CHINA_ID_MAX_LENGTH != idCard.length()) { + public static boolean isValidCard18(String idcard) { + if (CHINA_ID_MAX_LENGTH != idcard.length()) { return false; } // 省份 - final String proCode = idCard.substring(0, 2); + final String proCode = idcard.substring(0, 2); if (null == CITY_CODES.get(proCode)) { return false; } //校验生日 - if (false == Validator.isBirthday(idCard.substring(6, 14))) { + if (false == Validator.isBirthday(idcard.substring(6, 14))) { return false; } // 前17位 - String code17 = idCard.substring(0, 17); + String code17 = idcard.substring(0, 17); // 第18位 - char code18 = Character.toLowerCase(idCard.charAt(17)); + char code18 = Character.toLowerCase(idcard.charAt(17)); if (ReUtil.isMatch(PatternPool.NUMBERS, code17)) { // 获取校验位 char val = getCheckCode18(code17); @@ -228,22 +229,22 @@ public class IdcardUtil { /** * 验证15位身份编码是否合法 * - * @param idCard 身份编码 + * @param idcard 身份编码 * @return 是否合法 */ - public static boolean isValidCard15(String idCard) { - if (CHINA_ID_MIN_LENGTH != idCard.length()) { + public static boolean isValidCard15(String idcard) { + if (CHINA_ID_MIN_LENGTH != idcard.length()) { return false; } - if (ReUtil.isMatch(PatternPool.NUMBERS, idCard)) { + if (ReUtil.isMatch(PatternPool.NUMBERS, idcard)) { // 省份 - String proCode = idCard.substring(0, 2); + String proCode = idcard.substring(0, 2); if (null == CITY_CODES.get(proCode)) { return false; } //校验生日(两位年份,补充为19XX) - return false != Validator.isBirthday("19" + idCard.substring(6, 12)); + return false != Validator.isBirthday("19" + idcard.substring(6, 12)); } else { return false; } @@ -252,24 +253,24 @@ public class IdcardUtil { /** * 验证10位身份编码是否合法 * - * @param idCard 身份编码 + * @param idcard 身份编码 * @return 身份证信息数组 *

    * [0] - 台湾、澳门、香港 [1] - 性别(男M,女F,未知N) [2] - 是否合法(合法true,不合法false) 若不是身份证件号码则返回null *

    */ - public static String[] isValidCard10(String idCard) { - if (StrUtil.isBlank(idCard)) { + public static String[] isValidCard10(String idcard) { + if (StrUtil.isBlank(idcard)) { return null; } String[] info = new String[3]; - String card = idCard.replaceAll("[()]", ""); - if (card.length() != 8 && card.length() != 9 && idCard.length() != 10) { + String card = idcard.replaceAll("[()]", ""); + if (card.length() != 8 && card.length() != 9 && idcard.length() != 10) { return null; } - if (idCard.matches("^[a-zA-Z][0-9]{9}$")) { // 台湾 + if (idcard.matches("^[a-zA-Z][0-9]{9}$")) { // 台湾 info[0] = "台湾"; - char char2 = idCard.charAt(1); + char char2 = idcard.charAt(1); if ('1' == char2) { info[1] = "M"; } else if ('2' == char2) { @@ -279,14 +280,14 @@ public class IdcardUtil { info[2] = "false"; return info; } - info[2] = isValidTWCard(idCard) ? "true" : "false"; - } else if (idCard.matches("^[157][0-9]{6}\\(?[0-9A-Z]\\)?$")) { // 澳门 + info[2] = isValidTWCard(idcard) ? "true" : "false"; + } else if (idcard.matches("^[157][0-9]{6}\\(?[0-9A-Z]\\)?$")) { // 澳门 info[0] = "澳门"; info[1] = "N"; - } else if (idCard.matches("^[A-Z]{1,2}[0-9]{6}\\(?[0-9A]\\)?$")) { // 香港 + } else if (idcard.matches("^[A-Z]{1,2}[0-9]{6}\\(?[0-9A]\\)?$")) { // 香港 info[0] = "香港"; info[1] = "N"; - info[2] = isValidHKCard(idCard) ? "true" : "false"; + info[2] = isValidHKCard(idcard) ? "true" : "false"; } else { return null; } @@ -296,20 +297,20 @@ public class IdcardUtil { /** * 验证台湾身份证号码 * - * @param idCard 身份证号码 + * @param idcard 身份证号码 * @return 验证码是否符合 */ - public static boolean isValidTWCard(String idCard) { - if (StrUtil.isEmpty(idCard)) { + public static boolean isValidTWCard(String idcard) { + if (StrUtil.isEmpty(idcard)) { return false; } - String start = idCard.substring(0, 1); + String start = idcard.substring(0, 1); Integer iStart = TW_FIRST_CODE.get(start); if (null == iStart) { return false; } - String mid = idCard.substring(1, 9); - String end = idCard.substring(9, 10); + String mid = idcard.substring(1, 9); + String end = idcard.substring(9, 10); int sum = iStart / 10 + (iStart % 10) * 9; final char[] chars = mid.toCharArray(); int iflag = 8; @@ -329,11 +330,11 @@ public class IdcardUtil { * 将身份证号码全部转换为数字,分别对应乘9-1相加的总和,整除11则证件号码有效 *

    * - * @param idCard 身份证号码 + * @param idcard 身份证号码 * @return 验证码是否符合 */ - public static boolean isValidHKCard(String idCard) { - String card = idCard.replaceAll("[()]", ""); + public static boolean isValidHKCard(String idcard) { + String card = idcard.replaceAll("[()]", ""); int sum; if (card.length() == 9) { sum = (Character.toUpperCase(card.charAt(0)) - 55) * 9 + (Character.toUpperCase(card.charAt(1)) - 55) * 8; @@ -343,7 +344,7 @@ public class IdcardUtil { } // 首字母A-Z,A表示1,以此类推 - char start = idCard.charAt(0); + char start = idcard.charAt(0); int iStart = start - 'A' + 1; String mid = card.substring(1, 7); String end = card.substring(7, 8); @@ -364,12 +365,12 @@ public class IdcardUtil { /** * 根据身份编号获取生日,只支持15或18位身份证号码 * - * @param idCard 身份编号 + * @param idcard 身份编号 * @return 生日(yyyyMMdd) * @see #getBirth(String) */ - public static String getBirthByIdCard(String idCard) { - return getBirth(idCard); + public static String getBirthByIdCard(String idcard) { + return getBirth(idcard); } /** @@ -404,120 +405,145 @@ public class IdcardUtil { /** * 根据身份编号获取年龄,只支持15或18位身份证号码 * - * @param idCard 身份编号 + * @param idcard 身份编号 * @return 年龄 */ - public static int getAgeByIdCard(String idCard) { - return getAgeByIdCard(idCard, DateUtil.date()); + public static int getAgeByIdCard(String idcard) { + return getAgeByIdCard(idcard, DateUtil.date()); } /** * 根据身份编号获取指定日期当时的年龄年龄,只支持15或18位身份证号码 * - * @param idCard 身份编号 + * @param idcard 身份编号 * @param dateToCompare 以此日期为界,计算年龄。 * @return 年龄 */ - public static int getAgeByIdCard(String idCard, Date dateToCompare) { - String birth = getBirthByIdCard(idCard); + public static int getAgeByIdCard(String idcard, Date dateToCompare) { + String birth = getBirthByIdCard(idcard); return DateUtil.age(DateUtil.parse(birth, "yyyyMMdd"), dateToCompare); } /** * 根据身份编号获取生日年,只支持15或18位身份证号码 * - * @param idCard 身份编号 + * @param idcard 身份编号 * @return 生日(yyyy) */ - public static Short getYearByIdCard(String idCard) { - final int len = idCard.length(); + public static Short getYearByIdCard(String idcard) { + final int len = idcard.length(); if (len < CHINA_ID_MIN_LENGTH) { return null; } else if (len == CHINA_ID_MIN_LENGTH) { - idCard = convert15To18(idCard); + idcard = convert15To18(idcard); } - return Short.valueOf(Objects.requireNonNull(idCard).substring(6, 10)); + return Short.valueOf(Objects.requireNonNull(idcard).substring(6, 10)); } /** * 根据身份编号获取生日月,只支持15或18位身份证号码 * - * @param idCard 身份编号 + * @param idcard 身份编号 * @return 生日(MM) */ - public static Short getMonthByIdCard(String idCard) { - final int len = idCard.length(); + public static Short getMonthByIdCard(String idcard) { + final int len = idcard.length(); if (len < CHINA_ID_MIN_LENGTH) { return null; } else if (len == CHINA_ID_MIN_LENGTH) { - idCard = convert15To18(idCard); + idcard = convert15To18(idcard); } - return Short.valueOf(Objects.requireNonNull(idCard).substring(10, 12)); + return Short.valueOf(Objects.requireNonNull(idcard).substring(10, 12)); } /** * 根据身份编号获取生日天,只支持15或18位身份证号码 * - * @param idCard 身份编号 + * @param idcard 身份编号 * @return 生日(dd) */ - public static Short getDayByIdCard(String idCard) { - final int len = idCard.length(); + public static Short getDayByIdCard(String idcard) { + final int len = idcard.length(); if (len < CHINA_ID_MIN_LENGTH) { return null; } else if (len == CHINA_ID_MIN_LENGTH) { - idCard = convert15To18(idCard); + idcard = convert15To18(idcard); } - return Short.valueOf(Objects.requireNonNull(idCard).substring(12, 14)); + return Short.valueOf(Objects.requireNonNull(idcard).substring(12, 14)); } /** * 根据身份编号获取性别,只支持15或18位身份证号码 * - * @param idCard 身份编号 + * @param idcard 身份编号 * @return 性别(1 : 男 , 0 : 女) */ - public static int getGenderByIdCard(String idCard) { - Assert.notBlank(idCard); - final int len = idCard.length(); + public static int getGenderByIdCard(String idcard) { + Assert.notBlank(idcard); + final int len = idcard.length(); if (len < CHINA_ID_MIN_LENGTH) { throw new IllegalArgumentException("ID Card length must be 15 or 18"); } if (len == CHINA_ID_MIN_LENGTH) { - idCard = convert15To18(idCard); + idcard = convert15To18(idcard); } - char sCardChar = Objects.requireNonNull(idCard).charAt(16); + char sCardChar = Objects.requireNonNull(idcard).charAt(16); return (sCardChar % 2 != 0) ? 1 : 0; } /** * 根据身份编号获取户籍省份,只支持15或18位身份证号码 * - * @param idCard 身份编码 - * @return 省级编码。 + * @param idcard 身份编码 + * @return 省份名称。 */ - public static String getProvinceByIdCard(String idCard) { - int len = idCard.length(); + public static String getProvinceByIdCard(String idcard) { + int len = idcard.length(); if (len == CHINA_ID_MIN_LENGTH || len == CHINA_ID_MAX_LENGTH) { - String sProvinNum = idCard.substring(0, 2); + String sProvinNum = idcard.substring(0, 2); return CITY_CODES.get(sProvinNum); } return null; } + /** + * 根据身份编号获取户籍省份,只支持15或18位身份证号码 + * + * @param idcard 身份编码 + * @return 市级编码。 + */ + public static String getCityCodeByIdCard(String idcard) { + int len = idcard.length(); + if (len == CHINA_ID_MIN_LENGTH || len == CHINA_ID_MAX_LENGTH) { + return idcard.substring(0, 5); + } + return null; + } + /** * 隐藏指定位置的几个身份证号数字为“*” * - * @param idCard 身份证号 + * @param idcard 身份证号 * @param startInclude 开始位置(包含) * @param endExclude 结束位置(不包含) * @return 隐藏后的身份证号码 * @see StrUtil#hide(CharSequence, int, int) * @since 3.2.2 */ - public static String hide(String idCard, int startInclude, int endExclude) { - return StrUtil.hide(idCard, startInclude, endExclude); + public static String hide(String idcard, int startInclude, int endExclude) { + return StrUtil.hide(idcard, startInclude, endExclude); + } + + /** + * 获取身份证信息,包括身份、城市代码、生日、性别等 + * + * @param idcard 15或18位身份证 + * @return {@link Idcard} + * @since 5.4.3 + */ + public static Idcard getIdcardInfo(String idcard){ + return new Idcard(idcard); } // ----------------------------------------------------------------------------------- Private method start @@ -584,4 +610,76 @@ public class IdcardUtil { return iSum; } // ----------------------------------------------------------------------------------- Private method end + + /** + * 身份证信息,包括身份、城市代码、生日、性别等 + * + * @author looly + * @since 5.4.3 + */ + public static class Idcard implements Serializable { + private static final long serialVersionUID = 1L; + + private final String provinceCode; + private final String cityCode; + private final DateTime birthDate; + private final Integer gender; + + /** + * 构造 + * + * @param idcard 身份证号码 + */ + public Idcard(String idcard) { + this.provinceCode = IdcardUtil.getProvinceByIdCard(idcard); + this.cityCode = IdcardUtil.getCityCodeByIdCard(idcard); + this.birthDate = IdcardUtil.getBirthDate(idcard); + this.gender = IdcardUtil.getGenderByIdCard(idcard); + } + + /** + * 获取省份代码 + * + * @return 省份代码 + */ + public String getProvinceCode() { + return this.provinceCode; + } + + /** + * 获取省份名称 + * + * @return 省份代码 + */ + public String getProvince() { + return CITY_CODES.get(this.provinceCode); + } + + /** + * 获取省份代码 + * + * @return 省份代码 + */ + public String getCityCode() { + return this.cityCode; + } + + /** + * 获得生日日期 + * + * @return 生日日期 + */ + public DateTime getBirthDate() { + return this.birthDate; + } + + /** + * 获取性别代号,性别(1 : 男 , 0 : 女) + * + * @return 性别(1 : 男 , 0 : 女) + */ + public Integer getGender() { + return this.gender; + } + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java index 6b2a8261a..14100d28a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java @@ -13,6 +13,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Array; +import java.math.BigDecimal; import java.util.*; /** @@ -22,14 +23,33 @@ import java.util.*; */ public class ObjectUtil { + /** + * 比较两个对象是否相等,此方法是 {@link #equal(Object, Object)}的别名方法。
    + * 相同的条件有两个,满足其一即可:
    + *
      + *
    1. obj1 == null && obj2 == null
    2. + *
    3. obj1.equals(obj2)
    4. + *
    5. 如果是BigDecimal比较,0 == obj1.compareTo(obj2)
    6. + *
    + * + * @param obj1 对象1 + * @param obj2 对象2 + * @return 是否相等 + * @see #equal(Object, Object) + * @since 5.4.3 + */ + public static boolean equals(Object obj1, Object obj2) { + return equal(obj1, obj2); + } + /** * 比较两个对象是否相等。
    * 相同的条件有两个,满足其一即可:
    *
      *
    1. obj1 == null && obj2 == null
    2. *
    3. obj1.equals(obj2)
    4. + *
    5. 如果是BigDecimal比较,0 == obj1.compareTo(obj2)
    6. *
    - * 1. obj1 == null && obj2 == null 2. obj1.equals(obj2) * * @param obj1 对象1 * @param obj2 对象2 @@ -37,7 +57,9 @@ public class ObjectUtil { * @see Objects#equals(Object, Object) */ public static boolean equal(Object obj1, Object obj2) { - // return (obj1 != null) ? (obj1.equals(obj2)) : (obj2 == null); + if(obj1 instanceof BigDecimal && obj2 instanceof BigDecimal){ + return NumberUtil.equals((BigDecimal)obj1, (BigDecimal)obj2); + } return Objects.equals(obj1, obj2); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/PageUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/PageUtil.java index b1e7081fc..59670070d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/PageUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/PageUtil.java @@ -160,9 +160,10 @@ public class PageUtil { * @return 分页条 */ public static int[] rainbow(int pageNo, int totalPage, int displayCount) { - boolean isEven = displayCount % 2 == 0; - int left = displayCount / 2; - int right = displayCount / 2; + // displayCount % 2 + boolean isEven = (displayCount & 1) == 0; + int left = displayCount >> 1; + int right = displayCount >> 1; int length = displayCount; if (isEven) { 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 6400dcca1..26b7d688d 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 @@ -123,7 +123,7 @@ public class RandomUtil { } /** - * 获得随机数[0, 2^32) + * 获得随机数int值 * * @return 随机数 */ diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ServiceLoaderUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ServiceLoaderUtil.java index 113703285..ae7b76d93 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ServiceLoaderUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ServiceLoaderUtil.java @@ -34,7 +34,7 @@ public class ServiceLoaderUtil { while (iterator.hasNext()) { try { return iterator.next(); - } catch (ServiceConfigurationError e) { + } catch (ServiceConfigurationError ignore) { // ignore } } @@ -76,7 +76,7 @@ public class ServiceLoaderUtil { * @return 服务接口实现列表 */ public static ServiceLoader load(Class clazz, ClassLoader loader) { - return ServiceLoader.load(clazz, loader); + return ServiceLoader.load(clazz, ObjectUtil.defaultIfNull(loader, ClassLoaderUtil.getClassLoader())); } /** @@ -101,6 +101,6 @@ public class ServiceLoaderUtil { * @since 5.4.2 */ public static List loadList(Class clazz, ClassLoader loader) { - return ListUtil.list(false, load(clazz)); + return ListUtil.list(false, load(clazz, loader)); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java index 867b54d42..b557c8ce8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java @@ -48,6 +48,7 @@ public class StrUtil { public static final char C_BRACKET_START = CharUtil.BRACKET_START; public static final char C_BRACKET_END = CharUtil.BRACKET_END; public static final char C_COLON = CharUtil.COLON; + public static final char C_AT = CharUtil.AT; public static final String SPACE = " "; public static final String TAB = " "; @@ -68,6 +69,7 @@ public class StrUtil { public static final String BRACKET_START = "["; public static final String BRACKET_END = "]"; public static final String COLON = ":"; + public static final String AT = "@"; public static final String HTML_NBSP = " "; public static final String HTML_AMP = "&"; @@ -599,19 +601,44 @@ public class StrUtil { * * @param str 被监测字符串 * @param prefix 开头字符串 - * @param isIgnoreCase 是否忽略大小写 + * @param ignoreCase 是否忽略大小写 * @return 是否以指定字符串开头 + * @since 5.4.3 */ - public static boolean startWith(CharSequence str, CharSequence prefix, boolean isIgnoreCase) { + public static boolean startWith(CharSequence str, CharSequence prefix, boolean ignoreCase) { + return startWith(str, prefix, ignoreCase, false); + } + + /** + * 是否以指定字符串开头
    + * 如果给定的字符串和开头字符串都为null则返回true,否则任意一个值为null返回false + * + * @param str 被监测字符串 + * @param prefix 开头字符串 + * @param ignoreCase 是否忽略大小写 + * @param ignoreEquals 是否忽略字符串相等的情况 + * @return 是否以指定字符串开头 + * @since 5.4.3 + */ + public static boolean startWith(CharSequence str, CharSequence prefix, boolean ignoreCase, boolean ignoreEquals) { if (null == str || null == prefix) { + if(false == ignoreEquals){ + return false; + } return null == str && null == prefix; } - if (isIgnoreCase) { - return str.toString().toLowerCase().startsWith(prefix.toString().toLowerCase()); + boolean isStartWith; + if (ignoreCase) { + isStartWith = str.toString().toLowerCase().startsWith(prefix.toString().toLowerCase()); } else { - return str.toString().startsWith(prefix.toString()); + isStartWith = str.toString().startsWith(prefix.toString()); } + + if(isStartWith){ + return (false == ignoreEquals) || (false == equals(str, prefix, ignoreCase)); + } + return false; } /** @@ -625,6 +652,17 @@ public class StrUtil { return startWith(str, prefix, false); } + /** + * 是否以指定字符串开头,忽略相等字符串的情况 + * + * @param str 被监测字符串 + * @param prefix 开头字符串 + * @return 是否以指定字符串开头并且两个字符串不相等 + */ + public static boolean startWithIgnoreEquals(CharSequence str, CharSequence prefix) { + return startWith(str, prefix, false, true); + } + /** * 是否以指定字符串开头,忽略大小写 * @@ -2301,13 +2339,13 @@ public class StrUtil { * 转义{}: format("this is \\{} for {}", "a", "b") =》 this is \{} for a
    * 转义\: format("this is \\\\{} for {}", "a", "b") =》 this is \a for b
    * - * @param template 文本模板,被替换的部分用 {} 表示 + * @param template 文本模板,被替换的部分用 {} 表示,如果模板为null,返回"null" * @param params 参数值 - * @return 格式化后的文本 + * @return 格式化后的文本,如果模板为null,返回"null" */ public static String format(CharSequence template, Object... params) { if (null == template) { - return null; + return NULL; } if (ArrayUtil.isEmpty(params) || isBlank(template)) { return template.toString(); @@ -2337,6 +2375,20 @@ public class StrUtil { * @return 格式化后的文本 */ public static String format(CharSequence template, Map map) { + return format(template, map, true); + } + + /** + * 格式化文本,使用 {varName} 占位
    + * map = {a: "aValue", b: "bValue"} format("{a} and {b}", map) ---=》 aValue and bValue + * + * @param template 文本模板,被替换的部分用 {key} 表示 + * @param map 参数值对 + * @param ignoreNull 是否忽略 {@code null} 值,忽略则 {@code null} 值对应的变量不被替换,否则替换为"" + * @return 格式化后的文本 + * @since 5.4.3 + */ + public static String format(CharSequence template, Map map, boolean ignoreNull) { if (null == template) { return null; } @@ -2348,9 +2400,10 @@ public class StrUtil { String value; for (Entry entry : map.entrySet()) { value = utf8Str(entry.getValue()); - if (null != value) { - template2 = replace(template2, "{" + entry.getKey() + "}", value); + if (null == value && ignoreNull) { + continue; } + template2 = replace(template2, "{" + entry.getKey() + "}", value); } return template2; } @@ -2633,7 +2686,7 @@ public class StrUtil { } final int length = str.length(); - final StringBuilder sb = new StringBuilder(); + final StrBuilder sb = new StrBuilder(); char c; for (int i = 0; i < length; i++) { c = str.charAt(i); @@ -2642,12 +2695,12 @@ public class StrUtil { // 遇到大写字母处理 final Character nextChar = (i < str.length() - 1) ? str.charAt(i + 1) : null; if (null != preChar && Character.isUpperCase(preChar)) { - // 前一个字符为大写,则按照一个词对待 + // 前一个字符为大写,则按照一个词对待,例如AB sb.append(c); - } else if (null != nextChar && Character.isUpperCase(nextChar)) { - // 后一个为大写字母,按照一个词对待 + } else if (null != nextChar && (false == Character.isLowerCase(nextChar))) { + // 后一个为非小写字母,按照一个词对待 if (null != preChar && symbol != preChar) { - // 前一个是非大写时按照新词对待,加连接符 + // 前一个是非大写时按照新词对待,加连接符,例如xAB sb.append(symbol); } sb.append(c); @@ -2660,8 +2713,11 @@ public class StrUtil { sb.append(Character.toLowerCase(c)); } } else { - if (sb.length() > 0 && Character.isUpperCase(sb.charAt(sb.length() - 1)) && symbol != c) { - // 当结果中前一个字母为大写,当前为小写,说明此字符为新词开始(连接符也表示新词) + if (symbol != c + && sb.length() > 0 + && Character.isUpperCase(sb.charAt(-1)) + && Character.isLowerCase(c)) { + // 当结果中前一个字母为大写,当前为小写(非数字或字符),说明此字符为新词开始(连接符也表示新词) sb.append(symbol); } // 小写或符号 @@ -3445,7 +3501,7 @@ public class StrUtil { * @return 位置 */ public static int indexOf(final CharSequence str, char searchChar, int start, int end) { - if(isEmpty(str)){ + if (isEmpty(str)) { return INDEX_NOT_FOUND; } final int len = str.length(); @@ -4360,12 +4416,12 @@ public class StrUtil { * @param strs 多个元素 * @param 元素类型 * @return 第一个非空元素,如果给定的数组为空或者都为空,返回{@code null} - * @since 5.4.1 * @see #isNotEmpty(CharSequence) + * @since 5.4.1 */ @SuppressWarnings("unchecked") public T firstNonEmpty(T... strs) { - return ArrayUtil.firstMatch(StrUtil::isNotEmpty, strs); + return ArrayUtil.firstMatch(StrUtil::isNotEmpty, strs); } /** @@ -4374,8 +4430,8 @@ public class StrUtil { * @param strs 多个元素 * @param 元素类型 * @return 第一个非空元素,如果给定的数组为空或者都为空,返回{@code null} - * @since 5.4.1 * @see #isNotBlank(CharSequence) + * @since 5.4.1 */ @SuppressWarnings("unchecked") public T firstNonBlank(T... strs) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 78e7a8d94..8f61aeb5d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -483,13 +483,7 @@ public class URLUtil { * @throws UtilException 包装URISyntaxException */ public static String getPath(String uriStr) { - URI uri; - try { - uri = new URI(uriStr); - } catch (URISyntaxException e) { - throw new UtilException(e); - } - return uri.getPath(); + return toURI(uriStr).getPath(); } /** @@ -509,7 +503,7 @@ public class URLUtil { String path = null; try { // URL对象的getPath方法对于包含中文或空格的问题 - path = URLUtil.toURI(url).getPath(); + path = toURI(url).getPath(); } catch (UtilException e) { // ignore } @@ -569,7 +563,7 @@ public class URLUtil { location = encode(location); } try { - return new URI(location); + return new URI(StrUtil.trim(location)); } catch (URISyntaxException e) { throw new UtilException(e); } 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 582a5a539..a78212d1c 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 @@ -1116,6 +1116,8 @@ public class ZipUtil { * @since 5.0.5 */ private static File buildFile(File outFile, String fileName) { + // 替换Windows路径分隔符为Linux路径分隔符,便于统一处理 + fileName = fileName.replace('\\', '/'); if (false == FileUtil.isWindows() // 检查文件名中是否包含"/",不考虑以"/"结尾的情况 && fileName.lastIndexOf(CharUtil.SLASH, fileName.length() - 2) > 0) { diff --git a/hutool-core/src/test/java/cn/hutool/core/comparator/CompareUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/comparator/CompareUtilTest.java index b1378be80..d1470278d 100644 --- a/hutool-core/src/test/java/cn/hutool/core/comparator/CompareUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/comparator/CompareUtilTest.java @@ -1,8 +1,11 @@ package cn.hutool.core.comparator; +import cn.hutool.core.collection.ListUtil; import org.junit.Assert; import org.junit.Test; +import java.util.List; + public class CompareUtilTest { @Test @@ -13,4 +16,20 @@ public class CompareUtilTest { compare = CompareUtil.compare(null, "a", false); Assert.assertTrue(compare < 0); } + + @Test + public void comparingPinyin() { + List list = ListUtil.toList("成都", "北京", "上海", "深圳"); + + List ascendingOrderResult = ListUtil.of("北京", "成都", "上海", "深圳"); + List descendingOrderResult = ListUtil.of("深圳", "上海", "成都", "北京"); + + // 正序 + list.sort(CompareUtil.comparingPinyin(e -> e)); + Assert.assertEquals(list, ascendingOrderResult); + + // 反序 + list.sort(CompareUtil.comparingPinyin(e -> e, true)); + Assert.assertEquals(list, descendingOrderResult); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/comparator/VersionComparatorTest.java b/hutool-core/src/test/java/cn/hutool/core/comparator/VersionComparatorTest.java index cbfd95da0..976ed2240 100644 --- a/hutool-core/src/test/java/cn/hutool/core/comparator/VersionComparatorTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/comparator/VersionComparatorTest.java @@ -46,4 +46,11 @@ public class VersionComparatorTest { int compare = VersionComparator.INSTANCE.compare("V0.0.20170102", "V0.0.20170101"); Assert.assertTrue(compare > 0); } + + @Test + public void equalsTest(){ + VersionComparator first = new VersionComparator(); + VersionComparator other = new VersionComparator(); + Assert.assertFalse(first.equals(other)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToArrayTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToArrayTest.java index 8670e4c83..fe7fa68f0 100644 --- a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToArrayTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToArrayTest.java @@ -1,16 +1,15 @@ package cn.hutool.core.convert; -import java.io.File; -import java.net.URL; -import java.util.ArrayList; - -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.convert.impl.ArrayConverter; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Console; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.File; +import java.net.URL; +import java.util.ArrayList; /** * 类型转换工具单元测试
    @@ -35,6 +34,15 @@ public class ConvertToArrayTest { Integer[] intArray2 = Convert.toIntArray(c); Assert.assertArrayEquals(intArray2, new Integer[]{1,2,3,4,5}); } + + @Test + public void toIntArrayTestIgnoreComponentErrorTest() { + String[] b = { "a", "1" }; + + final ArrayConverter arrayConverter = new ArrayConverter(Integer[].class, true); + Integer[] integerArray = (Integer[]) arrayConverter.convert(b, null); + Assert.assertArrayEquals(integerArray, new Integer[]{null, 1}); + } @Test public void toLongArrayTest() { diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToNumberTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToNumberTest.java index f8b30cf26..a1600c664 100644 --- a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToNumberTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToNumberTest.java @@ -5,6 +5,7 @@ import cn.hutool.core.date.DateUtil; import org.junit.Assert; import org.junit.Test; +import java.math.BigDecimal; import java.util.concurrent.atomic.AtomicLong; public class ConvertToNumberTest { @@ -31,4 +32,13 @@ public class ConvertToNumberTest { assert date != null; Assert.assertEquals(date.getTime(), dateLong.longValue()); } + + @Test + public void toBigDecimalTest(){ + BigDecimal bigDecimal = Convert.toBigDecimal("1.1f"); + Assert.assertEquals(1.1f, bigDecimal.floatValue(), 1); + + bigDecimal = Convert.toBigDecimal("1L"); + Assert.assertEquals(1L, bigDecimal.longValue()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/BetweenFormaterTest.java b/hutool-core/src/test/java/cn/hutool/core/date/BetweenFormaterTest.java index 9b43ec3fe..5374d596f 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/BetweenFormaterTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/BetweenFormaterTest.java @@ -1,10 +1,9 @@ package cn.hutool.core.date; +import cn.hutool.core.date.BetweenFormater.Level; import org.junit.Assert; import org.junit.Test; -import cn.hutool.core.date.BetweenFormater.Level; - public class BetweenFormaterTest { @Test 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 9d328a3eb..8d8d00213 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 @@ -819,12 +819,4 @@ public class DateUtilTest { final DateTime parse = DateUtil.parse(dt); Assert.assertEquals("2020-06-03 12:32:12", parse.toString()); } - - @Test - public void getBetweenMonthsTest() { - List months1 = DateUtil.getBetweenMonths(new Date(), new Date()); - Assert.assertEquals(1, months1.size()); - List months = DateUtil.getBetweenMonths(DateUtil.parse("2020-05-08 3:12:3"), new Date()); - Assert.assertEquals(5, months.size()); - } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index aee6bd293..a8db0580a 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -53,6 +53,24 @@ public class LocalDateTimeUtilTest { Assert.assertEquals("2020-01-23T12:23:56", localDateTime.toString()); } + @Test + public void parseTest5() { + LocalDateTime localDateTime = LocalDateTimeUtil.parse("19940121183604", "yyyyMMddHHmmss"); + Assert.assertEquals("1994-01-21T18:36:04", localDateTime.toString()); + } + + @Test + public void parseTest6() { + LocalDateTime localDateTime = LocalDateTimeUtil.parse("19940121183604682", "yyyyMMddHHmmssSSS"); + Assert.assertEquals("1994-01-21T18:36:04.682", localDateTime.toString()); + + localDateTime = LocalDateTimeUtil.parse("1994012118360468", "yyyyMMddHHmmssSS"); + Assert.assertEquals("1994-01-21T18:36:04.680", localDateTime.toString()); + + localDateTime = LocalDateTimeUtil.parse("199401211836046", "yyyyMMddHHmmssS"); + Assert.assertEquals("1994-01-21T18:36:04.600", localDateTime.toString()); + } + @Test public void parseDateTest() { LocalDate localDate = LocalDateTimeUtil.parseDate("2020-01-23"); diff --git a/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java index b27be1f37..9243a34a1 100644 --- a/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java @@ -84,7 +84,7 @@ public class ImgUtilTest { @Test @Ignore public void sliceByRowsAndColsTest() { - ImgUtil.sliceByRowsAndCols(FileUtil.file("e:/pic/1.png"), FileUtil.file("e:/pic/dest"), 10, 10); + ImgUtil.sliceByRowsAndCols(FileUtil.file("d:/test/logo.jpg"), FileUtil.file("d:/test/dest"), 1, 5); } @Test diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/ConsoleTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/ConsoleTest.java index 491a332ef..c9a64371e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/ConsoleTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/ConsoleTest.java @@ -21,6 +21,12 @@ public class ConsoleTest { Console.log("This is Console log for {}.", "test"); } + + @Test + public void logTest2(){ + Console.log("a", "b", "c"); + Console.log((Object) "a", "b", "c"); + } @Test public void printTest(){ @@ -29,6 +35,12 @@ public class ConsoleTest { Console.log("This is Console print for {}.", "test"); } + + @Test + public void printTest2(){ + Console.print("a", "b", "c"); + Console.print((Object) "a", "b", "c"); + } @Test public void errorTest(){ @@ -39,6 +51,12 @@ public class ConsoleTest { Console.error("This is Console error for {}.", "test"); } + + @Test + public void errorTest2(){ + Console.error("a", "b", "c"); + Console.error((Object) "a", "b", "c"); + } @Test @Ignore diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/DictTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/DictTest.java index 7f86f7706..297672982 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/DictTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/DictTest.java @@ -30,4 +30,17 @@ public class DictTest { Assert.assertEquals(1, dict.get("A")); Assert.assertEquals(1, dict.get("a")); } + + @Test + public void ofTest(){ + Dict dict = Dict.of( + "RED", "#FF0000", + "GREEN", "#00FF00", + "BLUE", "#0000FF" + ); + + Assert.assertEquals("#FF0000", dict.get("RED")); + Assert.assertEquals("#00FF00", dict.get("GREEN")); + Assert.assertEquals("#0000FF", dict.get("BLUE")); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/intern/InternUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/intern/InternUtilTest.java new file mode 100644 index 000000000..3c724a3b5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/intern/InternUtilTest.java @@ -0,0 +1,24 @@ +package cn.hutool.core.lang.intern; + +import cn.hutool.core.util.RandomUtil; +import org.junit.Assert; +import org.junit.Test; + +public class InternUtilTest { + + /** + * 检查规范字符串是否相同 + */ + @SuppressWarnings("StringOperationCanBeSimplified") + @Test + public void weakTest(){ + final Interner interner = InternUtil.createWeakInterner(); + String a1 = RandomUtil.randomString(RandomUtil.randomInt(100)); + String a2 = new String(a1); + + Assert.assertNotSame(a1, a2); + + Assert.assertSame(interner.intern(a1), interner.intern(a2)); + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/math/CalculatorTest.java b/hutool-core/src/test/java/cn/hutool/core/math/CalculatorTest.java new file mode 100644 index 000000000..553c1691e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/math/CalculatorTest.java @@ -0,0 +1,19 @@ +package cn.hutool.core.math; + +import org.junit.Assert; +import org.junit.Test; + +public class CalculatorTest { + + @Test + public void conversationTest(){ + final double conversion = Calculator.conversion("(0*1--3)-5/-4-(3*(-2.13))"); + Assert.assertEquals(10.64, conversion, 2); + } + + @Test + public void conversationTest2(){ + final double conversion = Calculator.conversion("77 * 12"); + Assert.assertEquals(924.0, conversion, 2); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 8f910239b..235e44e62 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -1,5 +1,6 @@ package cn.hutool.core.net; +import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.net.url.UrlQuery; import org.junit.Assert; import org.junit.Test; @@ -16,4 +17,13 @@ public class UrlQueryTest { Assert.assertEquals("111==", parse.get("b")); Assert.assertEquals("a=1&b=111==", parse.toString()); } + + @Test + public void ofHttpWithoutEncodeTest(){ + // charset为null表示不做编码 + String url = "https://img-cloud.voc.com.cn/140/2020/09/03/c3d41b93e0d32138574af8e8b50928b376ca5ba61599127028157.png?imageMogr2/auto-orient/thumbnail/500&pid=259848"; + final UrlBuilder urlBuilder = UrlBuilder.ofHttpWithoutEncode(url); + final String queryStr = urlBuilder.getQueryStr(); + Assert.assertEquals("imageMogr2/auto-orient/thumbnail/500&pid=259848", queryStr); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/EscapeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/EscapeUtilTest.java index 238667630..76cb16472 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/EscapeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/EscapeUtilTest.java @@ -37,4 +37,11 @@ public class EscapeUtilTest { String unescape = EscapeUtil.unescape(escape); Assert.assertEquals(str, unescape); } + + @Test + public void escapeSinleQuotesTest(){ + String str = "'some text with single quotes'"; + final String s = EscapeUtil.escapeHtml4(str); + Assert.assertEquals(str, s); + } } 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 a022b28ab..5791433ff 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 @@ -8,9 +8,8 @@ import java.util.List; /** * 字符串工具类单元测试 - * - * @author Looly * + * @author Looly */ public class StrUtilTest { @@ -19,14 +18,14 @@ public class StrUtilTest { String blank = "   "; Assert.assertTrue(StrUtil.isBlank(blank)); } - + @Test public void trimTest() { String blank = " 哈哈  "; String trim = StrUtil.trim(blank); Assert.assertEquals("哈哈", trim); } - + @Test public void cleanBlankTest() { // 包含:制表符、英文空格、不间断空白符、全角空格 @@ -39,7 +38,7 @@ public class StrUtilTest { public void cutTest() { String str = "aaabbbcccdddaadfdfsdfsdf0"; String[] cut = StrUtil.cut(str, 4); - Assert.assertArrayEquals(new String[] { "aaab", "bbcc", "cddd", "aadf", "dfsd", "fsdf", "0" }, cut); + Assert.assertArrayEquals(new String[]{"aaab", "bbcc", "cddd", "aadf", "dfsd", "fsdf", "0"}, cut); } @Test @@ -59,20 +58,20 @@ public class StrUtilTest { public void splitToLongTest() { String str = "1,2,3,4, 5"; long[] longArray = StrUtil.splitToLong(str, ','); - Assert.assertArrayEquals(new long[] { 1, 2, 3, 4, 5 }, longArray); + Assert.assertArrayEquals(new long[]{1, 2, 3, 4, 5}, longArray); longArray = StrUtil.splitToLong(str, ","); - Assert.assertArrayEquals(new long[] { 1, 2, 3, 4, 5 }, longArray); + Assert.assertArrayEquals(new long[]{1, 2, 3, 4, 5}, longArray); } @Test public void splitToIntTest() { String str = "1,2,3,4, 5"; int[] intArray = StrUtil.splitToInt(str, ','); - Assert.assertArrayEquals(new int[] { 1, 2, 3, 4, 5 }, intArray); + Assert.assertArrayEquals(new int[]{1, 2, 3, 4, 5}, intArray); intArray = StrUtil.splitToInt(str, ","); - Assert.assertArrayEquals(new int[] { 1, 2, 3, 4, 5 }, intArray); + Assert.assertArrayEquals(new int[]{1, 2, 3, 4, 5}, intArray); } @Test @@ -80,7 +79,7 @@ public class StrUtilTest { String template = "你好,我是{name},我的电话是:{phone}"; String result = StrUtil.format(template, Dict.create().set("name", "张三").set("phone", "13888881111")); Assert.assertEquals("你好,我是张三,我的电话是:13888881111", result); - + String result2 = StrUtil.format(template, Dict.create().set("name", "张三").set("phone", null)); Assert.assertEquals("你好,我是张三,我的电话是:{phone}", result2); } @@ -102,11 +101,11 @@ public class StrUtilTest { str = "abcd123"; strip = StrUtil.strip(str, null, "567"); Assert.assertEquals("abcd123", strip); - - Assert.assertEquals("", StrUtil.strip("a","a")); - Assert.assertEquals("", StrUtil.strip("a","a", "b")); + + Assert.assertEquals("", StrUtil.strip("a", "a")); + Assert.assertEquals("", StrUtil.strip("a", "a", "b")); } - + @Test public void stripIgnoreCaseTest() { String str = "abcd123"; @@ -144,7 +143,7 @@ public class StrUtilTest { Assert.assertEquals(2, StrUtil.indexOfIgnoreCase("aabaabaa", "", 2)); Assert.assertEquals(-1, StrUtil.indexOfIgnoreCase("abc", "", 9)); } - + @Test public void lastIndexOfTest() { String a = "aabbccddcc"; @@ -180,17 +179,17 @@ public class StrUtilTest { String result = StrUtil.replace("123", "2", "3"); Assert.assertEquals("133", result); } - + @Test public void replaceTest3() { String result = StrUtil.replace(",abcdef,", ",", "|"); Assert.assertEquals("|abcdef|", result); } - + @Test public void replaceTest4() { String a = "1039"; - String result = StrUtil.padPre(a,8,"0"); //在字符串1039前补4个0 + String result = StrUtil.padPre(a, 8, "0"); //在字符串1039前补4个0 Assert.assertEquals("00001039", result); } @@ -228,7 +227,7 @@ public class StrUtilTest { String rightAnswer = StrUtil.subByCodePoint(test, 0, 3); Assert.assertEquals("\uD83E\uDD14\uD83D\uDC4D\uD83C\uDF53", rightAnswer); } - + @Test public void subBeforeTest() { String a = "abcderghigh"; @@ -238,14 +237,14 @@ public class StrUtilTest { Assert.assertEquals("abc", pre); pre = StrUtil.subBefore(a, 'a', false); Assert.assertEquals("", pre); - + //找不到返回原串 pre = StrUtil.subBefore(a, 'k', false); Assert.assertEquals(a, pre); pre = StrUtil.subBefore(a, 'k', true); Assert.assertEquals(a, pre); } - + @Test public void subAfterTest() { String a = "abcderghigh"; @@ -255,7 +254,7 @@ public class StrUtilTest { Assert.assertEquals("erghigh", pre); pre = StrUtil.subAfter(a, 'h', true); Assert.assertEquals("", pre); - + //找不到字符返回空串 pre = StrUtil.subAfter(a, 'k', false); Assert.assertEquals("", pre); @@ -306,20 +305,20 @@ public class StrUtilTest { result = StrUtil.move(str, 7, 12, 0); Assert.assertEquals("aaaaaaa22222bbbbbbb", result); } - + @Test public void removePrefixIgnorecaseTest() { String a = "aaabbb"; String prefix = "aaa"; Assert.assertEquals("bbb", StrUtil.removePrefixIgnoreCase(a, prefix)); - + prefix = "AAA"; Assert.assertEquals("bbb", StrUtil.removePrefixIgnoreCase(a, prefix)); - + prefix = "AAABBB"; Assert.assertEquals("", StrUtil.removePrefixIgnoreCase(a, prefix)); } - + @Test public void maxLengthTest() { String text = "我是一段正文,很长的正文,需要截取的正文"; @@ -330,13 +329,13 @@ public class StrUtilTest { str = StrUtil.maxLength(text, 50); Assert.assertEquals(text, str); } - + @Test public void toCamelCaseTest() { String str = "Table_Test_Of_day"; String result = StrUtil.toCamelCase(str); Assert.assertEquals("tableTestOfDay", result); - + String str1 = "TableTestOfDay"; String result1 = StrUtil.toCamelCase(str1); Assert.assertEquals("TableTestOfDay", result1); @@ -344,30 +343,20 @@ public class StrUtilTest { String abc1d = StrUtil.toCamelCase("abc_1d"); Assert.assertEquals("abc1d", abc1d); } - + @Test public void toUnderLineCaseTest() { - String str = "Table_Test_Of_day"; - String result = StrUtil.toUnderlineCase(str); - Assert.assertEquals("table_test_of_day", result); - - String str1 = "_Table_Test_Of_day_"; - String result1 = StrUtil.toUnderlineCase(str1); - Assert.assertEquals("_table_test_of_day_", result1); - - String str2 = "_Table_Test_Of_DAY_"; - String result2 = StrUtil.toUnderlineCase(str2); - Assert.assertEquals("_table_test_of_DAY_", result2); - - String str3 = "_TableTestOfDAYtoday"; - String result3 = StrUtil.toUnderlineCase(str3); - Assert.assertEquals("_table_test_of_DAY_today", result3); - - String str4 = "HelloWorld_test"; - String result4 = StrUtil.toUnderlineCase(str4); - Assert.assertEquals("hello_world_test", result4); + Dict.create() + .set("Table_Test_Of_day", "table_test_of_day") + .set("_Table_Test_Of_day_", "_table_test_of_day_") + .set("_Table_Test_Of_DAY_", "_table_test_of_DAY_") + .set("_TableTestOfDAYtoday", "_table_test_of_DAY_today") + .set("HelloWorld_test", "hello_world_test") + .set("H2", "H2") + .set("H#case", "H#case") + .forEach((key, value) -> Assert.assertEquals(value, StrUtil.toUnderlineCase(key))); } - + @Test public void containsAnyTest() { //字符 @@ -377,7 +366,7 @@ public class StrUtilTest { Assert.assertFalse(containsAny); containsAny = StrUtil.containsAny("aaabbbccc", 'd', 'c'); Assert.assertTrue(containsAny); - + //字符串 containsAny = StrUtil.containsAny("aaabbbccc", "a", "d"); Assert.assertTrue(containsAny); @@ -386,7 +375,7 @@ public class StrUtilTest { containsAny = StrUtil.containsAny("aaabbbccc", "d", "c"); Assert.assertTrue(containsAny); } - + @Test public void centerTest() { Assert.assertNull(StrUtil.center(null, 10)); @@ -396,24 +385,24 @@ public class StrUtilTest { Assert.assertEquals("abcd", StrUtil.center("abcd", 2)); Assert.assertEquals(" a ", StrUtil.center("a", 4)); } - + @Test public void padPreTest() { Assert.assertNull(StrUtil.padPre(null, 10, ' ')); Assert.assertEquals("001", StrUtil.padPre("1", 3, '0')); Assert.assertEquals("12", StrUtil.padPre("123", 2, '0')); - + Assert.assertNull(StrUtil.padPre(null, 10, "AA")); Assert.assertEquals("AB1", StrUtil.padPre("1", 3, "ABC")); Assert.assertEquals("12", StrUtil.padPre("123", 2, "ABC")); } - + @Test public void padAfterTest() { Assert.assertNull(StrUtil.padAfter(null, 10, ' ')); Assert.assertEquals("100", StrUtil.padAfter("1", 3, '0')); Assert.assertEquals("23", StrUtil.padAfter("123", 2, '0')); - + Assert.assertNull(StrUtil.padAfter(null, 10, "ABC")); Assert.assertEquals("1AB", StrUtil.padAfter("1", 3, "ABC")); Assert.assertEquals("23", StrUtil.padAfter("123", 2, "ABC")); @@ -421,13 +410,13 @@ public class StrUtilTest { @Test public void subBetweenAllTest() { - Assert.assertArrayEquals(new String[]{"yz","abc"},StrUtil.subBetweenAll("saho[yz]fdsadp[abc]a","[","]")); - Assert.assertArrayEquals(new String[]{"abc"}, StrUtil.subBetweenAll("saho[yzfdsadp[abc]a]","[","]")); - Assert.assertArrayEquals(new String[]{"abc", "abc"}, StrUtil.subBetweenAll("yabczyabcz","y","z")); - Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll(null,"y","z")); - Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("","y","z")); - Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("abc",null,"z")); - Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("abc","y",null)); + Assert.assertArrayEquals(new String[]{"yz", "abc"}, StrUtil.subBetweenAll("saho[yz]fdsadp[abc]a", "[", "]")); + Assert.assertArrayEquals(new String[]{"abc"}, StrUtil.subBetweenAll("saho[yzfdsadp[abc]a]", "[", "]")); + Assert.assertArrayEquals(new String[]{"abc", "abc"}, StrUtil.subBetweenAll("yabczyabcz", "y", "z")); + Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll(null, "y", "z")); + Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("", "y", "z")); + Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("abc", null, "z")); + Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("abc", "y", null)); } @Test @@ -436,15 +425,15 @@ public class StrUtilTest { String src1 = "/* \n* hutool */ asdas /* \n* hutool */"; String src2 = "/ * hutool */ asdas / * hutool */"; - String[] results1 = StrUtil.subBetweenAll(src1,"/**","*/"); + String[] results1 = StrUtil.subBetweenAll(src1, "/**", "*/"); Assert.assertEquals(0, results1.length); - String[] results2 = StrUtil.subBetweenAll(src2,"/*","*/"); + String[] results2 = StrUtil.subBetweenAll(src2, "/*", "*/"); Assert.assertEquals(0, results2.length); } @Test - public void briefTest(){ + public void briefTest() { String str = RandomUtil.randomString(1000); int maxLength = RandomUtil.randomInt(1000); String brief = StrUtil.brief(str, maxLength); @@ -460,11 +449,20 @@ public class StrUtilTest { } @Test - public void wrapAllTest(){ + public void wrapAllTest() { String[] strings = StrUtil.wrapAll("`", "`", StrUtil.splitToArray("1,2,3,4", ',')); Assert.assertEquals("[`1`, `2`, `3`, `4`]", StrUtil.utf8Str(strings)); strings = StrUtil.wrapAllWithPair("`", StrUtil.splitToArray("1,2,3,4", ',')); Assert.assertEquals("[`1`, `2`, `3`, `4`]", StrUtil.utf8Str(strings)); } + + @Test + public void startWithTest(){ + String a = "123"; + String b = "123"; + + Assert.assertTrue(StrUtil.startWith(a, b)); + Assert.assertFalse(StrUtil.startWithIgnoreEquals(a, b)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/URLUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/URLUtilTest.java index 2e925c164..1381a7fe8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/URLUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/URLUtilTest.java @@ -84,4 +84,11 @@ public class URLUtilTest { String encode2 = URLUtil.encodeQuery(body); Assert.assertEquals("366466+-+%E5%89%AF%E6%9C%AC.jpg", encode2); } + + @Test + public void getPathTest(){ + String url = " http://www.aaa.bbb/search?scope=ccc&q=ddd"; + String path = URLUtil.getPath(url); + Assert.assertEquals("/search", path); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 186b98045..e1fb492f9 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 71f466f32..e84f71c45 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java index e6e88ffb0..c73d91dec 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java @@ -1,6 +1,5 @@ package cn.hutool.crypto; -import cn.hutool.core.util.HexUtil; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.crypto.params.ECDomainParameters; @@ -16,8 +15,6 @@ import org.bouncycastle.math.ec.ECCurve; import java.io.InputStream; import java.math.BigInteger; -import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; @@ -83,12 +80,7 @@ public class BCUtil { // 根据曲线恢复公钥格式 final ECNamedCurveSpec ecSpec = new ECNamedCurveSpec(curveName, curve, x9ECParameters.getG(), x9ECParameters.getN()); - - try { - return KeyUtil.getKeyFactory("EC").generatePublic(new ECPublicKeySpec(point, ecSpec)); - } catch (GeneralSecurityException e) { - throw new CryptoException(e); - } + return KeyUtil.generatePublicKey("EC", new ECPublicKeySpec(point, ecSpec)); } /** @@ -141,27 +133,17 @@ public class BCUtil { * @since 5.2.0 */ public static AsymmetricKeyParameter toParams(Key key) { - try { - if (key instanceof PrivateKey) { - return ECUtil.generatePrivateKeyParameter((PrivateKey) key); - } else if (key instanceof PublicKey) { - return ECUtil.generatePublicKeyParameter((PublicKey) key); - } - } catch (InvalidKeyException e) { - throw new CryptoException(e); - } - - return null; + return ECKeyUtil.toParams(key); } /** * 转换为 ECPrivateKeyParameters * - * @param dHex 私钥d值16进制字符串 + * @param d 私钥d值 * @return ECPrivateKeyParameters */ - public static ECPrivateKeyParameters toSm2Params(String dHex) { - return toSm2Params(HexUtil.toBigInteger(dHex)); + public static ECPrivateKeyParameters toSm2Params(String d) { + return ECKeyUtil.toSm2PrivateParams(d); } /** @@ -172,7 +154,7 @@ public class BCUtil { * @return ECPrivateKeyParameters */ public static ECPrivateKeyParameters toParams(String dHex, ECDomainParameters domainParameters) { - return toParams(new BigInteger(dHex, 16), domainParameters); + return ECKeyUtil.toPrivateParams(dHex, domainParameters); } /** @@ -182,7 +164,7 @@ public class BCUtil { * @return ECPrivateKeyParameters */ public static ECPrivateKeyParameters toSm2Params(byte[] d) { - return toSm2Params(new BigInteger(d)); + return ECKeyUtil.toSm2PrivateParams(d); } /** @@ -193,7 +175,7 @@ public class BCUtil { * @return ECPrivateKeyParameters */ public static ECPrivateKeyParameters toParams(byte[] d, ECDomainParameters domainParameters) { - return toParams(new BigInteger(d), domainParameters); + return ECKeyUtil.toPrivateParams(d, domainParameters); } /** @@ -203,7 +185,7 @@ public class BCUtil { * @return ECPrivateKeyParameters */ public static ECPrivateKeyParameters toSm2Params(BigInteger d) { - return toParams(d, SmUtil.SM2_DOMAIN_PARAMS); + return ECKeyUtil.toSm2PrivateParams(d); } /** @@ -214,10 +196,7 @@ public class BCUtil { * @return ECPrivateKeyParameters */ public static ECPrivateKeyParameters toParams(BigInteger d, ECDomainParameters domainParameters) { - if(null == d){ - return null; - } - return new ECPrivateKeyParameters(d, domainParameters); + return ECKeyUtil.toPrivateParams(d, domainParameters); } /** @@ -229,10 +208,7 @@ public class BCUtil { * @return ECPublicKeyParameters */ public static ECPublicKeyParameters toParams(BigInteger x, BigInteger y, ECDomainParameters domainParameters) { - if(null == x || null == y){ - return null; - } - return toParams(x.toByteArray(), y.toByteArray(), domainParameters); + return ECKeyUtil.toPublicParams(x, y, domainParameters); } /** @@ -243,7 +219,7 @@ public class BCUtil { * @return ECPublicKeyParameters */ public static ECPublicKeyParameters toSm2Params(String xHex, String yHex) { - return toParams(xHex, yHex, SmUtil.SM2_DOMAIN_PARAMS); + return ECKeyUtil.toSm2PublicParams(xHex, yHex); } /** @@ -255,7 +231,7 @@ public class BCUtil { * @return ECPublicKeyParameters */ public static ECPublicKeyParameters toParams(String xHex, String yHex, ECDomainParameters domainParameters) { - return toParams(HexUtil.decodeHex(xHex), HexUtil.decodeHex(yHex), domainParameters); + return ECKeyUtil.toPublicParams(xHex, yHex, domainParameters); } /** @@ -266,7 +242,7 @@ public class BCUtil { * @return ECPublicKeyParameters */ public static ECPublicKeyParameters toSm2Params(byte[] xBytes, byte[] yBytes) { - return toParams(xBytes, yBytes, SmUtil.SM2_DOMAIN_PARAMS); + return ECKeyUtil.toSm2PublicParams(xBytes, yBytes); } /** @@ -278,13 +254,7 @@ public class BCUtil { * @return ECPublicKeyParameters */ public static ECPublicKeyParameters toParams(byte[] xBytes, byte[] yBytes, ECDomainParameters domainParameters) { - if(null == xBytes || null == yBytes){ - return null; - } - final ECCurve curve = domainParameters.getCurve(); - final int curveLength = getCurveLength(curve); - final byte[] encodedPubKey = encodePoint(xBytes, yBytes, curveLength); - return new ECPublicKeyParameters(curve.decodePoint(encodedPubKey), domainParameters); + return ECKeyUtil.toPublicParams(xBytes, yBytes, domainParameters); } /** @@ -294,14 +264,7 @@ public class BCUtil { * @return {@link ECPublicKeyParameters}或null */ public static ECPublicKeyParameters toParams(PublicKey publicKey) { - if (null == publicKey) { - return null; - } - try { - return (ECPublicKeyParameters) ECUtil.generatePublicKeyParameter(publicKey); - } catch (InvalidKeyException e) { - throw new CryptoException(e); - } + return ECKeyUtil.toPublicParams(publicKey); } /** @@ -311,14 +274,7 @@ public class BCUtil { * @return {@link ECPrivateKeyParameters}或null */ public static ECPrivateKeyParameters toParams(PrivateKey privateKey) { - if (null == privateKey) { - return null; - } - try { - return (ECPrivateKeyParameters) ECUtil.generatePrivateKeyParameter(privateKey); - } catch (InvalidKeyException e) { - throw new CryptoException(e); - } + return ECKeyUtil.toPrivateParams(privateKey); } /** @@ -344,58 +300,4 @@ public class BCUtil { public static PublicKey readPemPublicKey(InputStream pemStream) { return PemUtil.readPemPublicKey(pemStream); } - - /** - * 将X,Y曲线点编码为bytes - * - * @param xBytes X坐标bytes - * @param yBytes Y坐标bytes - * @param curveLength 曲线编码后的长度 - * @return 编码bytes - */ - private static byte[] encodePoint(byte[] xBytes, byte[] yBytes, int curveLength) { - xBytes = fixLength(curveLength, xBytes); - yBytes = fixLength(curveLength, yBytes); - final byte[] encodedPubKey = new byte[1 + xBytes.length + yBytes.length]; - - // 压缩类型:无压缩 - encodedPubKey[0] = 0x04; - System.arraycopy(xBytes, 0, encodedPubKey, 1, xBytes.length); - System.arraycopy(yBytes, 0, encodedPubKey, 1 + xBytes.length, yBytes.length); - - return encodedPubKey; - } - - /** - * 获取Curve长度 - * - * @param curve {@link ECCurve} - * @return Curve长度 - */ - private static int getCurveLength(ECCurve curve) { - return (curve.getFieldSize() + 7) / 8; - } - - /** - * 修正长度 - * - * @param curveLength 修正后的长度 - * @param src bytes - * @return 修正后的bytes - */ - private static byte[] fixLength(int curveLength, byte[] src) { - if (src.length == curveLength) { - return src; - } - - byte[] result = new byte[curveLength]; - if (src.length > curveLength) { - // 裁剪末尾的指定长度 - System.arraycopy(src, src.length - result.length, result, 0, result.length); - } else { - // 放置于末尾 - System.arraycopy(src, 0, result, result.length - src.length, src.length); - } - return result; - } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/CipherMode.java b/hutool-crypto/src/main/java/cn/hutool/crypto/CipherMode.java new file mode 100644 index 000000000..e7c949fca --- /dev/null +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/CipherMode.java @@ -0,0 +1,49 @@ +package cn.hutool.crypto; + +import javax.crypto.Cipher; + +/** + * Cipher模式的枚举封装 + * + * @author looly + * @since 5.4.3 + */ +public enum CipherMode { + /** + * 加密模式 + */ + encrypt(Cipher.ENCRYPT_MODE), + /** + * 解密模式 + */ + decrypt(Cipher.DECRYPT_MODE), + /** + * 包装模式 + */ + wrap(Cipher.WRAP_MODE), + /** + * 拆包模式 + */ + unwrap(Cipher.UNWRAP_MODE); + + + /** + * 构造 + * + * @param value 见{@link Cipher} + */ + CipherMode(int value) { + this.value = value; + } + + private final int value; + + /** + * 获取枚举值对应的int表示 + * + * @return 枚举值对应的int表示 + */ + public int getValue() { + return this.value; + } +} diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/ECKeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/ECKeyUtil.java new file mode 100644 index 000000000..bef444137 --- /dev/null +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/ECKeyUtil.java @@ -0,0 +1,262 @@ +package cn.hutool.crypto; + +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.util.BigIntegers; + +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * EC密钥参数相关工具类封装 + * + * @author looly + * @since 5.4.3 + */ +public class ECKeyUtil { + + /** + * 密钥转换为AsymmetricKeyParameter + * + * @param key PrivateKey或者PublicKey + * @return ECPrivateKeyParameters或者ECPublicKeyParameters + */ + public static AsymmetricKeyParameter toParams(Key key) { + if (key instanceof PrivateKey) { + return toPrivateParams((PrivateKey) key); + } else if (key instanceof PublicKey) { + return toPublicParams((PublicKey) key); + } + + return null; + } + + //--------------------------------------------------------------------------- Public Key + /** + * 转换为 ECPublicKeyParameters + * + * @param q 公钥Q值 + * @return ECPublicKeyParameters + */ + public static ECPublicKeyParameters toSm2PublicParams(byte[] q) { + return toPublicParams(q, SmUtil.SM2_DOMAIN_PARAMS); + } + + /** + * 转换为 ECPublicKeyParameters + * + * @param q 公钥Q值 + * @return ECPublicKeyParameters + */ + public static ECPublicKeyParameters toSm2PublicParams(String q) { + return toPublicParams(q, SmUtil.SM2_DOMAIN_PARAMS); + } + + /** + * 转换为SM2的ECPublicKeyParameters + * + * @param x 公钥X + * @param y 公钥Y + * @return ECPublicKeyParameters + */ + public static ECPublicKeyParameters toSm2PublicParams(String x, String y) { + return toPublicParams(x, y, SmUtil.SM2_DOMAIN_PARAMS); + } + + /** + * 转换为SM2的ECPublicKeyParameters + * + * @param xBytes 公钥X + * @param yBytes 公钥Y + * @return ECPublicKeyParameters + */ + public static ECPublicKeyParameters toSm2PublicParams(byte[] xBytes, byte[] yBytes) { + return toPublicParams(xBytes, yBytes, SmUtil.SM2_DOMAIN_PARAMS); + } + + /** + * 转换为ECPublicKeyParameters + * + * @param x 公钥X + * @param y 公钥Y + * @param domainParameters ECDomainParameters + * @return ECPublicKeyParameters + */ + public static ECPublicKeyParameters toPublicParams(String x, String y, ECDomainParameters domainParameters) { + return toPublicParams(SecureUtil.decode(x), SecureUtil.decode(y), domainParameters); + } + + /** + * 转换为ECPublicKeyParameters + * + * @param xBytes 公钥X + * @param yBytes 公钥Y + * @param domainParameters ECDomainParameters曲线参数 + * @return ECPublicKeyParameters + */ + public static ECPublicKeyParameters toPublicParams(byte[] xBytes, byte[] yBytes, ECDomainParameters domainParameters) { + return toPublicParams(BigIntegers.fromUnsignedByteArray(xBytes), BigIntegers.fromUnsignedByteArray(yBytes), domainParameters); + } + + /** + * 转换为ECPublicKeyParameters + * + * @param x 公钥X + * @param y 公钥Y + * @param domainParameters ECDomainParameters + * @return ECPublicKeyParameters + */ + public static ECPublicKeyParameters toPublicParams(BigInteger x, BigInteger y, ECDomainParameters domainParameters) { + if (null == x || null == y) { + return null; + } + final ECCurve curve = domainParameters.getCurve(); + return toPublicParams(curve.createPoint(x, y), domainParameters); + } + + /** + * 转换为ECPublicKeyParameters + * + * @param pointEncoded 被编码的曲线坐标点 + * @param domainParameters ECDomainParameters + * @return ECPublicKeyParameters + * @since 5.4.3 + */ + public static ECPublicKeyParameters toPublicParams(String pointEncoded, ECDomainParameters domainParameters) { + final ECCurve curve = domainParameters.getCurve(); + return toPublicParams(curve.decodePoint(SecureUtil.decode(pointEncoded)), domainParameters); + } + + /** + * 转换为ECPublicKeyParameters + * + * @param pointEncoded 被编码的曲线坐标点 + * @param domainParameters ECDomainParameters + * @return ECPublicKeyParameters + * @since 5.4.3 + */ + public static ECPublicKeyParameters toPublicParams(byte[] pointEncoded, ECDomainParameters domainParameters) { + final ECCurve curve = domainParameters.getCurve(); + return toPublicParams(curve.decodePoint(pointEncoded), domainParameters); + } + + /** + * 转换为ECPublicKeyParameters + * + * @param point 曲线坐标点 + * @param domainParameters ECDomainParameters + * @return ECPublicKeyParameters + * @since 5.4.3 + */ + public static ECPublicKeyParameters toPublicParams(org.bouncycastle.math.ec.ECPoint point, ECDomainParameters domainParameters) { + return new ECPublicKeyParameters(point, domainParameters); + } + + /** + * 公钥转换为 {@link ECPublicKeyParameters} + * + * @param publicKey 公钥,传入null返回null + * @return {@link ECPublicKeyParameters}或null + */ + public static ECPublicKeyParameters toPublicParams(PublicKey publicKey) { + if (null == publicKey) { + return null; + } + try { + return (ECPublicKeyParameters) ECUtil.generatePublicKeyParameter(publicKey); + } catch (InvalidKeyException e) { + throw new CryptoException(e); + } + } + + //--------------------------------------------------------------------------- Private Key + /** + * 转换为 ECPrivateKeyParameters + * + * @param d 私钥d值16进制字符串 + * @return ECPrivateKeyParameters + */ + public static ECPrivateKeyParameters toSm2PrivateParams(String d) { + return toPrivateParams(d, SmUtil.SM2_DOMAIN_PARAMS); + } + + /** + * 转换为 ECPrivateKeyParameters + * + * @param d 私钥d值 + * @return ECPrivateKeyParameters + */ + public static ECPrivateKeyParameters toSm2PrivateParams(byte[] d) { + return toPrivateParams(d, SmUtil.SM2_DOMAIN_PARAMS); + } + + /** + * 转换为 ECPrivateKeyParameters + * + * @param d 私钥d值 + * @return ECPrivateKeyParameters + */ + public static ECPrivateKeyParameters toSm2PrivateParams(BigInteger d) { + return toPrivateParams(d, SmUtil.SM2_DOMAIN_PARAMS); + } + + /** + * 转换为 ECPrivateKeyParameters + * + * @param d 私钥d值16进制字符串 + * @param domainParameters ECDomainParameters + * @return ECPrivateKeyParameters + */ + public static ECPrivateKeyParameters toPrivateParams(String d, ECDomainParameters domainParameters) { + return toPrivateParams(BigIntegers.fromUnsignedByteArray(SecureUtil.decode(d)), domainParameters); + } + + /** + * 转换为 ECPrivateKeyParameters + * + * @param d 私钥d值 + * @param domainParameters ECDomainParameters + * @return ECPrivateKeyParameters + */ + public static ECPrivateKeyParameters toPrivateParams(byte[] d, ECDomainParameters domainParameters) { + return toPrivateParams(BigIntegers.fromUnsignedByteArray(d), domainParameters); + } + + /** + * 转换为 ECPrivateKeyParameters + * + * @param d 私钥d值 + * @param domainParameters ECDomainParameters + * @return ECPrivateKeyParameters + */ + public static ECPrivateKeyParameters toPrivateParams(BigInteger d, ECDomainParameters domainParameters) { + if (null == d) { + return null; + } + return new ECPrivateKeyParameters(d, domainParameters); + } + + /** + * 私钥转换为 {@link ECPrivateKeyParameters} + * + * @param privateKey 私钥,传入null返回null + * @return {@link ECPrivateKeyParameters}或null + */ + public static ECPrivateKeyParameters toPrivateParams(PrivateKey privateKey) { + if (null == privateKey) { + return null; + } + try { + return (ECPrivateKeyParameters) ECUtil.generatePrivateKeyParameter(privateKey); + } catch (InvalidKeyException e) { + throw new CryptoException(e); + } + } +} 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 2c9a834c1..27402d5da 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -618,11 +618,19 @@ public class KeyUtil { */ public static String getAlgorithmAfterWith(String algorithm) { Assert.notNull(algorithm, "algorithm must be not null !"); + + if(StrUtil.startWithIgnoreCase(algorithm, "ECIESWith")){ + return "EC"; + } + int indexOfWith = StrUtil.lastIndexOfIgnoreCase(algorithm, "with"); if (indexOfWith > 0) { algorithm = StrUtil.subSuf(algorithm, indexOfWith + "with".length()); } - if ("ECDSA".equalsIgnoreCase(algorithm) || "SM2".equalsIgnoreCase(algorithm)) { + if ("ECDSA".equalsIgnoreCase(algorithm) + || "SM2".equalsIgnoreCase(algorithm) + || "ECIES".equalsIgnoreCase(algorithm) + ) { algorithm = "EC"; } return algorithm; diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AsymmetricCrypto.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AsymmetricCrypto.java index 3f694a97f..a3c989817 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AsymmetricCrypto.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AsymmetricCrypto.java @@ -3,6 +3,7 @@ package cn.hutool.crypto.asymmetric; import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FastByteArrayOutputStream; import cn.hutool.crypto.CryptoException; +import cn.hutool.crypto.KeyUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.symmetric.SymmetricAlgorithm; @@ -10,38 +11,52 @@ import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.spec.AlgorithmParameterSpec; /** * 非对称加密算法 - * + * *
      * 1、签名:使用私钥加密,公钥解密。
      * 用于让所有公钥所有者验证私钥所有者的身份并且用来防止私钥所有者发布的内容被篡改,但是不用来保证内容不被他人获得。
    - * 
    + *
      * 2、加密:用公钥加密,私钥解密。
      * 用于向公钥所有者发布信息,这个信息可能被他人篡改,但是无法被他人获得。
      * 
    - * - * @author Looly * + * @author Looly */ public class AsymmetricCrypto extends AbstractAsymmetricCrypto { - /** Cipher负责完成加密或解密工作 */ + /** + * Cipher负责完成加密或解密工作 + */ protected Cipher cipher; - /** 加密的块大小 */ + /** + * 加密的块大小 + */ protected int encryptBlockSize = -1; - /** 解密的块大小 */ + /** + * 解密的块大小 + */ protected int decryptBlockSize = -1; + /** + * 算法参数 + */ + private AlgorithmParameterSpec algorithmParameterSpec; + // ------------------------------------------------------------------ Constructor start + /** * 构造,创建新的私钥公钥对 - * + * * @param algorithm {@link SymmetricAlgorithm} */ @SuppressWarnings("RedundantCast") @@ -51,7 +66,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto /** * 构造,创建新的私钥公钥对 - * + * * @param algorithm 算法 */ @SuppressWarnings("RedundantCast") @@ -62,10 +77,10 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto /** * 构造 私钥和公钥同时为空时生成一对新的私钥和公钥
    * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密 - * - * @param algorithm {@link SymmetricAlgorithm} + * + * @param algorithm {@link SymmetricAlgorithm} * @param privateKeyStr 私钥Hex或Base64表示 - * @param publicKeyStr 公钥Hex或Base64表示 + * @param publicKeyStr 公钥Hex或Base64表示 */ public AsymmetricCrypto(AsymmetricAlgorithm algorithm, String privateKeyStr, String publicKeyStr) { this(algorithm.getValue(), SecureUtil.decode(privateKeyStr), SecureUtil.decode(publicKeyStr)); @@ -74,10 +89,10 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto /** * 构造 私钥和公钥同时为空时生成一对新的私钥和公钥
    * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密 - * - * @param algorithm {@link SymmetricAlgorithm} + * + * @param algorithm {@link SymmetricAlgorithm} * @param privateKey 私钥 - * @param publicKey 公钥 + * @param publicKey 公钥 */ public AsymmetricCrypto(AsymmetricAlgorithm algorithm, byte[] privateKey, byte[] publicKey) { this(algorithm.getValue(), privateKey, publicKey); @@ -86,10 +101,10 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto /** * 构造 私钥和公钥同时为空时生成一对新的私钥和公钥
    * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密 - * - * @param algorithm {@link SymmetricAlgorithm} + * + * @param algorithm {@link SymmetricAlgorithm} * @param privateKey 私钥 - * @param publicKey 公钥 + * @param publicKey 公钥 * @since 3.1.1 */ public AsymmetricCrypto(AsymmetricAlgorithm algorithm, PrivateKey privateKey, PublicKey publicKey) { @@ -99,10 +114,10 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto /** * 构造 私钥和公钥同时为空时生成一对新的私钥和公钥
    * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密 - * - * @param algorithm 非对称加密算法 + * + * @param algorithm 非对称加密算法 * @param privateKeyBase64 私钥Base64 - * @param publicKeyBase64 公钥Base64 + * @param publicKeyBase64 公钥Base64 */ public AsymmetricCrypto(String algorithm, String privateKeyBase64, String publicKeyBase64) { this(algorithm, Base64.decode(privateKeyBase64), Base64.decode(publicKeyBase64)); @@ -110,30 +125,30 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto /** * 构造 - * + *

    * 私钥和公钥同时为空时生成一对新的私钥和公钥
    * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密 - * - * @param algorithm 算法 + * + * @param algorithm 算法 * @param privateKey 私钥 - * @param publicKey 公钥 + * @param publicKey 公钥 */ public AsymmetricCrypto(String algorithm, byte[] privateKey, byte[] publicKey) { this(algorithm, // - SecureUtil.generatePrivateKey(algorithm, privateKey), // - SecureUtil.generatePublicKey(algorithm, publicKey)// + KeyUtil.generatePrivateKey(algorithm, privateKey), // + KeyUtil.generatePublicKey(algorithm, publicKey)// ); } /** * 构造 - * + *

    * 私钥和公钥同时为空时生成一对新的私钥和公钥
    * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密 - * - * @param algorithm 算法 + * + * @param algorithm 算法 * @param privateKey 私钥 - * @param publicKey 公钥 + * @param publicKey 公钥 * @since 3.1.1 */ public AsymmetricCrypto(String algorithm, PrivateKey privateKey, PublicKey publicKey) { @@ -143,7 +158,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto /** * 获取加密块大小 - * + * * @return 加密块大小 */ public int getEncryptBlockSize() { @@ -152,7 +167,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto /** * 设置加密块大小 - * + * * @param encryptBlockSize 加密块大小 */ public void setEncryptBlockSize(int encryptBlockSize) { @@ -161,7 +176,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto /** * 获取解密块大小 - * + * * @return 解密块大小 */ public int getDecryptBlockSize() { @@ -170,13 +185,35 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto /** * 设置解密块大小 - * + * * @param decryptBlockSize 解密块大小 */ public void setDecryptBlockSize(int decryptBlockSize) { this.decryptBlockSize = decryptBlockSize; } + /** + * 获取{@link AlgorithmParameterSpec}
    + * 在某些算法中,需要特别的参数,例如在ECIES中,此处为IESParameterSpec + * + * @return {@link AlgorithmParameterSpec} + * @since 5.4.3 + */ + public AlgorithmParameterSpec getAlgorithmParameterSpec() { + return algorithmParameterSpec; + } + + /** + * 设置{@link AlgorithmParameterSpec}
    + * 在某些算法中,需要特别的参数,例如在ECIES中,此处为IESParameterSpec + * + * @param algorithmParameterSpec {@link AlgorithmParameterSpec} + * @since 5.4.3 + */ + public void setAlgorithmParameterSpec(AlgorithmParameterSpec algorithmParameterSpec) { + this.algorithmParameterSpec = algorithmParameterSpec; + } + @Override public AsymmetricCrypto init(String algorithm, PrivateKey privateKey, PublicKey publicKey) { super.init(algorithm, privateKey, publicKey); @@ -185,10 +222,11 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto } // --------------------------------------------------------------------------------- Encrypt + /** * 加密 - * - * @param data 被加密的bytes + * + * @param data 被加密的bytes * @param keyType 私钥或公钥 {@link KeyType} * @return 加密后的bytes */ @@ -197,12 +235,12 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto final Key key = getKeyByType(keyType); lock.lock(); try { - cipher.init(Cipher.ENCRYPT_MODE, key); + initCipher(Cipher.ENCRYPT_MODE, key); - if(this.encryptBlockSize < 0){ + if (this.encryptBlockSize < 0) { // 在引入BC库情况下,自动获取块大小 final int blockSize = this.cipher.getBlockSize(); - if(blockSize > 0){ + if (blockSize > 0) { this.encryptBlockSize = blockSize; } } @@ -216,10 +254,11 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto } // --------------------------------------------------------------------------------- Decrypt + /** * 解密 - * - * @param data 被解密的bytes + * + * @param data 被解密的bytes * @param keyType 私钥或公钥 {@link KeyType} * @return 解密后的bytes */ @@ -228,12 +267,12 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto final Key key = getKeyByType(keyType); lock.lock(); try { - cipher.init(Cipher.DECRYPT_MODE, key); + initCipher(Cipher.DECRYPT_MODE, key); - if(this.decryptBlockSize < 0){ + if (this.decryptBlockSize < 0) { // 在引入BC库情况下,自动获取块大小 final int blockSize = this.cipher.getBlockSize(); - if(blockSize > 0){ + if (blockSize > 0) { this.decryptBlockSize = blockSize; } } @@ -250,16 +289,28 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto /** * 获得加密或解密器 - * + * * @return 加密或解密 + * @deprecated 拼写错误,请使用{@link #getCipher()} */ + @Deprecated public Cipher getClipher() { return cipher; } + /** + * 获得加密或解密器 + * + * @return 加密或解密 + * @since 5.4.3 + */ + public Cipher getCipher() { + return cipher; + } + /** * 初始化{@link Cipher},默认尝试加载BC库 - * + * * @since 4.5.2 */ protected void initCipher() { @@ -268,13 +319,13 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto /** * 加密或解密 - * - * @param data 被加密或解密的内容数据 + * + * @param data 被加密或解密的内容数据 * @param maxBlockSize 最大块(分段)大小 * @return 加密或解密后的数据 * @throws IllegalBlockSizeException 分段异常 - * @throws BadPaddingException padding错误异常 - * @throws IOException IO异常,不会被触发 + * @throws BadPaddingException padding错误异常 + * @throws IOException IO异常,不会被触发 */ private byte[] doFinal(byte[] data, int maxBlockSize) throws IllegalBlockSizeException, BadPaddingException, IOException { // 模长 @@ -291,19 +342,18 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto /** * 分段加密或解密 - * - * @param data 数据 + * + * @param data 数据 * @param maxBlockSize 最大分段的段大小,不能为小于1 * @return 加密或解密后的数据 * @throws IllegalBlockSizeException 分段异常 - * @throws BadPaddingException padding错误异常 - * @throws IOException IO异常,不会被触发 + * @throws BadPaddingException padding错误异常 + * @throws IOException IO异常,不会被触发 */ private byte[] doFinalWithBlock(byte[] data, int maxBlockSize) throws IllegalBlockSizeException, BadPaddingException, IOException { final int dataLength = data.length; - @SuppressWarnings("resource") - final FastByteArrayOutputStream out = new FastByteArrayOutputStream(); - + @SuppressWarnings("resource") final FastByteArrayOutputStream out = new FastByteArrayOutputStream(); + int offSet = 0; // 剩余长度 int remainLength = dataLength; @@ -316,7 +366,23 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto offSet += blockSize; remainLength = dataLength - offSet; } - + return out.toByteArray(); } + + /** + * 初始化{@link Cipher} + * + * @param mode 模式,可选{@link Cipher#ENCRYPT_MODE}或者{@link Cipher#DECRYPT_MODE} + * @param key 密钥 + * @throws InvalidAlgorithmParameterException 异常算法错误 + * @throws InvalidKeyException 异常KEY错误 + */ + private void initCipher(int mode, Key key) throws InvalidAlgorithmParameterException, InvalidKeyException { + if (null != this.algorithmParameterSpec) { + cipher.init(mode, key, this.algorithmParameterSpec); + } else { + cipher.init(mode, key); + } + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/BaseAsymmetric.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/BaseAsymmetric.java index 266006656..ae46f935a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/BaseAsymmetric.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/BaseAsymmetric.java @@ -196,6 +196,6 @@ public class BaseAsymmetric> { } return this.publicKey; } - throw new CryptoException("Uknown key type: " + type); + throw new CryptoException("Unsupported key type: " + type); } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/KeyType.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/KeyType.java index 56038502a..62383231a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/KeyType.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/KeyType.java @@ -1,5 +1,7 @@ package cn.hutool.crypto.asymmetric; +import javax.crypto.Cipher; + /** * 密钥类型 * @@ -7,5 +9,37 @@ package cn.hutool.crypto.asymmetric; * */ public enum KeyType { - PrivateKey, PublicKey + /** + * 公钥 + */ + PublicKey(Cipher.PUBLIC_KEY), + /** + * 私钥 + */ + PrivateKey(Cipher.PRIVATE_KEY), + /** + * 密钥 + */ + SecretKey(Cipher.SECRET_KEY); + + + /** + * 构造 + * + * @param value 见{@link Cipher} + */ + KeyType(int value) { + this.value = value; + } + + private final int value; + + /** + * 获取枚举值对应的int表示 + * + * @return 枚举值对应的int表示 + */ + public int getValue() { + return this.value; + } } \ No newline at end of file diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index b33f49614..d0315bd61 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -499,8 +499,10 @@ public class SM2 extends AbstractAsymmetricCrypto { */ private SM2Engine getEngine() { if (null == this.engine) { + Assert.notNull(this.digest, "digest must be not null !"); this.engine = new SM2Engine(this.digest, this.mode); } + this.digest.reset(); return this.engine; } @@ -511,8 +513,10 @@ public class SM2 extends AbstractAsymmetricCrypto { */ private SM2Signer getSigner() { if (null == this.signer) { + Assert.notNull(this.digest, "digest must be not null !"); this.signer = new SM2Signer(this.encoding, this.digest); } + this.digest.reset(); return this.signer; } // ------------------------------------------------------------------------------------------------------------------------- Private method end diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/RC4.java b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/RC4.java index 2cf2e477e..13fb145f1 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/RC4.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/RC4.java @@ -1,16 +1,17 @@ package cn.hutool.crypto.symmetric; -import java.io.Serializable; -import java.nio.charset.Charset; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; - import cn.hutool.core.codec.Base64; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.CryptoException; +import cn.hutool.crypto.SecureUtil; + +import java.io.Serializable; +import java.nio.charset.Charset; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; /** * RC4加密解密算法实现
    @@ -97,6 +98,17 @@ public class RC4 implements Serializable { return HexUtil.encodeHexStr(encrypt(data, charset)); } + /** + * 加密,使用UTF-8编码 + * + * @param data 被加密的字符串 + * @return 加密后的Hex + * @since 5.4.4 + */ + public String encryptHex(String data) { + return HexUtil.encodeHexStr(encrypt(data)); + } + /** * 加密 * @@ -109,6 +121,18 @@ public class RC4 implements Serializable { return Base64.encode(encrypt(data, charset)); } + + /** + * 加密,使用UTF-8编码 + * + * @param data 被加密的字符串 + * @return 加密后的Base64 + * @since 5.4.4 + */ + public String encryptBase64(String data) { + return Base64.encode(encrypt(data)); + } + /** * 解密 * @@ -132,6 +156,30 @@ public class RC4 implements Serializable { return decrypt(message, CharsetUtil.CHARSET_UTF_8); } + /** + * 解密Hex(16进制)或Base64表示的字符串,使用默认编码UTF-8 + * + * @param message 消息 + * @return 明文 + * @since 5.4.4 + */ + public String decrypt(String message) { + return decrypt(SecureUtil.decode(message)); + } + + /** + * 解密Hex(16进制)或Base64表示的字符串 + * + * @param message 明文 + * @param charset 解密后的charset + * @return 明文 + * @since 5.4.4 + */ + public String decrypt(String message, Charset charset) { + return StrUtil.str(decrypt(message), charset); + } + + /** * 加密或解密指定值,调用此方法前需初始化密钥 * @@ -216,4 +264,4 @@ public class RC4 implements Serializable { sbox[j] = temp; } //----------------------------------------------------------------------------------------------------------------------- Private method end -} \ No newline at end of file +} diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index b038fd67d..0489bc9ba 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -31,4 +31,19 @@ public class KeyUtilTest { final PublicKey rsaPublicKey = KeyUtil.getRSAPublicKey(aPrivate); Assert.assertEquals(rsaPublicKey, keyPair.getPublic()); } + + /** + * 测试EC和ECIES算法生成的KEY是一致的 + */ + @Test + public void generateECIESKeyTest(){ + final KeyPair ecies = KeyUtil.generateKeyPair("ECIES"); + Assert.assertNotNull(ecies.getPrivate()); + Assert.assertNotNull(ecies.getPublic()); + + byte[] privateKeyBytes = ecies.getPrivate().getEncoded(); + + final PrivateKey privateKey = KeyUtil.generatePrivateKey("EC", privateKeyBytes); + Assert.assertEquals(ecies.getPrivate(), privateKey); + } } diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/ECIESTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/ECIESTest.java index 1b83fe4f0..e00e6b65f 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/ECIESTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/ECIESTest.java @@ -1,6 +1,7 @@ package cn.hutool.crypto.test.asymmetric; import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.asymmetric.AsymmetricCrypto; import cn.hutool.crypto.asymmetric.ECIES; import cn.hutool.crypto.asymmetric.KeyType; import org.junit.Assert; @@ -12,6 +13,25 @@ public class ECIESTest { public void eciesTest(){ final ECIES ecies = new ECIES(); + doTest(ecies, ecies); + } + + @Test + public void eciesTest2(){ + final ECIES ecies = new ECIES(); + final byte[] privateKeyBytes = ecies.getPrivateKey().getEncoded(); + final ECIES ecies2 = new ECIES(privateKeyBytes, null); + + doTest(ecies, ecies2); + } + + /** + * 测试用例 + * + * @param cryptoForEncrypt 加密的Crypto + * @param cryptoForDecrypt 解密的Crypto + */ + private void doTest(AsymmetricCrypto cryptoForEncrypt, AsymmetricCrypto cryptoForDecrypt){ String textBase = "我是一段特别长的测试"; StringBuilder text = new StringBuilder(); for (int i = 0; i < 10; i++) { @@ -19,8 +39,9 @@ public class ECIESTest { } // 公钥加密,私钥解密 - String encryptStr = ecies.encryptBase64(text.toString(), KeyType.PublicKey); - String decryptStr = StrUtil.utf8Str(ecies.decrypt(encryptStr, KeyType.PrivateKey)); + String encryptStr = cryptoForEncrypt.encryptBase64(text.toString(), KeyType.PublicKey); + + String decryptStr = StrUtil.utf8Str(cryptoForDecrypt.decrypt(encryptStr, KeyType.PrivateKey)); Assert.assertEquals(text.toString(), decryptStr); } } diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SM2Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SM2Test.java index 56abb282d..42e2bcb97 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SM2Test.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SM2Test.java @@ -1,15 +1,19 @@ package cn.hutool.crypto.test.asymmetric; import cn.hutool.core.codec.Base64; +import cn.hutool.core.lang.Console; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.ECKeyUtil; import cn.hutool.crypto.KeyUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.SmUtil; import cn.hutool.crypto.asymmetric.KeyType; import cn.hutool.crypto.asymmetric.SM2; import org.bouncycastle.crypto.engines.SM2Engine; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.junit.Assert; import org.junit.Test; @@ -205,4 +209,38 @@ public class SM2Test { String sign = "DCA0E80A7F46C93714B51C3EFC55A922BCEF7ECF0FE9E62B53BA6A7438B543A76C145A452CA9036F3CB70D7E6C67D4D9D7FE114E5367A2F6F5A4D39F2B10F3D6"; Assert.assertTrue(sm2.verifyHex(data, sign)); } + + @Test + public void sm2PlainWithPointTest2() { + String d = "4BD9A450D7E68A5D7E08EB7A0BFA468FD3EB32B71126246E66249A73A9E4D44A"; + String q = "04970AB36C3B870FBC04041087DB1BC36FB4C6E125B5EA406DB0EC3E2F80F0A55D8AFF28357A0BB215ADC2928BE76F1AFF869BF4C0A3852A78F3B827812C650AD3"; + + String data = "123456"; + + final ECPublicKeyParameters ecPublicKeyParameters = ECKeyUtil.toSm2PublicParams(q); + final ECPrivateKeyParameters ecPrivateKeyParameters = ECKeyUtil.toSm2PrivateParams(d); + + final SM2 sm2 = new SM2(ecPrivateKeyParameters, ecPublicKeyParameters); + sm2.setMode(SM2Engine.Mode.C1C2C3); + final String encryptHex = sm2.encryptHex(data, KeyType.PublicKey); + Console.log(encryptHex); + final String decryptStr = sm2.decryptStr(encryptHex, KeyType.PrivateKey); + + Assert.assertEquals(data, decryptStr); + } + + @Test + public void encryptAndSignTest(){ + SM2 sm2 = SmUtil.sm2(); + + String src = "Sm2Test"; + byte[] data = sm2.encrypt(src, KeyType.PublicKey); + byte[] sign = sm2.sign(src.getBytes()); + + Assert.assertTrue(sm2.verify( src.getBytes(), sign)); + + byte[] dec = sm2.decrypt(data, KeyType.PrivateKey); + Assert.assertArrayEquals(dec, src.getBytes()); + } + } diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/AESTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/AESTest.java index f89c9cfc2..a4ac43dd2 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/AESTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/AESTest.java @@ -17,4 +17,22 @@ public class AESTest { Assert.assertEquals("d637735ae9e21ba50cb686b74fab8d2c", encryptHex); } + @Test + public void encryptTest2() { + String content = "test中文"; + AES aes = new AES(Mode.CTS, Padding.PKCS5Padding, + "0CoJUm6Qyw8W8jue".getBytes(), "0102030405060708".getBytes()); + final String encryptHex = aes.encryptHex(content); + Assert.assertEquals("8dc9de7f050e86ca2c8261dde56dfec9", encryptHex); + } + + @Test + public void encryptPKCS7Test() { + // 构建 + AES aes = new AES(Mode.CBC.name(), "pkcs7padding", + "1234567890123456".getBytes(), "1234567890123456".getBytes()); + String encryptHex = aes.encryptHex("123456"); + Assert.assertEquals("d637735ae9e21ba50cb686b74fab8d2c", encryptHex); + } + } diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/RC4Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/RC4Test.java index 7869edc6a..bb9980bd5 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/RC4Test.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/RC4Test.java @@ -1,5 +1,6 @@ package cn.hutool.crypto.test.symmetric; +import cn.hutool.core.util.CharsetUtil; import org.junit.Assert; import org.junit.Test; @@ -36,4 +37,35 @@ public class RC4Test { String msg2 = rc4.decrypt(crypt2); Assert.assertEquals(message2, msg2); } + + @Test + public void testDecryptWithHexMessage() { + String message = "这是第一个用来测试密文为十六进制字符串的消息!"; + String key = "生成一个密钥"; + RC4 rc4 = new RC4(key); + String encryptHex = rc4.encryptHex(message, CharsetUtil.CHARSET_UTF_8); + String msg = rc4.decrypt(encryptHex); + Assert.assertEquals(message, msg); + + String message2 = "这是第二个用来测试密文为十六进制字符串的消息!"; + String encryptHex2 = rc4.encryptHex(message2); + String msg2 = rc4.decrypt(encryptHex2); + Assert.assertEquals(message2, msg2); + } + + + @Test + public void testDecryptWithBase64Message() { + String message = "这是第一个用来测试密文为Base64编码的消息!"; + String key = "生成一个密钥"; + RC4 rc4 = new RC4(key); + String encryptHex = rc4.encryptBase64(message, CharsetUtil.CHARSET_UTF_8); + String msg = rc4.decrypt(encryptHex); + Assert.assertEquals(message, msg); + + String message2 = "这是第一个用来测试密文为Base64编码的消息!"; + String encryptHex2 = rc4.encryptBase64(message2); + String msg2 = rc4.decrypt(encryptHex2); + Assert.assertEquals(message2, msg2); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index a58bd610a..991444907 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-db @@ -23,8 +23,8 @@ 9.0.30 1.1.23 2.4.13 - 3.12.4 - 3.32.3 + 3.12.7 + 3.32.3.2 2.5.1 3.3.0 @@ -120,13 +120,13 @@ mysql mysql-connector-java - 8.0.20 + 8.0.21 test org.postgresql postgresql - 42.2.12.jre7 + 42.2.16.jre7 test diff --git a/hutool-db/src/main/java/cn/hutool/db/AbstractDb.java b/hutool-db/src/main/java/cn/hutool/db/AbstractDb.java index 3cf9bdadf..86a05ba3b 100644 --- a/hutool-db/src/main/java/cn/hutool/db/AbstractDb.java +++ b/hutool-db/src/main/java/cn/hutool/db/AbstractDb.java @@ -10,6 +10,7 @@ import cn.hutool.db.handler.RsHandler; import cn.hutool.db.handler.StringHandler; import cn.hutool.db.sql.Condition; import cn.hutool.db.sql.Condition.LikeType; +import cn.hutool.db.sql.LogicalOperator; import cn.hutool.db.sql.Query; import cn.hutool.db.sql.SqlExecutor; import cn.hutool.db.sql.SqlUtil; @@ -628,7 +629,7 @@ public abstract class AbstractDb implements Serializable { * 根据多个条件查询数据列表,返回所有字段 * * @param tableName 表名 - * @param wheres 字段名 + * @param wheres 条件,多个条件的连接逻辑使用{@link Condition#setLinkOperator(LogicalOperator)} 定义 * @return 数据对象列表 * @throws SQLException SQL执行异常 * @since 4.0.0 diff --git a/hutool-db/src/main/java/cn/hutool/db/StatementUtil.java b/hutool-db/src/main/java/cn/hutool/db/StatementUtil.java index 34f437560..413e5a5ce 100644 --- a/hutool-db/src/main/java/cn/hutool/db/StatementUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/StatementUtil.java @@ -1,7 +1,7 @@ package cn.hutool.db; import cn.hutool.core.collection.ArrayIter; -import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; @@ -13,8 +13,19 @@ import cn.hutool.db.sql.SqlUtil; import java.math.BigDecimal; import java.math.BigInteger; -import java.sql.*; -import java.util.*; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * Statement和PreparedStatement工具类 @@ -163,8 +174,9 @@ public class StatementUtil { sql = sql.trim(); SqlLog.INSTANCE.log(sql, paramsBatch); PreparedStatement ps = conn.prepareStatement(sql); + final Map nullTypeMap = new HashMap<>(); for (Object[] params : paramsBatch) { - StatementUtil.fillParams(ps, params); + fillParams(ps, new ArrayIter<>(params), nullTypeMap); ps.addBatch(); } return ps; @@ -190,7 +202,7 @@ public class StatementUtil { //null参数的类型缓存,避免循环中重复获取类型 final Map nullTypeMap = new HashMap<>(); for (Entity entity : entities) { - StatementUtil.fillParams(ps, CollectionUtil.valuesOfKeys(entity, fields), nullTypeMap); + fillParams(ps, CollUtil.valuesOfKeys(entity, fields), nullTypeMap); ps.addBatch(); } return ps; diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/AnsiSqlDialect.java b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/AnsiSqlDialect.java index 1cfb65f25..03b58dda1 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/AnsiSqlDialect.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/AnsiSqlDialect.java @@ -1,6 +1,6 @@ package cn.hutool.db.dialect.impl; -import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; @@ -10,7 +10,10 @@ import cn.hutool.db.Page; import cn.hutool.db.StatementUtil; import cn.hutool.db.dialect.Dialect; import cn.hutool.db.dialect.DialectName; -import cn.hutool.db.sql.*; +import cn.hutool.db.sql.Condition; +import cn.hutool.db.sql.Query; +import cn.hutool.db.sql.SqlBuilder; +import cn.hutool.db.sql.Wrapper; import java.sql.Connection; import java.sql.PreparedStatement; @@ -63,7 +66,7 @@ public class AnsiSqlDialect implements Dialect { // 对于无条件的删除语句直接抛出异常禁止,防止误删除 throw new SQLException("No 'WHERE' condition, we can't prepared statement for delete everything."); } - final SqlBuilder delete = SqlBuilder.create(wrapper).delete(query.getFirstTableName()).where(LogicalOperator.AND, where); + final SqlBuilder delete = SqlBuilder.create(wrapper).delete(query.getFirstTableName()).where(where); return StatementUtil.prepareStatement(conn, delete); } @@ -72,13 +75,13 @@ public class AnsiSqlDialect implements Dialect { public PreparedStatement psForUpdate(Connection conn, Entity entity, Query query) throws SQLException { Assert.notNull(query, "query must not be null !"); - Condition[] where = query.getWhere(); + final Condition[] where = query.getWhere(); if (ArrayUtil.isEmpty(where)) { // 对于无条件的删除语句直接抛出异常禁止,防止误删除 throw new SQLException("No 'WHERE' condition, we can't prepare statement for update everything."); } - final SqlBuilder update = SqlBuilder.create(wrapper).update(entity).where(LogicalOperator.AND, where); + final SqlBuilder update = SqlBuilder.create(wrapper).update(entity).where(where); return StatementUtil.prepareStatement(conn, update); } @@ -129,7 +132,7 @@ public class AnsiSqlDialect implements Dialect { @Override public PreparedStatement psForCount(Connection conn, Query query) throws SQLException { - query.setFields(CollectionUtil.newArrayList("count(1)")); + query.setFields(ListUtil.toList("count(1)")); return psForFind(conn, query); } diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/Column.java b/hutool-db/src/main/java/cn/hutool/db/meta/Column.java index eb4285a04..a7edbb18c 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/Column.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/Column.java @@ -1,51 +1,88 @@ package cn.hutool.db.meta; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.DbRuntimeException; + import java.io.Serializable; import java.sql.ResultSet; import java.sql.SQLException; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.DbRuntimeException; - /** * 数据库表的列信息 - * - * @author loolly * + * @author loolly */ public class Column implements Serializable, Cloneable { private static final long serialVersionUID = 577527740359719367L; // ----------------------------------------------------- Fields start - /** 表名 */ + /** + * 表名 + */ private String tableName; - /** 列名 */ + /** + * 列名 + */ private String name; - /** 类型,对应java.sql.Types中的类型 */ + /** + * 类型,对应java.sql.Types中的类型 + */ private int type; - /** 类型名称 */ + /** + * 类型名称 + */ private String typeName; - /** 大小或数据长度 */ + /** + * 大小或数据长度 + */ private int size; - /** 是否为可空 */ + private Integer digit; + /** + * 是否为可空 + */ private boolean isNullable; - /** 注释 */ + /** + * 注释 + */ private String comment; + /** + * 是否自增 + */ + private boolean autoIncrement; + /** + * 是否为主键 + */ + private boolean isPk; // ----------------------------------------------------- Fields end /** * 创建列对象 - * - * @param tableName 表名 + * + * @param tableName 表名 * @param columnMetaRs 列元信息的ResultSet * @return 列对象 + * @deprecated 请使用 {@link #create(Table, ResultSet)} */ public static Column create(String tableName, ResultSet columnMetaRs) { return new Column(tableName, columnMetaRs); } + /** + * 创建列对象 + * + * @param columnMetaRs 列元信息的ResultSet + * @param table 表信息 + * @return 列对象 + * @since 5.4.3 + */ + public static Column create(Table table, ResultSet columnMetaRs) { + return new Column(table, columnMetaRs); + } + // ----------------------------------------------------- Constructor start + /** * 构造 */ @@ -54,10 +91,12 @@ public class Column implements Serializable, Cloneable { /** * 构造 - * - * @param tableName 表名 + * + * @param tableName 表名 * @param columnMetaRs Meta信息的ResultSet + * @deprecated 请使用 {@link #Column(Table, ResultSet)} */ + @Deprecated public Column(String tableName, ResultSet columnMetaRs) { try { init(tableName, columnMetaRs); @@ -65,30 +104,78 @@ public class Column implements Serializable, Cloneable { throw new DbRuntimeException(StrUtil.format("Get table [{}] meta info error!", tableName)); } } + + /** + * 构造 + * + * @param table 表信息 + * @param columnMetaRs Meta信息的ResultSet + * @since 5.4.3 + */ + public Column(Table table, ResultSet columnMetaRs) { + try { + init(table, columnMetaRs); + } catch (SQLException e) { + throw new DbRuntimeException(StrUtil.format("Get table [{}] meta info error!", tableName)); + } + } // ----------------------------------------------------- Constructor end /** * 初始化 - * - * @param tableName 表名 + * + * @param tableName 表名 + * @param columnMetaRs 列的meta ResultSet + * @throws SQLException SQL执行异常 + * @deprecated 请使用 {@link #init(Table, ResultSet)} + */ + @Deprecated + public void init(String tableName, ResultSet columnMetaRs) throws SQLException { + init(Table.create(tableName), columnMetaRs); + } + + /** + * 初始化 + * + * @param table 表信息 * @param columnMetaRs 列的meta ResultSet * @throws SQLException SQL执行异常 */ - public void init(String tableName, ResultSet columnMetaRs) throws SQLException { - this.tableName = tableName; + public void init(Table table, ResultSet columnMetaRs) throws SQLException { + this.tableName = table.getTableName(); this.name = columnMetaRs.getString("COLUMN_NAME"); + this.isPk = table.isPk(this.name); + this.type = columnMetaRs.getInt("DATA_TYPE"); this.typeName = columnMetaRs.getString("TYPE_NAME"); this.size = columnMetaRs.getInt("COLUMN_SIZE"); this.isNullable = columnMetaRs.getBoolean("NULLABLE"); this.comment = columnMetaRs.getString("REMARKS"); + + // 保留小数位数 + try { + this.digit = columnMetaRs.getInt("DECIMAL_DIGITS"); + } catch (SQLException ignore) { + //某些驱动可能不支持,跳过 + } + + // 是否自增 + try { + String auto = columnMetaRs.getString("IS_AUTOINCREMENT"); + if (BooleanUtil.toBoolean(auto)) { + this.autoIncrement = true; + } + } catch (SQLException ignore) { + //某些驱动可能不支持,跳过 + } } // ----------------------------------------------------- Getters and Setters start + /** * 获取表名 - * + * * @return 表名 */ public String getTableName() { @@ -97,7 +184,7 @@ public class Column implements Serializable, Cloneable { /** * 设置表名 - * + * * @param tableName 表名 * @return this */ @@ -108,7 +195,7 @@ public class Column implements Serializable, Cloneable { /** * 获取列名 - * + * * @return 列名 */ public String getName() { @@ -117,7 +204,7 @@ public class Column implements Serializable, Cloneable { /** * 设置列名 - * + * * @param name 列名 * @return this */ @@ -128,17 +215,17 @@ public class Column implements Serializable, Cloneable { /** * 获取字段类型的枚举 - * + * * @return 阻断类型枚举 * @since 4.5.8 */ public JdbcType getTypeEnum() { return JdbcType.valueOf(this.type); } - + /** * 获取类型,对应{@link java.sql.Types}中的类型 - * + * * @return 类型 */ public int getType() { @@ -147,7 +234,7 @@ public class Column implements Serializable, Cloneable { /** * 设置类型,对应java.sql.Types中的类型 - * + * * @param type 类型 * @return this */ @@ -155,19 +242,19 @@ public class Column implements Serializable, Cloneable { this.type = type; return this; } - + /** * 获取类型名称 - * + * * @return 类型名称 */ public String getTypeName() { return typeName; } - + /** * 设置类型名称 - * + * * @param typeName 类型名称 * @return this */ @@ -178,7 +265,7 @@ public class Column implements Serializable, Cloneable { /** * 获取大小或数据长度 - * + * * @return 大小或数据长度 */ public int getSize() { @@ -187,7 +274,7 @@ public class Column implements Serializable, Cloneable { /** * 设置大小或数据长度 - * + * * @param size 大小或数据长度 * @return this */ @@ -196,9 +283,29 @@ public class Column implements Serializable, Cloneable { return this; } + /** + * 获取小数位数 + * + * @return 大小或数据长度 + */ + public int getDigit() { + return digit; + } + + /** + * 设置小数位数 + * + * @param digit 小数位数 + * @return this + */ + public Column setDigit(int digit) { + this.digit = digit; + return this; + } + /** * 是否为可空 - * + * * @return 是否为可空 */ public boolean isNullable() { @@ -207,7 +314,7 @@ public class Column implements Serializable, Cloneable { /** * 设置是否为可空 - * + * * @param isNullable 是否为可空 * @return this */ @@ -218,7 +325,7 @@ public class Column implements Serializable, Cloneable { /** * 获取注释 - * + * * @return 注释 */ public String getComment() { @@ -227,7 +334,7 @@ public class Column implements Serializable, Cloneable { /** * 设置注释 - * + * * @param comment 注释 * @return this */ @@ -235,6 +342,50 @@ public class Column implements Serializable, Cloneable { this.comment = comment; return this; } + + /** + * 是否自增 + * + * @return 是否自增 + * @since 5.4.3 + */ + public boolean isAutoIncrement() { + return autoIncrement; + } + + /** + * 设置是否自增 + * + * @param autoIncrement 是否自增 + * @return this + * @since 5.4.3 + */ + public Column setAutoIncrement(boolean autoIncrement) { + this.autoIncrement = autoIncrement; + return this; + } + + /** + * 是否主键 + * + * @return 是否主键 + * @since 5.4.3 + */ + public boolean isPk() { + return isPk; + } + + /** + * 设置是否主键 + * + * @param isPk 是否主键 + * @return this + * @since 5.4.3 + */ + public Column setPk(boolean isPk) { + this.isPk = isPk; + return this; + } // ----------------------------------------------------- Getters and Setters end @Override diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 414caac5d..839692761 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -186,8 +186,10 @@ public class MetaUtil { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - String catalog = getCataLog(conn); - String schema = getSchema(conn); + final String catalog = getCataLog(conn); + table.setCatalog(catalog); + final String schema = getSchema(conn); + table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); @@ -213,7 +215,7 @@ public class MetaUtil { try (ResultSet rs = metaData.getColumns(catalog, schema, tableName, null)) { if (null != rs) { while (rs.next()) { - table.setColumn(Column.create(tableName, rs)); + table.setColumn(Column.create(table, rs)); } } } diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/Table.java b/hutool-db/src/main/java/cn/hutool/db/meta/Table.java index 62a12f5e0..86a4975f7 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/Table.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/Table.java @@ -9,18 +9,31 @@ import java.util.Set; /** * 数据库表信息 - * - * @author loolly * + * @author loolly */ public class Table implements Serializable, Cloneable { private static final long serialVersionUID = -810699625961392983L; - /** 表名 */ + /** + * table所在的schema + */ + private String schema; + /** + * tables所在的catalog + */ + private String catalog; + /** + * 表名 + */ private String tableName; - /** 注释 */ + /** + * 注释 + */ private String comment; - /** 主键字段名列表 */ + /** + * 主键字段名列表 + */ private Set pkNames = new LinkedHashSet<>(); private final Map columns = new LinkedHashMap<>(); @@ -29,9 +42,10 @@ public class Table implements Serializable, Cloneable { } // ----------------------------------------------------- Constructor start + /** * 构造 - * + * * @param tableName 表名 */ public Table(String tableName) { @@ -40,9 +54,54 @@ public class Table implements Serializable, Cloneable { // ----------------------------------------------------- Constructor end // ----------------------------------------------------- Getters and Setters start + + /** + * 获取 schema + * + * @return schema + * @since 5.4.3 + */ + public String getSchema() { + return schema; + } + + /** + * 设置schema + * + * @param schema schema + * @return this + * @since 5.4.3 + */ + public Table setSchema(String schema) { + this.schema = schema; + return this; + } + + /** + * 获取catalog + * + * @return catalog + * @since 5.4.3 + */ + public String getCatalog() { + return catalog; + } + + /** + * 设置catalog + * + * @param catalog catalog + * @return this + * @since 5.4.3 + */ + public Table setCatalog(String catalog) { + this.catalog = catalog; + return this; + } + /** * 获取表名 - * + * * @return 表名 */ public String getTableName() { @@ -51,7 +110,7 @@ public class Table implements Serializable, Cloneable { /** * 设置表名 - * + * * @param tableName 表名 */ public void setTableName(String tableName) { @@ -60,7 +119,7 @@ public class Table implements Serializable, Cloneable { /** * 获取注释 - * + * * @return 注释 */ public String getComment() { @@ -69,7 +128,7 @@ public class Table implements Serializable, Cloneable { /** * 设置注释 - * + * * @param comment 注释 * @return this */ @@ -80,16 +139,27 @@ public class Table implements Serializable, Cloneable { /** * 获取主键列表 - * + * * @return 主键列表 */ public Set getPkNames() { return pkNames; } + /** + * 给定列名是否为主键 + * + * @param columnName 列名 + * @return 是否为主键 + * @since 5.4.3 + */ + public boolean isPk(String columnName){ + return getPkNames().contains(columnName); + } + /** * 设置主键列表 - * + * * @param pkNames 主键列表 */ public void setPkNames(Set pkNames) { @@ -99,7 +169,7 @@ public class Table implements Serializable, Cloneable { /** * 设置列对象 - * + * * @param column 列对象 * @return 自己 */ @@ -110,7 +180,7 @@ public class Table implements Serializable, Cloneable { /** * 获取某列信息 - * + * * @param name 列名 * @return 列对象 * @since 4.2.2 @@ -121,7 +191,7 @@ public class Table implements Serializable, Cloneable { /** * 获取所有字段元信息 - * + * * @return 字段元信息集合 * @since 4.5.8 */ @@ -131,7 +201,7 @@ public class Table implements Serializable, Cloneable { /** * 添加主键 - * + * * @param pkColumnName 主键的列名 * @return 自己 */ diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/Condition.java b/hutool-db/src/main/java/cn/hutool/db/sql/Condition.java index 017a2113a..d624f4242 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/Condition.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/Condition.java @@ -68,6 +68,11 @@ public class Condition extends CloneSupport { */ private Object secondValue; + /** + * 与前一个Condition连接的逻辑运算符,可以是and或or + */ + private LogicalOperator linkOperator = LogicalOperator.AND; + /** * 解析为Condition * @@ -282,6 +287,26 @@ public class Condition extends CloneSupport { this.secondValue = secondValue; } + /** + * 获取与前一个Condition连接的逻辑运算符,可以是and或or + * + * @return 与前一个Condition连接的逻辑运算符,可以是and或or + * @since 5.4.3 + */ + public LogicalOperator getLinkOperator() { + return linkOperator; + } + + /** + * 设置与前一个Condition连接的逻辑运算符,可以是and或or + * + * @param linkOperator 与前一个Condition连接的逻辑运算符,可以是and或or + * @since 5.4.3 + */ + public void setLinkOperator(LogicalOperator linkOperator) { + this.linkOperator = linkOperator; + } + // --------------------------------------------------------------- Getters and Setters end @Override diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/ConditionBuilder.java b/hutool-db/src/main/java/cn/hutool/db/sql/ConditionBuilder.java new file mode 100644 index 000000000..9f5df4d7d --- /dev/null +++ b/hutool-db/src/main/java/cn/hutool/db/sql/ConditionBuilder.java @@ -0,0 +1,116 @@ +package cn.hutool.db.sql; + +import cn.hutool.core.builder.Builder; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.StrUtil; + +import java.util.ArrayList; +import java.util.List; + +/** + * 多条件构建封装
    + * 可以将多个条件构建为SQL语句的一部分,并将参数值转换为占位符,并提取对应位置的参数值。
    + * 例如:name = ? AND type IN (?, ?) AND other LIKE ? + * + * @author looly + * @since 5.4.3 + */ +public class ConditionBuilder implements Builder { + private static final long serialVersionUID = 1L; + + /** + * 创建构建器 + * + * @param conditions 条件列表 + * @return {@link ConditionBuilder} + */ + public static ConditionBuilder of(Condition... conditions) { + return new ConditionBuilder(conditions); + } + + /** + * 条件数组 + */ + private final Condition[] conditions; + /** + * 占位符对应的值列表 + */ + private List paramValues; + + /** + * 构造 + * + * @param conditions 条件列表 + */ + public ConditionBuilder(Condition... conditions) { + this.conditions = conditions; + } + + /** + * 返回构建后的参数列表
    + * 此方法调用前必须调用{@link #build()} + * + * @return 参数列表 + */ + public List getParamValues() { + return ListUtil.unmodifiable(this.paramValues); + } + + /** + * 构建组合条件
    + * 例如:name = ? AND type IN (?, ?) AND other LIKE ? + * + * @return 构建后的SQL语句条件部分 + */ + @Override + public String build() { + if(null == this.paramValues){ + this.paramValues = new ArrayList<>(); + } else { + this.paramValues.clear(); + } + return build(this.paramValues); + } + + /** + * 构建组合条件
    + * 例如:name = ? AND type IN (?, ?) AND other LIKE ? + * + * @param paramValues 用于写出参数的List,构建时会将参数写入此List + * @return 构建后的SQL语句条件部分 + */ + public String build(List paramValues) { + if (ArrayUtil.isEmpty(conditions)) { + return StrUtil.EMPTY; + } + + final StringBuilder conditionStrBuilder = new StringBuilder(); + boolean isFirst = true; + for (Condition condition : conditions) { + // 添加逻辑运算符 + if (isFirst) { + isFirst = false; + } else { + // " AND " 或者 " OR " + conditionStrBuilder.append(CharUtil.SPACE).append(condition.getLinkOperator()).append(CharUtil.SPACE); + } + + // 构建条件部分:"name = ?"、"name IN (?,?,?)"、"name BETWEEN ?AND ?"、"name LIKE ?" + conditionStrBuilder.append(condition.toString(paramValues)); + } + return conditionStrBuilder.toString(); + } + + /** + * 构建组合条件
    + * 例如:name = ? AND type IN (?, ?) AND other LIKE ? + * + * @return 构建后的SQL语句条件部分 + */ + @Override + public String toString() { + return build(); + } +} diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java b/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java index f1775ddcf..893a6dae9 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java @@ -67,11 +67,11 @@ public class SqlBuilder implements Builder{ } // --------------------------------------------------------------- Enums end - final private StringBuilder sql = new StringBuilder(); + private final StringBuilder sql = new StringBuilder(); /** 字段列表(仅用于插入和更新) */ - final private List fields = new ArrayList<>(); + private final List fields = new ArrayList<>(); /** 占位符对应的值列表 */ - final private List paramValues = new ArrayList<>(); + private final List paramValues = new ArrayList<>(); /** 包装器 */ private Wrapper wrapper; @@ -101,7 +101,7 @@ public class SqlBuilder implements Builder{ * 插入会忽略空的字段名及其对应值,但是对于有字段名对应值为{@code null}的情况不忽略 * * @param entity 实体 - * @param dialectName 方言名 + * @param dialectName 方言名,用于对特殊数据库特殊处理 * @return 自己 */ public SqlBuilder insert(Entity entity, DialectName dialectName) { @@ -284,14 +284,18 @@ public class SqlBuilder implements Builder{ } /** - * 添加Where语句,所有逻辑之间为AND的关系 + * 添加Where语句,所有逻辑之间关系使用{@link Condition#setLinkOperator(LogicalOperator)} 定义 * * @param conditions 条件,当条件为空时,只添加WHERE关键字 * @return 自己 * @since 4.4.4 */ public SqlBuilder where(Condition... conditions) { - return where(LogicalOperator.AND, conditions); + if (ArrayUtil.isNotEmpty(conditions)) { + where(buildCondition(conditions)); + } + + return this; } /** @@ -301,17 +305,11 @@ public class SqlBuilder implements Builder{ * @param logicalOperator 逻辑运算符 * @param conditions 条件,当条件为空时,只添加WHERE关键字 * @return 自己 + * @deprecated logicalOperator放在Condition中了,因此请使用 {@link #where(Condition...)} */ + @Deprecated public SqlBuilder where(LogicalOperator logicalOperator, Condition... conditions) { - if (ArrayUtil.isNotEmpty(conditions)) { - if (null != wrapper) { - // 包装字段名 - conditions = wrapper.wrap(conditions); - } - where(buildCondition(logicalOperator, conditions)); - } - - return this; + return where(conditions); } /** @@ -366,14 +364,23 @@ public class SqlBuilder implements Builder{ * @param logicalOperator 逻辑运算符 * @param conditions 条件 * @return 自己 + * @deprecated logicalOperator放在Condition中了,因此请使用 {@link #having(Condition...)} */ + @Deprecated public SqlBuilder having(LogicalOperator logicalOperator, Condition... conditions) { + return having(conditions); + } + + /** + * 添加Having语句,所有逻辑之间关系使用{@link Condition#setLinkOperator(LogicalOperator)} 定义 + * + * @param conditions 条件 + * @return this + * @since 5.4.3 + */ + public SqlBuilder having(Condition... conditions) { if (ArrayUtil.isNotEmpty(conditions)) { - if (null != wrapper) { - // 包装字段名 - conditions = wrapper.wrap(conditions); - } - having(buildCondition(logicalOperator, conditions)); + having(buildCondition(conditions)); } return this; @@ -461,14 +468,23 @@ public class SqlBuilder implements Builder{ * @param logicalOperator 逻辑运算符 * @param conditions 条件 * @return 自己 + * @deprecated logicalOperator放在Condition中了,因此请使用 {@link #on(Condition...)} */ + @Deprecated public SqlBuilder on(LogicalOperator logicalOperator, Condition... conditions) { + return on(conditions); + } + + /** + * 配合JOIN的 ON语句,多表关联的条件语句,所有逻辑之间关系使用{@link Condition#setLinkOperator(LogicalOperator)} 定义 + * + * @param conditions 条件 + * @return this + * @since 5.4.3 + */ + public SqlBuilder on(Condition... conditions) { if (ArrayUtil.isNotEmpty(conditions)) { - if (null != wrapper) { - // 包装字段名 - conditions = wrapper.wrap(conditions); - } - on(buildCondition(logicalOperator, conditions)); + on(buildCondition(conditions)); } return this; @@ -582,34 +598,20 @@ public class SqlBuilder implements Builder{ * 构建组合条件
    * 例如:name = ? AND type IN (?, ?) AND other LIKE ? * - * @param logicalOperator 逻辑运算符 * @param conditions 条件对象 * @return 构建后的SQL语句条件部分 */ - private String buildCondition(LogicalOperator logicalOperator, Condition... conditions) { + private String buildCondition(Condition... conditions) { if (ArrayUtil.isEmpty(conditions)) { return StrUtil.EMPTY; } - if (null == logicalOperator) { - logicalOperator = LogicalOperator.AND; + + if (null != wrapper) { + // 包装字段名 + conditions = wrapper.wrap(conditions); } - final StringBuilder conditionStrBuilder = new StringBuilder(); - boolean isFirst = true; - for (Condition condition : conditions) { - // 添加逻辑运算符 - if (isFirst) { - isFirst = false; - } else { - // " AND " 或者 " OR " - conditionStrBuilder.append(StrUtil.SPACE).append(logicalOperator).append(StrUtil.SPACE); - } - - // 构建条件部分:"name = ?"、"name IN (?,?,?)"、"name BETWEEN ?AND ?"、"name LIKE ?" - conditionStrBuilder.append(condition.toString(this.paramValues)); - } - - return conditionStrBuilder.toString(); + return ConditionBuilder.of(conditions).build(this.paramValues); } /** diff --git a/hutool-db/src/test/java/cn/hutool/db/sql/ConditionBuilderTest.java b/hutool-db/src/test/java/cn/hutool/db/sql/ConditionBuilderTest.java new file mode 100644 index 000000000..6cfc7e6c9 --- /dev/null +++ b/hutool-db/src/test/java/cn/hutool/db/sql/ConditionBuilderTest.java @@ -0,0 +1,21 @@ +package cn.hutool.db.sql; + +import org.junit.Assert; +import org.junit.Test; + +public class ConditionBuilderTest { + + @Test + public void buildTest(){ + Condition c1 = new Condition("user", null); + Condition c2 = new Condition("name", "!= null"); + c2.setLinkOperator(LogicalOperator.OR); + Condition c3 = new Condition("group", "like %aaa"); + + final ConditionBuilder builder = ConditionBuilder.of(c1, c2, c3); + final String sql = builder.build(); + Assert.assertEquals("user IS NULL OR name IS NOT NULL AND group LIKE ?", sql); + Assert.assertEquals(1, builder.getParamValues().size()); + Assert.assertEquals("%aaa", builder.getParamValues().get(0)); + } +} diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 58d504a60..07dc1656d 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 405dc792a..454c0df47 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-extra @@ -19,10 +19,10 @@ 2.2 - 3.1.8.RELEASE + 3.2.0.RELEASE 1.3.0 2.3.30 - 4.9 + 4.9.01 3.0.11.RELEASE 1.6.2 0.1.55 @@ -30,7 +30,7 @@ 3.6 5.1.1 4.0.1 - 2.3.1.RELEASE + 2.3.4.RELEASE 3.3.0 @@ -193,7 +193,7 @@ org.apache.lucene lucene-analyzers-smartcn - 8.5.2 + 8.6.2 true diff --git a/hutool-extra/src/main/java/cn/hutool/extra/emoji/EmojiUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/emoji/EmojiUtil.java index b1b0612dc..1a55be8a5 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/emoji/EmojiUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/emoji/EmojiUtil.java @@ -1,15 +1,13 @@ package cn.hutool.extra.emoji; -import java.util.Arrays; -import java.util.List; -import java.util.Set; - import com.vdurmont.emoji.Emoji; import com.vdurmont.emoji.EmojiManager; import com.vdurmont.emoji.EmojiParser; -import com.vdurmont.emoji.EmojiTrie; import com.vdurmont.emoji.EmojiParser.FitzpatrickAction; +import java.util.List; +import java.util.Set; + /** * 基于https://github.com/vdurmont/emoji-java的Emoji表情工具类 *

    @@ -38,22 +36,7 @@ public class EmojiUtil { * @since 4.5.11 */ public static boolean containsEmoji(String str) { - if (str == null) { - return false; - } - final char[] chars = str.toCharArray(); - EmojiTrie.Matches status; - for (int i = 0; i < chars.length; i++) { - for (int j = i + 1; j <= chars.length; j++) { - status = EmojiManager.isEmoji(Arrays.copyOfRange(chars, i, j)); - if (status.impossibleMatch()) { - break; - } else if (status.exactMatch()) { - return true; - } - } - } - return false; + return EmojiManager.containsEmoji(str); } /** 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 8d4539793..2ae68c917 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 @@ -342,6 +342,20 @@ public class Ftp extends AbstractFtp { } } + /** + * 获取服务端目录状态。 + * @param path 路径 + * @return 状态int,服务端不同,返回不同 + * @since 5.4.3 + */ + public int stat(String path) { + try { + return this.client.stat(path); + } catch (IOException e) { + throw new FtpException(e); + } + } + /** * 判断ftp服务器文件是否存在 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/GlobalMailAccount.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/GlobalMailAccount.java index f881aeb29..ca9ab1796 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/GlobalMailAccount.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/GlobalMailAccount.java @@ -3,7 +3,7 @@ package cn.hutool.extra.mail; import cn.hutool.core.io.IORuntimeException; /** - * 全局邮件帐户,依赖于邮件配置文件{@link MailAccount#MAIL_SETTING_PATH}或{@link MailAccount#MAIL_SETTING_PATH2} + * 全局邮件帐户,依赖于邮件配置文件{@link MailAccount#MAIL_SETTING_PATHS} * * @author looly * @@ -35,31 +35,13 @@ public enum GlobalMailAccount { * @return MailAccount */ private MailAccount createDefaultAccount() { - MailAccount mailAccount = null; - try { - mailAccount = new MailAccount(MailAccount.MAIL_SETTING_PATH); - } catch (IORuntimeException e) { - //ignore - } - - // 寻找config/mailAccount.setting - if(null == mailAccount) { + for (String mailSettingPath : MailAccount.MAIL_SETTING_PATHS) { try { - mailAccount = new MailAccount(MailAccount.MAIL_SETTING_PATH2); - } catch (IORuntimeException e) { + return new MailAccount(mailSettingPath); + } catch (IORuntimeException ignore) { //ignore } } - - // 寻找mail.setting - if(null == mailAccount) { - try { - mailAccount = new MailAccount(MailAccount.MAIL_SETTING_PATH3); - } catch (IORuntimeException e) { - //ignore - } - } - - return mailAccount; + return null; } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java index 8641c990d..199c79368 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java @@ -32,9 +32,7 @@ public class MailAccount implements Serializable { private static final String MAIL_DEBUG = "mail.debug"; private static final String SPLIT_LONG_PARAMS = "mail.mime.splitlongparameters"; - public static final String MAIL_SETTING_PATH = "config/mail.setting"; - public static final String MAIL_SETTING_PATH2 = "config/mailAccount.setting"; - public static final String MAIL_SETTING_PATH3 = "mail.setting"; + public static final String[] MAIL_SETTING_PATHS = new String[]{"config/mail.setting", "config/mailAccount.setting", "mail.setting"}; /** * SMTP服务器域名 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java index 5465518ab..3c733c1e6 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java @@ -1,18 +1,17 @@ package cn.hutool.extra.qrcode; +import cn.hutool.core.img.ImgUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.CharsetUtil; +import com.google.zxing.EncodeHintType; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; + import java.awt.Color; import java.awt.Image; import java.io.File; import java.nio.charset.Charset; import java.util.HashMap; -import com.google.zxing.EncodeHintType; -import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; - -import cn.hutool.core.img.ImgUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.CharsetUtil; - /** * 二维码设置 * @@ -34,8 +33,8 @@ public class QrConfig { protected Integer backColor = WHITE; /** 边距1~4 */ protected Integer margin = 2; - /** 设置二维码中的信息量,可设置0-40的整数,二维码图片也会根据qrVersion而变化,0表示根据传入信息自动变化 */ - protected Integer qrVersion = 0; + /** 设置二维码中的信息量,可设置1-40的整数 */ + protected Integer qrVersion; /** 纠错级别 */ protected ErrorCorrectionLevel errorCorrection = ErrorCorrectionLevel.M; /** 编码 */ diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java index 1f2ffd4d1..52848aa7f 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java @@ -27,6 +27,7 @@ import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -91,8 +92,8 @@ public class ServletUtil { * @since 4.0.2 */ public static String getBody(ServletRequest request) { - try { - return IoUtil.read(request.getReader()); + try(final BufferedReader reader = request.getReader()) { + return IoUtil.read(reader); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/beetl/BeetlEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/beetl/BeetlEngine.java index d777af3e6..4bb6adfa8 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/beetl/BeetlEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/beetl/BeetlEngine.java @@ -1,7 +1,9 @@ package cn.hutool.extra.template.engine.beetl; -import java.io.IOException; - +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.extra.template.Template; +import cn.hutool.extra.template.TemplateConfig; +import cn.hutool.extra.template.TemplateEngine; import org.beetl.core.Configuration; import org.beetl.core.GroupTemplate; import org.beetl.core.ResourceLoader; @@ -11,10 +13,7 @@ import org.beetl.core.resource.FileResourceLoader; import org.beetl.core.resource.StringTemplateResourceLoader; import org.beetl.core.resource.WebAppResourceLoader; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.extra.template.Template; -import cn.hutool.extra.template.TemplateConfig; -import cn.hutool.extra.template.TemplateEngine; +import java.io.IOException; /** * Beetl模板引擎封装 @@ -109,7 +108,7 @@ public class BeetlEngine implements TemplateEngine { * @return {@link GroupTemplate} * @since 3.2.0 */ - private static GroupTemplate createGroupTemplate(ResourceLoader loader) { + private static GroupTemplate createGroupTemplate(ResourceLoader loader) { try { return createGroupTemplate(loader, Configuration.defaultConfiguration()); } catch (IOException e) { @@ -124,7 +123,7 @@ public class BeetlEngine implements TemplateEngine { * @param conf {@link Configuration} 配置文件 * @return {@link GroupTemplate} */ - private static GroupTemplate createGroupTemplate(ResourceLoader loader, Configuration conf) { + private static GroupTemplate createGroupTemplate(ResourceLoader loader, Configuration conf) { return new GroupTemplate(loader, conf); } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/beetl/BeetlUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/beetl/BeetlUtil.java index eb94ef293..36d182b56 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/beetl/BeetlUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/beetl/BeetlUtil.java @@ -106,7 +106,7 @@ public final class BeetlUtil { * @return {@link GroupTemplate} * @since 3.2.0 */ - public static GroupTemplate createGroupTemplate(ResourceLoader loader) { + public static GroupTemplate createGroupTemplate(ResourceLoader loader) { try { return createGroupTemplate(loader, Configuration.defaultConfiguration()); } catch (IOException e) { @@ -121,7 +121,7 @@ public final class BeetlUtil { * @param conf {@link Configuration} 配置文件 * @return {@link GroupTemplate} */ - public static GroupTemplate createGroupTemplate(ResourceLoader loader, Configuration conf) { + public static GroupTemplate createGroupTemplate(ResourceLoader loader, Configuration conf) { return new GroupTemplate(loader, conf); } @@ -273,7 +273,7 @@ public final class BeetlUtil { * @param resourceLoader {@link ResourceLoader} 匹配时对应的资源加载器 * @return {@link ResourceLoaderBuilder} */ - public ResourceLoaderBuilder add(Matcher matcher, ResourceLoader resourceLoader) { + public ResourceLoaderBuilder add(Matcher matcher, ResourceLoader resourceLoader) { compositeResourceLoader.addResourceLoader(matcher, resourceLoader); return this; } @@ -283,7 +283,7 @@ public final class BeetlUtil { * * @return {@link ResourceLoader} 资源加载器 */ - public ResourceLoader build() { + public ResourceLoader build() { return compositeResourceLoader; } } diff --git a/hutool-extra/src/test/java/cn/hutool/extra/emoji/EmojiUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/emoji/EmojiUtilTest.java index fe1620c58..f307e4fe3 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/emoji/EmojiUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/emoji/EmojiUtilTest.java @@ -16,4 +16,13 @@ public class EmojiUtilTest { String alias = EmojiUtil.toAlias("😄"); Assert.assertEquals(":smile:", alias); } + + @Test + public void containsEmojiTest() { + boolean containsEmoji = EmojiUtil.containsEmoji("测试一下是否包含EMOJ:😄"); + Assert.assertEquals(containsEmoji, true); + boolean notContainsEmoji = EmojiUtil.containsEmoji("不包含EMOJ:^_^"); + Assert.assertEquals(notContainsEmoji, false); + + } } diff --git a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java index f8600f8ff..cb44cfabd 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java @@ -4,10 +4,12 @@ import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Console; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import java.awt.Color; +import java.awt.image.BufferedImage; import java.io.File; /** @@ -19,9 +21,9 @@ import java.io.File; public class QrCodeUtilTest { @Test - @Ignore public void generateTest() { - QrCodeUtil.generate("https://hutool.cn/", 300, 300, FileUtil.file("e:/qrcode.jpg")); + final BufferedImage image = QrCodeUtil.generate("https://hutool.cn/", 300, 300); + Assert.assertNotNull(image); } @Test diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index bdc828c06..f5ca543b9 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/cookie/GlobalCookieManager.java b/hutool-http/src/main/java/cn/hutool/http/cookie/GlobalCookieManager.java index 541e53027..79d63d6a9 100644 --- a/hutool-http/src/main/java/cn/hutool/http/cookie/GlobalCookieManager.java +++ b/hutool-http/src/main/java/cn/hutool/http/cookie/GlobalCookieManager.java @@ -69,7 +69,7 @@ public class GlobalCookieManager { Map> cookieHeader; try { - cookieHeader = cookieManager.get(getURI(conn), new HashMap>(0)); + cookieHeader = cookieManager.get(getURI(conn), new HashMap<>(0)); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-http/src/main/java/cn/hutool/http/webservice/SoapClient.java b/hutool-http/src/main/java/cn/hutool/http/webservice/SoapClient.java index d2dfc74b6..b13120f25 100644 --- a/hutool-http/src/main/java/cn/hutool/http/webservice/SoapClient.java +++ b/hutool-http/src/main/java/cn/hutool/http/webservice/SoapClient.java @@ -93,7 +93,7 @@ public class SoapClient extends HttpBase { * 创建SOAP客户端,默认使用soap1.1版本协议 * * @param url WS的URL地址 - * @return {@link SoapClient} + * @return this */ public static SoapClient create(String url) { return new SoapClient(url); @@ -104,7 +104,7 @@ public class SoapClient extends HttpBase { * * @param url WS的URL地址 * @param protocol 协议,见{@link SoapProtocol} - * @return {@link SoapClient} + * @return this */ public static SoapClient create(String url, SoapProtocol protocol) { return new SoapClient(url, protocol); @@ -116,7 +116,7 @@ public class SoapClient extends HttpBase { * @param url WS的URL地址 * @param protocol 协议,见{@link SoapProtocol} * @param namespaceURI 方法上的命名空间URI - * @return {@link SoapClient} + * @return this * @since 4.5.6 */ public static SoapClient create(String url, SoapProtocol protocol, String namespaceURI) { diff --git a/hutool-http/src/test/java/cn/hutool/http/test/HttpRequestTest.java b/hutool-http/src/test/java/cn/hutool/http/test/HttpRequestTest.java index 99d6a4ca6..aa1d9d93f 100644 --- a/hutool-http/src/test/java/cn/hutool/http/test/HttpRequestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/test/HttpRequestTest.java @@ -133,4 +133,14 @@ public class HttpRequestTest { HttpRequest request = HttpUtil.createGet("http://localhost:8888/get"); Console.log(request.execute().body()); } + + @Test + @Ignore + public void getWithoutEncodeTest(){ + String url = "https://img-cloud.voc.com.cn/140/2020/09/03/c3d41b93e0d32138574af8e8b50928b376ca5ba61599127028157.png?imageMogr2/auto-orient/thumbnail/500&pid=259848"; + HttpRequest get = HttpUtil.createGet(url); + Console.log(get.getUrl()); + HttpResponse execute = get.execute(); + Console.log(execute.body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java index c1b3473cd..201605cae 100644 --- a/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java @@ -236,7 +236,8 @@ public class HttpUtilTest { map = HttpUtil.decodeParams(a, CharsetUtil.UTF_8); Assert.assertEquals("b", map.get("a").get(0)); Assert.assertEquals("d", map.get("c").get(0)); - Assert.assertEquals("", map.get("e").get(0)); + Assert.assertNull(map.get("e").get(0)); + Assert.assertNull(map.get("").get(0)); // 被编码的键和值被还原 a = "a=bbb&c=%E4%BD%A0%E5%A5%BD&%E5%93%88%E5%96%BD="; diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index ef68f3bee..617a9ca00 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-json diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue1075Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue1075Test.java new file mode 100644 index 000000000..8eb8f718a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue1075Test.java @@ -0,0 +1,33 @@ +package cn.hutool.json; + +import lombok.Data; +import org.junit.Assert; +import org.junit.Test; + +public class Issue1075Test { + + final String jsonStr = "{\"f1\":\"f1\",\"f2\":\"f2\",\"fac\":\"fac\"}"; + + @Test + public void testToBean() { + // 在不忽略大小写的情况下,f2、fac都不匹配 + ObjA o2 = JSONUtil.toBean(jsonStr, ObjA.class); + Assert.assertNull(o2.getFAC()); + Assert.assertNull(o2.getF2()); + } + + @Test + public void testToBeanIgnoreCase() { + // 在忽略大小写的情况下,f2、fac都匹配 + ObjA o2 = JSONUtil.parseObj(jsonStr, JSONConfig.create().setIgnoreCase(true)).toBean(ObjA.class); + Assert.assertEquals("fac", o2.getFAC()); + Assert.assertEquals("f2", o2.getF2()); + } + + @Data + public static class ObjA { + private String f1; + private String F2; + private String FAC; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue1101Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue1101Test.java new file mode 100644 index 000000000..cdb17b458 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue1101Test.java @@ -0,0 +1,96 @@ +package cn.hutool.json; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.TypeReference; +import lombok.Data; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Comparator; +import java.util.TreeSet; + +/** + * 测试转换为TreeSet是否成功。 TreeSet必须有默认的比较器 + */ +public class Issue1101Test { + + @Test + public void treeMapConvertTest(){ + String json = "[{\"nodeName\":\"admin\",\"treeNodeId\":\"00010001_52c95b83-2083-4138-99fb-e6e21f0c1277\",\"sort\":0,\"type\":10,\"parentId\":\"00010001\",\"children\":[],\"id\":\"52c95b83-2083-4138-99fb-e6e21f0c1277\",\"status\":true},{\"nodeName\":\"test\",\"treeNodeId\":\"00010001_97054a82-f8ff-46a1-b76c-cbacf6d18045\",\"sort\":0,\"type\":10,\"parentId\":\"00010001\",\"children\":[],\"id\":\"97054a82-f8ff-46a1-b76c-cbacf6d18045\",\"status\":true}]"; + final JSONArray objects = JSONUtil.parseArray(json); + final TreeSet convert = Convert.convert(new TypeReference>() { + }, objects); + Assert.assertEquals(2, convert.size()); + } + + @Test + public void test(){ + String json = "{\n" + + "\t\"children\": [{\n" + + "\t\t\"children\": [],\n" + + "\t\t\"id\": \"52c95b83-2083-4138-99fb-e6e21f0c1277\",\n" + + "\t\t\"nodeName\": \"admin\",\n" + + "\t\t\"parentId\": \"00010001\",\n" + + "\t\t\"sort\": 0,\n" + + "\t\t\"status\": true,\n" + + "\t\t\"treeNodeId\": \"00010001_52c95b83-2083-4138-99fb-e6e21f0c1277\",\n" + + "\t\t\"type\": 10\n" + + "\t}, {\n" + + "\t\t\"children\": [],\n" + + "\t\t\"id\": \"97054a82-f8ff-46a1-b76c-cbacf6d18045\",\n" + + "\t\t\"nodeName\": \"test\",\n" + + "\t\t\"parentId\": \"00010001\",\n" + + "\t\t\"sort\": 0,\n" + + "\t\t\"status\": true,\n" + + "\t\t\"treeNodeId\": \"00010001_97054a82-f8ff-46a1-b76c-cbacf6d18045\",\n" + + "\t\t\"type\": 10\n" + + "\t}],\n" + + "\t\"id\": \"00010001\",\n" + + "\t\"nodeName\": \"测试\",\n" + + "\t\"parentId\": \"0001\",\n" + + "\t\"sort\": 0,\n" + + "\t\"status\": true,\n" + + "\t\"treeNodeId\": \"00010001\",\n" + + "\t\"type\": 0\n" + + "}"; + + final JSONObject jsonObject = JSONUtil.parseObj(json); + + final TreeNode treeNode = JSONUtil.toBean(jsonObject, TreeNode.class); + Assert.assertEquals(2, treeNode.getChildren().size()); + + TreeNodeDto dto = new TreeNodeDto(); + BeanUtil.copyProperties(treeNode, dto, true); + Assert.assertEquals(2, dto.getChildren().size()); + } + + @Data + public static class TreeNodeDto { + private String id; + private String parentId; + private int sort; + private String nodeName; + private int type; + private Boolean status; + private String treeNodeId; + private TreeSet children = new TreeSet<>(Comparator.comparing(o -> o.id)); + } + + @Data + public static class TreeNode implements Comparable { + private String id; + private String parentId; + private int sort; + private String nodeName; + private int type; + private Boolean status; + private String treeNodeId; + private TreeSet children = new TreeSet<>(); + + @Override + public int compareTo(TreeNode o) { + return id.compareTo(o.getId()); + } + } +} diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index e08dfd720..19429e26e 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-log @@ -18,7 +18,7 @@ - 1.7.26 + 1.7.30 1.3.0-alpha5 1.2.17 2.13.3 @@ -71,11 +71,29 @@ ${tinylog.version} true + + org.tinylog + tinylog-api + 2.1.2 + true + org.jboss.logging jboss-logging ${jboss-logging.version} true + + org.slf4j + slf4j-simple + ${slf4j.version} + test + + + org.tinylog + tinylog-impl + 2.1.2 + test + diff --git a/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java b/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java index 4de619a00..5e6ec44e7 100644 --- a/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java +++ b/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java @@ -120,6 +120,7 @@ public class ConsoleLog extends AbstractLog { return; } + final Dict dict = Dict.create() .set("date", DateUtil.now()) .set("level", level.toString()) diff --git a/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog.java b/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog.java index 945527f43..57da23e8e 100644 --- a/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog.java +++ b/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog.java @@ -116,7 +116,7 @@ public class TinyLog extends AbstractLog { if(null == t){ t = getLastArgumentIfThrowable(arguments); } - LogEntryForwarder.forward(DEPTH, level, t, format, arguments); + LogEntryForwarder.forward(DEPTH, level, t, StrUtil.toString(format), arguments); } /** diff --git a/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog2.java b/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog2.java new file mode 100644 index 000000000..758a92e2f --- /dev/null +++ b/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog2.java @@ -0,0 +1,177 @@ +package cn.hutool.log.dialect.tinylog; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.log.AbstractLog; +import org.tinylog.Level; +import org.tinylog.configuration.Configuration; +import org.tinylog.format.AdvancedMessageFormatter; +import org.tinylog.format.MessageFormatter; +import org.tinylog.provider.LoggingProvider; +import org.tinylog.provider.ProviderRegistry; + +/** + * tinylog log.
    + * + * @author Looly + * + */ +public class TinyLog2 extends AbstractLog { + private static final long serialVersionUID = 1L; + + /** 堆栈增加层数,因为封装因此多了两层,此值用于正确获取当前类名 */ + private static final int DEPTH = 5; + + private final int level; + private final String name; + private static final LoggingProvider provider = ProviderRegistry.getLoggingProvider(); + private static final MessageFormatter formatter = new AdvancedMessageFormatter( + Configuration.getLocale(), + Configuration.isEscapingEnabled() + ); + + // ------------------------------------------------------------------------- Constructor + public TinyLog2(Class clazz) { + this(null == clazz ? StrUtil.NULL : clazz.getName()); + } + + public TinyLog2(String name) { + this.name = name; + this.level = provider.getMinimumLevel().ordinal(); + } + + @Override + public String getName() { + return this.name; + } + + // ------------------------------------------------------------------------- Trace + @Override + public boolean isTraceEnabled() { + return this.level <= Level.TRACE.ordinal(); + } + + @Override + public void trace(String fqcn, Throwable t, String format, Object... arguments) { + logIfEnabled(fqcn, Level.TRACE, t, format, arguments); + } + + // ------------------------------------------------------------------------- Debug + @Override + public boolean isDebugEnabled() { + return this.level <= Level.DEBUG.ordinal(); + } + + @Override + public void debug(String fqcn, Throwable t, String format, Object... arguments) { + logIfEnabled(fqcn, Level.DEBUG, t, format, arguments); + } + // ------------------------------------------------------------------------- Info + @Override + public boolean isInfoEnabled() { + return this.level <= Level.INFO.ordinal(); + } + + @Override + public void info(String fqcn, Throwable t, String format, Object... arguments) { + logIfEnabled(fqcn, Level.INFO, t, format, arguments); + } + + // ------------------------------------------------------------------------- Warn + @Override + public boolean isWarnEnabled() { + return this.level <= Level.WARN.ordinal(); + } + + @Override + public void warn(String fqcn, Throwable t, String format, Object... arguments) { + logIfEnabled(fqcn, Level.WARN, t, format, arguments); + } + + // ------------------------------------------------------------------------- Error + @Override + public boolean isErrorEnabled() { + return this.level <= Level.ERROR.ordinal(); + } + + @Override + public void error(String fqcn, Throwable t, String format, Object... arguments) { + logIfEnabled(fqcn, Level.ERROR, t, format, arguments); + } + + // ------------------------------------------------------------------------- Log + @Override + public void log(String fqcn, cn.hutool.log.level.Level level, Throwable t, String format, Object... arguments) { + logIfEnabled(fqcn, toTinyLevel(level), t, format, arguments); + } + + @Override + public boolean isEnabled(cn.hutool.log.level.Level level) { + return this.level <= toTinyLevel(level).ordinal(); + } + + /** + * 在对应日志级别打开情况下打印日志 + * @param fqcn 完全限定类名(Fully Qualified Class Name),用于定位日志位置 + * @param level 日志级别 + * @param t 异常,null则检查最后一个参数是否为Throwable类型,是则取之,否则不打印堆栈 + * @param format 日志消息模板 + * @param arguments 日志消息参数 + */ + private void logIfEnabled(String fqcn, Level level, Throwable t, String format, Object... arguments) { + // fqcn 无效 + if(null == t){ + t = getLastArgumentIfThrowable(arguments); + } + provider.log(DEPTH, null, level, t, formatter, StrUtil.toString(format), arguments); + } + + /** + * 将Hutool的Level等级转换为Tinylog的Level等级 + * + * @param level Hutool的Level等级 + * @return Tinylog的Level + * @since 4.0.3 + */ + private Level toTinyLevel(cn.hutool.log.level.Level level) { + Level tinyLevel; + switch (level) { + case TRACE: + tinyLevel = Level.TRACE; + break; + case DEBUG: + tinyLevel = Level.DEBUG; + break; + case INFO: + tinyLevel = Level.INFO; + break; + case WARN: + tinyLevel = Level.WARN; + break; + case ERROR: + tinyLevel = Level.ERROR; + break; + case OFF: + tinyLevel = Level.OFF; + break; + default: + throw new Error(StrUtil.format("Can not identify level: {}", level)); + } + return tinyLevel; + } + + /** + * 如果最后一个参数为异常参数,则获取之,否则返回null + * + * @param arguments 参数 + * @return 最后一个异常参数 + * @since 4.0.3 + */ + private static Throwable getLastArgumentIfThrowable(Object... arguments) { + if (ArrayUtil.isNotEmpty(arguments) && arguments[arguments.length - 1] instanceof Throwable) { + return (Throwable) arguments[arguments.length - 1]; + } else { + return null; + } + } +} diff --git a/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog2Factory.java b/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog2Factory.java new file mode 100644 index 000000000..ebb0cd96d --- /dev/null +++ b/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog2Factory.java @@ -0,0 +1,32 @@ +package cn.hutool.log.dialect.tinylog; + +import cn.hutool.log.Log; +import cn.hutool.log.LogFactory; + +/** + * TinyLog2 log.
    + * + * @author Looly + * + */ +public class TinyLog2Factory extends LogFactory { + + /** + * 构造 + */ + public TinyLog2Factory() { + super("TinyLog"); + checkLogExist(org.tinylog.Logger.class); + } + + @Override + public Log createLog(String name) { + return new TinyLog2(name); + } + + @Override + public Log createLog(Class clazz) { + return new TinyLog2(clazz); + } + +} diff --git a/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/package-info.java b/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/package-info.java index e9ed843c9..f935993e1 100644 --- a/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/package-info.java +++ b/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/package-info.java @@ -1,7 +1,7 @@ /** - * TinyLog的实现封装 - * - * @author looly + * tinylog的实现封装
    + * 封装包括TinyLog和TinyLog2 * + * @author looly */ package cn.hutool.log.dialect.tinylog; \ No newline at end of file diff --git a/hutool-log/src/main/resources/META-INF/services/cn.hutool.log.LogFactory b/hutool-log/src/main/resources/META-INF/services/cn.hutool.log.LogFactory index e2d708ccf..487035520 100644 --- a/hutool-log/src/main/resources/META-INF/services/cn.hutool.log.LogFactory +++ b/hutool-log/src/main/resources/META-INF/services/cn.hutool.log.LogFactory @@ -3,4 +3,5 @@ cn.hutool.log.dialect.log4j2.Log4j2LogFactory cn.hutool.log.dialect.log4j.Log4jLogFactory cn.hutool.log.dialect.commons.ApacheCommonsLogFactory cn.hutool.log.dialect.tinylog.TinyLogFactory +cn.hutool.log.dialect.tinylog.TinyLog2Factory cn.hutool.log.dialect.jboss.JbossLogFactory \ No newline at end of file diff --git a/hutool-log/src/test/java/cn/hutool/log/test/CustomLogTest.java b/hutool-log/src/test/java/cn/hutool/log/test/CustomLogTest.java index b455419e6..b72ffda6b 100644 --- a/hutool-log/src/test/java/cn/hutool/log/test/CustomLogTest.java +++ b/hutool-log/src/test/java/cn/hutool/log/test/CustomLogTest.java @@ -1,7 +1,5 @@ package cn.hutool.log.test; -import org.junit.Test; - import cn.hutool.log.Log; import cn.hutool.log.LogFactory; import cn.hutool.log.dialect.commons.ApacheCommonsLogFactory; @@ -11,7 +9,9 @@ import cn.hutool.log.dialect.jdk.JdkLogFactory; import cn.hutool.log.dialect.log4j.Log4jLogFactory; import cn.hutool.log.dialect.log4j2.Log4j2LogFactory; import cn.hutool.log.dialect.slf4j.Slf4jLogFactory; +import cn.hutool.log.dialect.tinylog.TinyLog2Factory; import cn.hutool.log.dialect.tinylog.TinyLogFactory; +import org.junit.Test; /** * 日志门面单元测试 @@ -28,9 +28,18 @@ public class CustomLogTest { LogFactory.setCurrentLogFactory(factory); Log log = LogFactory.get(); - log.info(null); log.info("This is custom '{}' log\n{}", factory.getName(), LINE); } + + @Test + public void consoleLogNullTest(){ + LogFactory factory = new ConsoleLogFactory(); + LogFactory.setCurrentLogFactory(factory); + Log log = LogFactory.get(); + + log.info(null); + log.info((String)null); + } @Test public void commonsLogTest(){ @@ -39,6 +48,7 @@ public class CustomLogTest { Log log = LogFactory.get(); log.info(null); + log.info((String)null); log.info("This is custom '{}' log\n{}", factory.getName(), LINE); } @@ -49,6 +59,18 @@ public class CustomLogTest { Log log = LogFactory.get(); log.info(null); + log.info((String)null); + log.info("This is custom '{}' log\n{}", factory.getName(), LINE); + } + + @Test + public void tinyLog2Test(){ + LogFactory factory = new TinyLog2Factory(); + LogFactory.setCurrentLogFactory(factory); + Log log = LogFactory.get(); + + log.info(null); + log.info((String)null); log.info("This is custom '{}' log\n{}", factory.getName(), LINE); } @@ -61,6 +83,7 @@ public class CustomLogTest { log.debug(null); log.debug("This is custom '{}' log\n{}", factory.getName(), LINE); log.info(null); + log.info((String)null); log.info("This is custom '{}' log\n{}", factory.getName(), LINE); } @@ -71,6 +94,7 @@ public class CustomLogTest { Log log = LogFactory.get(); log.info(null); + log.info((String)null); log.info("This is custom '{}' log\n{}", factory.getName(), LINE); } @@ -82,6 +106,7 @@ public class CustomLogTest { Log log = LogFactory.get(); log.info(null); + log.info((String)null); log.info("This is custom '{}' log\n{}", factory.getName(), LINE); } @@ -92,6 +117,7 @@ public class CustomLogTest { Log log = LogFactory.get(); log.info(null); + log.info((String)null); log.info("This is custom '{}' log\n{}", factory.getName(), LINE); } @@ -102,6 +128,7 @@ public class CustomLogTest { Log log = LogFactory.get(); log.info(null); + log.info((String)null); log.info("This is custom '{}' log\n{}", factory.getName(), LINE); } } diff --git a/hutool-log/src/test/java/cn/hutool/log/test/LogTest.java b/hutool-log/src/test/java/cn/hutool/log/test/LogTest.java index 2d4d1fcc8..3a26b50b3 100644 --- a/hutool-log/src/test/java/cn/hutool/log/test/LogTest.java +++ b/hutool-log/src/test/java/cn/hutool/log/test/LogTest.java @@ -37,4 +37,12 @@ public class LogTest { Exception e = new Exception("test Exception"); log.error("我是错误消息", e); } + + @Test + public void logNullTest(){ + final Log log = Log.get(); + log.debug(null); + log.info(null); + log.warn(null); + } } diff --git a/hutool-log/src/test/resources/tinylog.properties b/hutool-log/src/test/resources/tinylog.properties index 6078a18b4..2d7c470c9 100644 --- a/hutool-log/src/test/resources/tinylog.properties +++ b/hutool-log/src/test/resources/tinylog.properties @@ -5,7 +5,9 @@ # \u65e5\u5fd7\u5199\u51fa\u65b9\u5f0f\uff1a[console] [file] [jdbc] [logcat] [rollingfile] [sharedfile] [null] tinylog.writer = console +writer = console # \u65e5\u5fd7\u5199\u51fa\u7ea7\u522b\uff1aTRACE < DEBUG < INFO < WARNING < ERROR < OFF tinylog.level = debug # \u65e5\u5fd7\u6253\u5370\u683c\u5f0f -tinylog.format = [{date:HH:mm:ss}][{level}] {class}:{line} - {message} \ No newline at end of file +tinylog.format = [{date:HH:mm:ss}][{level}] {class}:{line} - {message} +writer.format = [{date:HH:mm:ss}][{level}] {class}:{line} - {message} \ No newline at end of file diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index cff64e77f..6d74e4a4b 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/TableUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/word/TableUtil.java index a3eb3c991..4a700a086 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/TableUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/TableUtil.java @@ -71,7 +71,7 @@ public class TableUtil { return; } - Map rowMap = null; + Map rowMap; if(rowBean instanceof Map) { rowMap = (Map) rowBean; } else if (BeanUtil.isBean(rowBean.getClass())) { @@ -79,6 +79,7 @@ public class TableUtil { } else { // 其它转为字符串默认输出 writeRow(row, CollUtil.newArrayList(rowBean), isWriteKeyAsHead); + return; } writeRow(row, rowMap, isWriteKeyAsHead); @@ -98,6 +99,7 @@ public class TableUtil { if (isWriteKeyAsHead) { writeRow(row, rowMap.keySet()); + row = row.getTable().createRow(); } writeRow(row, rowMap.values()); } diff --git a/hutool-poi/src/test/java/cn/hutool/poi/word/test/WordWriterTest.java b/hutool-poi/src/test/java/cn/hutool/poi/word/test/WordWriterTest.java index 11df0311b..b05131edd 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/word/test/WordWriterTest.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/word/test/WordWriterTest.java @@ -1,5 +1,6 @@ package cn.hutool.poi.word.test; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Console; import cn.hutool.poi.word.Word07Writer; @@ -8,6 +9,8 @@ import org.junit.Test; import java.awt.Font; import java.io.File; +import java.util.LinkedHashMap; +import java.util.Map; public class WordWriterTest { @@ -32,4 +35,18 @@ public class WordWriterTest { // 关闭 writer.close(); } + + @Test + @Ignore + public void writeTableTest(){ + final Word07Writer writer = new Word07Writer(); + Map map = new LinkedHashMap<>(); + map.put("姓名", "张三"); + map.put("年龄", "23"); + map.put("成绩", 88.32); + map.put("是否合格", true); + + writer.addTable(CollUtil.newArrayList(map)); + writer.flush(FileUtil.file("d:/test/test.docx")); + } } diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 871e2bad4..0bc5d4c63 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 232e9ed06..7ab60de80 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-setting diff --git a/hutool-setting/src/main/java/cn/hutool/setting/Setting.java b/hutool-setting/src/main/java/cn/hutool/setting/Setting.java index 2f3143a7a..44eaff7ce 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/Setting.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/Setting.java @@ -2,6 +2,7 @@ package cn.hutool.setting; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.resource.ClassPathResource; import cn.hutool.core.io.resource.FileResource; @@ -50,11 +51,21 @@ public class Setting extends AbsSetting implements Map { /** * 默认字符集 */ - public final static Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + public static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; /** * 默认配置文件扩展名 */ - public final static String EXT_NAME = "setting"; + public static final String EXT_NAME = "setting"; + + /** + * 构建一个空的Setting,用于手动加入参数 + * + * @return Setting + * @since 5.4.3 + */ + public static Setting create() { + return new Setting(); + } /** * 附带分组的键值对存储 @@ -153,6 +164,18 @@ public class Setting extends AbsSetting implements Map { Assert.notNull(url, "Null setting url define!"); this.init(new UrlResource(url), charset, isUseVariable); } + + /** + * 构造 + * + * @param resource Setting的Resource + * @param charset 字符集 + * @param isUseVariable 是否使用变量 + * @since 5.4.4 + */ + public Setting(Resource resource, Charset charset, boolean isUseVariable) { + this.init(resource, charset, isUseVariable); + } // ------------------------------------------------------------------------------------- Constructor end /** @@ -165,7 +188,7 @@ public class Setting extends AbsSetting implements Map { */ public boolean init(Resource resource, Charset charset, boolean isUseVariable) { if (resource == null) { - throw new NullPointerException("Null setting url define!"); + throw new NullPointerException("Null setting resource define!"); } this.settingUrl = resource.getUrl(); this.charset = charset; @@ -227,6 +250,18 @@ public class Setting extends AbsSetting implements Map { } /** + * 获得设定文件的URL + * + * @return 获得设定文件的路径 + * @since 5.4.3 + */ + public URL getSettingUrl() { + return this.settingUrl; + } + + /** + * 获得设定文件的路径 + * * @return 获得设定文件的路径 */ public String getSettingPath() { @@ -296,10 +331,10 @@ public class Setting extends AbsSetting implements Map { } /** - * 获取group分组下所有配置键值对,组成新的{@link Setting} + * 获取group分组下所有配置键值对,组成新的Setting * * @param group 分组 - * @return {@link Setting} + * @return Setting */ public Setting getSetting(String group) { final Setting setting = new Setting(); @@ -334,6 +369,17 @@ public class Setting extends AbsSetting implements Map { // --------------------------------------------------------------------------------- Functions + /** + * 持久化当前设置,会覆盖掉之前的设置
    + * 持久化不会保留之前的分组,注意如果配置文件在jar内部或者在exe中,此方法会报错。 + * + * @since 5.4.3 + */ + public void store() { + Assert.notNull(this.settingUrl, "Setting path must be not null !"); + store(FileUtil.file(this.settingUrl)); + } + /** * 持久化当前设置,会覆盖掉之前的设置
    * 持久化不会保留之前的分组 @@ -341,10 +387,21 @@ public class Setting extends AbsSetting implements Map { * @param absolutePath 设置文件的绝对路径 */ public void store(String absolutePath) { + store(FileUtil.touch(absolutePath)); + } + + /** + * 持久化当前设置,会覆盖掉之前的设置
    + * 持久化不会保留之前的分组 + * + * @param file 设置文件 + * @since 5.4.3 + */ + public void store(File file) { if (null == this.settingLoader) { settingLoader = new SettingLoader(this.groupedMap, this.charset, this.isUseVariable); } - settingLoader.store(absolutePath); + settingLoader.store(file); } /** diff --git a/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java b/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java index 542bceeaa..401e0a878 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java @@ -3,6 +3,7 @@ package cn.hutool.setting; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.resource.UrlResource; +import cn.hutool.core.lang.Assert; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ReUtil; @@ -10,6 +11,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.log.Log; import java.io.BufferedReader; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; @@ -168,9 +170,22 @@ public class SettingLoader { * @param absolutePath 设置文件的绝对路径 */ public void store(String absolutePath) { + store(FileUtil.touch(absolutePath)); + } + + /** + * 持久化当前设置,会覆盖掉之前的设置
    + * 持久化会不会保留之前的分组 + * + * @param file 设置文件 + * @since 5.4.3 + */ + public void store(File file) { + Assert.notNull(file, "File to store must be not null !"); + log.debug("Store Setting to [{}]...", file.getAbsolutePath()); PrintWriter writer = null; try { - writer = FileUtil.getPrintWriter(absolutePath, charset, false); + writer = FileUtil.getPrintWriter(file, charset, false); store(writer); } finally { IoUtil.close(writer); diff --git a/hutool-setting/src/main/java/cn/hutool/setting/dialect/Props.java b/hutool-setting/src/main/java/cn/hutool/setting/dialect/Props.java index 1fa4004ff..5c5c08513 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/dialect/Props.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/dialect/Props.java @@ -21,6 +21,7 @@ import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.log.StaticLog; +import cn.hutool.setting.Setting; import cn.hutool.setting.SettingRuntimeException; import java.io.BufferedReader; @@ -50,6 +51,16 @@ public final class Props extends Properties implements BasicTypeGetter, */ public final static String EXT_NAME = "properties"; + /** + * 构建一个空的Props,用于手动加入参数 + * + * @return Setting + * @since 5.4.3 + */ + public static Props create() { + return new Props(); + } + // ----------------------------------------------------------------------- 私有属性 start /** 属性文件的URL */ private URL propertiesFileUrl; diff --git a/hutool-setting/src/test/java/cn/hutool/setting/test/PropsTest.java b/hutool-setting/src/test/java/cn/hutool/setting/test/PropsTest.java index 817bc4901..63f792e0a 100644 --- a/hutool-setting/src/test/java/cn/hutool/setting/test/PropsTest.java +++ b/hutool-setting/src/test/java/cn/hutool/setting/test/PropsTest.java @@ -30,6 +30,7 @@ public class PropsTest { @Test public void propTest() { + //noinspection MismatchedQueryAndUpdateOfCollection Props props = new Props("test.properties"); String user = props.getProperty("user"); Assert.assertEquals(user, "root"); @@ -41,6 +42,7 @@ public class PropsTest { @Test @Ignore public void propTestForAbsPAth() { + //noinspection MismatchedQueryAndUpdateOfCollection Props props = new Props("d:/test.properties"); String user = props.getProperty("user"); Assert.assertEquals(user, "root"); diff --git a/hutool-setting/src/test/java/cn/hutool/setting/test/SettingTest.java b/hutool-setting/src/test/java/cn/hutool/setting/test/SettingTest.java index 5c1ec1886..d0f3a68ec 100644 --- a/hutool-setting/src/test/java/cn/hutool/setting/test/SettingTest.java +++ b/hutool-setting/src/test/java/cn/hutool/setting/test/SettingTest.java @@ -9,50 +9,63 @@ import cn.hutool.setting.Setting; /** * Setting单元测试 - * @author Looly * + * @author Looly */ public class SettingTest { - + @Test - public void settingTest(){ + public void settingTest() { + //noinspection MismatchedQueryAndUpdateOfCollection Setting setting = new Setting("test.setting", true); - + String driver = setting.getByGroup("driver", "demo"); Assert.assertEquals("com.mysql.jdbc.Driver", driver); - + //本分组变量替换 String user = setting.getByGroup("user", "demo"); Assert.assertEquals("rootcom.mysql.jdbc.Driver", user); - + //跨分组变量替换 String user2 = setting.getByGroup("user2", "demo"); Assert.assertEquals("rootcom.mysql.jdbc.Driver", user2); - + //默认值测试 String value = setting.getStr("keyNotExist", "defaultTest"); Assert.assertEquals("defaultTest", value); } - + @Test @Ignore - public void settingTestForAbsPath(){ + public void settingTestForAbsPath() { + //noinspection MismatchedQueryAndUpdateOfCollection Setting setting = new Setting("d:\\excel-plugin\\other.setting", true); Console.log(setting.getStr("a")); } - + @Test public void settingTestForCustom() { Setting setting = new Setting(); - + setting.put("group1", "user", "root"); setting.put("group2", "user", "root2"); setting.put("group3", "user", "root3"); setting.set("user", "root4"); - + Assert.assertEquals("root", setting.getByGroup("user", "group1")); Assert.assertEquals("root2", setting.getByGroup("user", "group2")); Assert.assertEquals("root3", setting.getByGroup("user", "group3")); Assert.assertEquals("root4", setting.get("user")); } + + /** + * 测试写出是否正常 + */ + @Test + public void storeTest() { + Setting setting = new Setting("test.setting"); + setting.set("testKey", "testValue"); + + setting.store(); + } } diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 5dbfb584a..19509870c 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 0ea477d5a..93fb286bb 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool-system @@ -26,7 +26,7 @@ com.github.oshi oshi-core - 5.2.0 + 5.2.5 provided diff --git a/pom.xml b/pom.xml index 2a63f4344..534a944a2 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.4.2 + 5.4.4-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/looly/hutool