diff --git a/CHANGELOG.md b/CHANGELOG.md index b3d7a6032..a025b72e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,23 @@ ------------------------------------------------------------------------------------------------------------- -# 5.4.1 (2020-08-24) +# 5.4.2 (2020-09-03) + +### 新特性 +* 【core 】 lock放在try外边(pr#1050@Github) +* 【core 】 MailUtil增加错误信息(issue#I1TAKJ@Gitee) +* 【core 】 JschUtil添加远程转发功能(pr#171@Gitee) +* 【db 】 AbstractDb增加executeBatch重载(issue#1053@Github) +* 【extra 】 新增方便引入SpringUtil的注解@EnableSpringUtil(pr#172@Gitee) +* 【poi 】 RowUtil增加插入和删除行(pr#1060@Github) + +### Bug修复# +* 【core 】 重新整理农历节假日,解决一个pr过来的玩笑导致的问题 +* 【poi 】 修复ExcelFileUtil.isXls判断问题(pr#1055@Github) + +------------------------------------------------------------------------------------------------------------- + +# 5.4.1 (2020-08-29) ### 新特性 * 【core 】 StrUtil增加firstNonXXX方法(issue#1020@Github) @@ -17,10 +33,16 @@ * 【core 】 增加Ipv4Util(pr#161@Gitee) * 【core 】 增加CalendarUtil和DateUtil增加isSameMonth方法(pr#161@Gitee) * 【core 】 Dict增加of方法(issue#1035@Github) +* 【core 】 StrUtil.wrapAll方法不明确修改改为wrapAllWithPair(issue#1042@Github) +* 【core 】 EnumUtil.getEnumAt负数返回null(pr#167@Gitee) +* 【core 】 ChineseDate增加天干地支和转换为公历方法(pr#169@Gitee) +* 【core 】 Img增加stroke描边方法(issue#1033@Github) ### Bug修复# * 【poi 】 修复ExcelBase.isXlsx方法判断问题(issue#I1S502@Gitee) * 【poi 】 修复Excel03SaxReader日期方法判断问题(pr#1026@Github) +* 【core 】 修复StrUtil.indexOf空指针问题(issue#1038@Github) +* 【extra 】 修复VelocityEngine编码问题和路径前缀问题(issue#I1T0IG@Gitee) ------------------------------------------------------------------------------------------------------------- @@ -130,7 +152,7 @@ * 【core 】 增加StrUtil.removeAny方法(issue#923@Github) * 【db 】 增加部分Connection参数支持(issue#924@Github) * 【core 】 FileUtil增加别名方法(pr#926@Github) -* 【poi 】 EcelReader中增加read重载,提供每个单元格单独处理的方法(issue#I1JZTL@Gitee) +* 【poi 】 ExcelReader中增加read重载,提供每个单元格单独处理的方法(issue#I1JZTL@Gitee) ### Bug修复 * 【json 】 修复append方法导致的JSONConfig传递失效问题(issue#906@Github) @@ -163,7 +185,7 @@ * 【extra 】 新增 QRCode base64 编码形式返回(pr#878@Github) * 【core 】 ImgUtil增加toBase64DateUri,URLUtil增加getDataUri方法 * 【core 】 IterUtil添加List转Map的工具方法(pr#123@Gitee) -* 【core 】 BeanValuePovider转换失败时,返回原数据,而非null +* 【core 】 BeanValueProvider转换失败时,返回原数据,而非null * 【core 】 支持BeanUtil.toBean(object, Map.class)转换(issue#I1I4HC@Gitee) * 【core 】 MapUtil和CollUtil增加clear方法(issue#I1I4HC@Gitee) * 【core 】 增加FontUtil,可定义pressText是否从中间(issue#I1HSWU@Gitee) diff --git a/README-EN.md b/README-EN.md index c2f6e5d79..a52d4dbc3 100644 --- a/README-EN.md +++ b/README-EN.md @@ -121,19 +121,19 @@ Each module can be introduced individually, or all modules can be introduced by cn.hutool hutool-all - 5.4.1 + 5.4.2 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:5.4.1' +compile 'cn.hutool:hutool-all:5.4.2' ``` ## Download -- [Maven1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.1/) -- [Maven2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.4.1/) +- [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/) > 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 decdce560..cb75a9441 100644 --- a/README.md +++ b/README.md @@ -120,21 +120,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.4.1 + 5.4.2 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:5.4.1' +compile 'cn.hutool:hutool-all:5.4.2' ``` ### 非Maven项目 点击以下任一链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.1/) -- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.4.1/) +- [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/) > 注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index ade65226e..8ae03c119 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.4.1 +5.4.2 diff --git a/docs/js/version.js b/docs/js/version.js index 9d97d0b3d..857a3c16d 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.4.1' \ No newline at end of file +var version = '5.4.2' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 3bbfd6f91..4bb97ec19 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index fe22ba9e4..44555a3f5 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index e4afbbe54..10c93edf5 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 603656421..1f6af9cd2 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index a5c2c9f26..85862ab35 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index a3a2831fd..3b101ee76 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 255dbf4b8..7bab37e2a 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-core diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java index 493b19309..62fc4d268 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java @@ -333,7 +333,7 @@ public class BeanDesc implements Serializable { // ------------------------------------------------------------------------------------------------------ Private method end /** - * 属性描述 + * 属性描述,包括了字段、getter、setter和相应的方法执行 * * @author looly */ diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/ConcurrentHashSet.java b/hutool-core/src/main/java/cn/hutool/core/collection/ConcurrentHashSet.java index c3fb02ddd..811c01ca4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/ConcurrentHashSet.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/ConcurrentHashSet.java @@ -105,7 +105,7 @@ public class ConcurrentHashSet extends AbstractSet implements java.io.Seri @Override public boolean remove(Object o) { - return map.remove(o) == PRESENT; + return PRESENT.equals(map.remove(o)); } @Override diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/AtomicBooleanConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/AtomicBooleanConverter.java index 1cbd873bc..874161bc6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/AtomicBooleanConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/AtomicBooleanConverter.java @@ -16,9 +16,6 @@ public class AtomicBooleanConverter extends AbstractConverter { @Override protected AtomicBoolean convertInternal(Object value) { - if (boolean.class == value.getClass()) { - return new AtomicBoolean((boolean) value); - } if (value instanceof Boolean) { return new AtomicBoolean((Boolean) value); } diff --git a/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java b/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java index db7424c91..98c5bbe21 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java @@ -1,11 +1,14 @@ package cn.hutool.core.date; import cn.hutool.core.convert.NumberChineseFormatter; +import cn.hutool.core.date.chinese.ChineseMonth; +import cn.hutool.core.date.chinese.GanZhi; +import cn.hutool.core.date.chinese.LunarFestival; +import cn.hutool.core.date.chinese.LunarInfo; +import cn.hutool.core.date.chinese.SolarTerms; import cn.hutool.core.util.StrUtil; -import java.util.ArrayList; import java.util.Date; -import java.util.List; /** @@ -15,62 +18,26 @@ import java.util.List; * @since 5.1.1 */ public class ChineseDate { - -// private static final Date baseDate = DateUtil.parseDate("1900-01-31"); /** * 1900-01-31 */ private static final long baseDate = -2206425943000L; - //农历年 private final int year; //农历月 private final int month; //农历日 private final int day; + + //公历年 + private final int gyear; + //公历月 + private final int gmonth; + //公里日 + private final int gday; + //是否闰年 private boolean leap; - private final String[] chineseNumber = {"一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二"}; - private final String[] chineseNumberName = {"正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "腊"}; - /** - * 此表来自:https://github.com/jjonline/calendar.js/blob/master/calendar.js - *农历表示: - * 1. 表示当年有无闰年,有的话,为闰月的月份,没有的话,为0。 - * 2-4.为除了闰月外的正常月份是大月还是小月,1为30天,0为29天。 - * 5. 表示闰月是大月还是小月,仅当存在闰月的情况下有意义。 - */ - private final long[] lunarInfo = new long[]{ - 0x04bd8,0x04ae0,0x0a570,0x054d5,0x0d260,0x0d950,0x16554,0x056a0,0x09ad0,0x055d2,//1900-1909 - 0x04ae0,0x0a5b6,0x0a4d0,0x0d250,0x1d255,0x0b540,0x0d6a0,0x0ada2,0x095b0,0x14977,//1910-1919 - 0x04970,0x0a4b0,0x0b4b5,0x06a50,0x06d40,0x1ab54,0x02b60,0x09570,0x052f2,0x04970,//1920-1929 - 0x06566,0x0d4a0,0x0ea50,0x16a95,0x05ad0,0x02b60,0x186e3,0x092e0,0x1c8d7,0x0c950,//1930-1939 - 0x0d4a0,0x1d8a6,0x0b550,0x056a0,0x1a5b4,0x025d0,0x092d0,0x0d2b2,0x0a950,0x0b557,//1940-1949 - 0x06ca0,0x0b550,0x15355,0x04da0,0x0a5b0,0x14573,0x052b0,0x0a9a8,0x0e950,0x06aa0,//1950-1959 - 0x0aea6,0x0ab50,0x04b60,0x0aae4,0x0a570,0x05260,0x0f263,0x0d950,0x05b57,0x056a0,//1960-1969 - 0x096d0,0x04dd5,0x04ad0,0x0a4d0,0x0d4d4,0x0d250,0x0d558,0x0b540,0x0b6a0,0x195a6,//1970-1979 - 0x095b0,0x049b0,0x0a974,0x0a4b0,0x0b27a,0x06a50,0x06d40,0x0af46,0x0ab60,0x09570,//1980-1989 - 0x04af5,0x04970,0x064b0,0x074a3,0x0ea50,0x06b58,0x05ac0,0x0ab60,0x096d5,0x092e0,//1990-1999 - 0x0c960,0x0d954,0x0d4a0,0x0da50,0x07552,0x056a0,0x0abb7,0x025d0,0x092d0,0x0cab5,//2000-2009 - 0x0a950,0x0b4a0,0x0baa4,0x0ad50,0x055d9,0x04ba0,0x0a5b0,0x15176,0x052b0,0x0a930,//2010-2019 - 0x07954,0x06aa0,0x0ad50,0x05b52,0x04b60,0x0a6e6,0x0a4e0,0x0d260,0x0ea65,0x0d530,//2020-2029 - 0x05aa0,0x076a3,0x096d0,0x04afb,0x04ad0,0x0a4d0,0x1d0b6,0x0d250,0x0d520,0x0dd45,//2030-2039 - 0x0b5a0,0x056d0,0x055b2,0x049b0,0x0a577,0x0a4b0,0x0aa50,0x1b255,0x06d20,0x0ada0,//2040-2049 - 0x14b63,0x09370,0x049f8,0x04970,0x064b0,0x168a6,0x0ea50, 0x06b20,0x1a6c4,0x0aae0,//2050-2059 - 0x092e0,0x0d2e3,0x0c960,0x0d557,0x0d4a0,0x0da50,0x05d55,0x056a0,0x0a6d0,0x055d4,//2060-2069 - 0x052d0,0x0a9b8,0x0a950,0x0b4a0,0x0b6a6,0x0ad50,0x055a0,0x0aba4,0x0a5b0,0x052b0,//2070-2079 - 0x0b273,0x06930,0x07337,0x06aa0,0x0ad50,0x14b55,0x04b60,0x0a570,0x054e4,0x0d160,//2080-2089 - 0x0e968,0x0d520,0x0daa0,0x16aa6,0x056d0,0x04ae0,0x0a9d4,0x0a2d0,0x0d150,0x0f252,//2090-2099 - }; - - //农历节日 *表示放假日 - private final String[] lFtv = new String[]{ - "0101 春节", "0102 大年初二", "0103 大年初三", "0104 大年初四", - "0105 大年初五", "0106 大年初六", "0107 大年初七", "0105 路神生日", - "0115 元宵节", "0202 龙抬头", "0219 观世音圣诞", "0404 寒食节", - "0408 佛诞节 ", "0505 端午节", "0606 天贶节 姑姑节", "0624 彝族火把节", - "0707 七夕情人节", "0714 鬼节(南方)", "0715 盂兰节", "0730 地藏节", - "0815 中秋节", "0909 重阳节", "1001 祭祖节", "1117 阿弥陀佛圣诞", - "1208 腊八节 释迦如来成道日", "1223 过小年", "1229 腊月二十九", "1230 除夕"}; /** * 构造方法传入日期 @@ -80,23 +47,22 @@ public class ChineseDate { public ChineseDate(Date date) { // 求出和1900年1月31日相差的天数 int offset = (int) ((date.getTime() - baseDate) / DateUnit.DAY.getMillis()); - // 计算农历年份 // 用offset减去每农历年的天数,计算当天是农历第几天,offset是当年的第几天 int daysOfYear; - int iYear = 1900; - final int maxYear = iYear + lunarInfo.length - 1; + int iYear = LunarInfo.BASE_YEAR; + final int maxYear = LunarInfo.getMaxYear(); for (; iYear <= maxYear; iYear++) { - daysOfYear = yearDays(iYear); + daysOfYear = LunarInfo.yearDays(iYear); if (offset < daysOfYear) { break; } offset -= daysOfYear; } - year = iYear; + year = iYear; // 计算农历月份 - int leapMonth = leapMonth(iYear); // 闰哪个月,1-12 + int leapMonth = LunarInfo.leapMonth(iYear); // 闰哪个月,1-12 // 用当年的天数offset,逐个减去每月(农历)的天数,求出当天是本月的第几天 int iMonth; int daysOfMonth = 0; @@ -105,14 +71,14 @@ public class ChineseDate { if (leapMonth > 0 && iMonth == (leapMonth + 1) && false == leap) { --iMonth; leap = true; - daysOfMonth = leapDays(year); + daysOfMonth = LunarInfo.leapDays(year); } else { - daysOfMonth = monthDays(year, iMonth); + daysOfMonth = LunarInfo.monthDays(year, iMonth); } offset -= daysOfMonth; // 解除闰月 - if (leap && iMonth == (leapMonth + 1)){ + if (leap && iMonth == (leapMonth + 1)) { leap = false; } } @@ -126,14 +92,20 @@ public class ChineseDate { --iMonth; } } - // offset小于0时,也要校正 if (offset < 0) { offset += daysOfMonth; --iMonth; } + month = iMonth; day = offset + 1; + + // 公历 + final DateTime dt = DateUtil.date(date); + gyear = dt.year(); + gmonth = dt.month() + 1; + gday = dt.dayOfMonth(); } /** @@ -149,6 +121,25 @@ public class ChineseDate { this.month = chineseMonth; this.year = chineseYear; this.leap = DateUtil.isLeapYear(chineseYear); + //先判断传入的月份是不是闰月 + int leapMonth = LunarInfo.leapMonth(chineseYear); + + final DateTime dateTime = lunar2solar(chineseYear, chineseMonth, chineseDay, chineseMonth == leapMonth); + if (null != dateTime) { + //初始化公历年 + this.gday = dateTime.dayOfMonth(); + //初始化公历月 + this.gmonth = dateTime.month() + 1; + //初始化公历日 + this.gyear = dateTime.year(); + } else { + //初始化公历年 + this.gday = -1; + //初始化公历月 + this.gmonth = -1; + //初始化公历日 + this.gyear = -1; + } } /** @@ -170,13 +161,14 @@ public class ChineseDate { return this.month; } + /** * 获得农历月份(中文,例如二月,十二月,或者润一月) * * @return 返回农历月份 */ public String getChineseMonth() { - return (leap ? "闰" : "") + NumberChineseFormatter.format(this.month, false) + "月"; + return ChineseMonth.getChineseMonthName(this.leap, this.month, false); } /** @@ -185,7 +177,7 @@ public class ChineseDate { * @return 返回农历月份称呼 */ public String getChineseMonthName() { - return (leap ? "闰" : "") + chineseNumberName[month - 1] + "月"; + return ChineseMonth.getChineseMonthName(this.leap, this.month, true); } /** @@ -228,25 +220,7 @@ public class ChineseDate { * @return 获得农历节日 */ public String getFestivals() { - StringBuilder currentChineseDate = new StringBuilder(); - if (month < 10) { - currentChineseDate.append('0'); - } - currentChineseDate.append(this.month); - - if (day < 10) { - currentChineseDate.append('0'); - } - currentChineseDate.append(this.day); - - List getFestivalsList = new ArrayList<>(); - for (String fv : lFtv) { - final List split = StrUtil.split(fv, ' '); - if (split.get(0).contentEquals(currentChineseDate)) { - getFestivalsList.add(split.get(1)); - } - } - return StrUtil.join(",", getFestivalsList); + return StrUtil.join(",", LunarFestival.getFestivals(this.month, this.day)); } /** @@ -265,8 +239,19 @@ public class ChineseDate { * @return 获得天干地支 */ public String getCyclical() { - int num = year - 1900 + 36; - return (cyclicalm(num)); + return GanZhi.cyclicalm(year - LunarInfo.BASE_YEAR + 36); + } + + /** + * 干支纪年信息 + * + * @return 获得天干地支的年月日信息 + */ + public String getCyclicalYMD() { + if (gyear >= LunarInfo.BASE_YEAR && gmonth > 0 && gday > 0){ + return (cyclicalm(gyear, gmonth, gday)); + } + return null; } /** @@ -287,64 +272,77 @@ public class ChineseDate { // ------------------------------------------------------- private method start /** - * 传入 月日的offset 传回干支, 0=甲子 - */ - private static String cyclicalm(int num) { - final String[] Gan = new String[]{"甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"}; - final String[] Zhi = new String[]{"子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"}; - return (Gan[num % 10] + Zhi[num % 12]); - } - - /** - * 传回农历 y年的总天数 + * 这里同步处理年月日的天干地支信息 * - * @param y 年 - * @return 总天数 + * @param Y 年 + * @param M 月 + * @param D 日 + * @return 天干地支信息 */ - private int yearDays(int y) { - int i, sum = 348; - for (i = 0x8000; i > 0x8; i >>= 1) { - if ((lunarInfo[y - 1900] & i) != 0) - sum += 1; + private String cyclicalm(int Y, int M, int D) { + String gzyear = GanZhi.cyclicalm(year - LunarInfo.BASE_YEAR + 36); + //天干地支处理 + //返回当月「节」为几日开始 + int firstNode = SolarTerms.getTerm(Y, (M * 2 - 1)); + // 依据12节气修正干支月 + String gzM = GanZhi.cyclicalm((Y - LunarInfo.BASE_YEAR) * 12 + M + 11); + if (D >= firstNode) { + gzM = GanZhi.cyclicalm((Y - LunarInfo.BASE_YEAR) * 12 + M + 12); } - return (sum + leapDays(y)); + int dayCyclical = (int) ((DateUtil.parseDate(Y + "-" + M + "-" + "1").getTime() - baseDate + 2592000000L) / DateUnit.DAY.getMillis()) + 10; + String gzD = GanZhi.cyclicalm(dayCyclical + D - 1); + return gzyear + "年" + gzM + "月" + gzD + "日"; } /** - * 传回农历 y年闰月的天数 + * 通过农历年月日信息 返回公历信息 提供给构造函数 * - * @param y 年 - * @return 闰月的天数 + * @param chineseYear 农历年 + * @param chineseMonth 农历月 + * @param chineseDay 农历日 + * @param isLeapMonth 传入的月是不是闰月 + * @return 公历信息 */ - private int leapDays(int y) { - if (leapMonth(y) != 0) { - return (lunarInfo[y - 1900] & 0x10000) != 0 ? 30 : 29; + private DateTime lunar2solar(int chineseYear, int chineseMonth, int chineseDay, boolean isLeapMonth) { + //超出了最大极限值 + if ((chineseYear == 2100 && chineseMonth == 12 && chineseDay > 1) || + (chineseYear == LunarInfo.BASE_YEAR && chineseMonth == 1 && chineseDay < 31)) { + return null; } - - return 0; - } - - - /** - * 传回农历 y年m月的总天数 - * - * @param y 年 - * @param m 月 - * @return 总天数 - */ - private int monthDays(int y, int m) { - return (lunarInfo[y - 1900] & (0x10000 >> m)) == 0 ? 29 : 30; - } - - /** - * 传回农历 y年闰哪个月 1-12 , 没闰传回 0 - * - * @param y 年 - * @return 润的月 - */ - private int leapMonth(int y) { - return (int) (lunarInfo[y - 1900] & 0xf); + int day = LunarInfo.monthDays(chineseYear, chineseMonth); + int _day = day; + if (isLeapMonth) { + _day = LunarInfo.leapDays(chineseYear); + } + //参数合法性效验 + if (chineseYear < LunarInfo.BASE_YEAR || chineseYear > 2100 || chineseDay > _day) { + return null; + } + //计算农历的时间差 + int offset = 0; + for (int i = LunarInfo.BASE_YEAR; i < chineseYear; i++) { + offset += LunarInfo.yearDays(i); + } + int leap; + boolean isAdd = false; + for (int i = 1; i < chineseMonth; i++) { + leap = LunarInfo.leapMonth(chineseYear); + if (false == isAdd) {//处理闰月 + if (leap <= i && leap > 0) { + offset += LunarInfo.leapDays(chineseYear); + isAdd = true; + } + } + offset += LunarInfo.monthDays(chineseYear, i); + } + //转换闰月农历 需补充该年闰月的前一个月的时差 + if (isLeapMonth) { + offset += day; + } + //1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点) -2203804800000 + return DateUtil.date(((offset + chineseDay - 31) * 86400000L) - 2203804800000L); } // ------------------------------------------------------- private method end -} + +} \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java b/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java index 47b93f7e3..e683352f6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java @@ -399,6 +399,15 @@ public class DateTime extends Date { return getField(DateField.MONTH); } + /** + * 获取月,从1开始计数 + * @return 月份,1表示一月 + * @since 5.4.1 + */ + public int monthBaseOne(){ + return month() + 1; + } + /** * 获得月份,从1开始计数
* 由于{@link Calendar} 中的月份按照0开始计数,导致某些需求容易误解,因此如果想用1表示一月,2表示二月则调用此方法 @@ -446,18 +455,18 @@ public class DateTime extends Date { } /** - * 获得指定日期是这个日期所在月份的第几天
+ * 获得指定日期是这个日期所在月份的第几天,从1开始 * - * @return 天 + * @return 天,1表示第一天 */ public int dayOfMonth() { return getField(DateField.DAY_OF_MONTH); } /** - * 获得指定日期是这个日期所在年份的第几天
+ * 获得指定日期是这个日期所在年份的第几天,从1开始 * - * @return 天 + * @return 天,1表示第一天 * @since 5.3.6 */ public int dayOfYear() { 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 new file mode 100644 index 000000000..c327ee11b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/date/chinese/ChineseMonth.java @@ -0,0 +1,27 @@ +package cn.hutool.core.date.chinese; + +/** + * 农历月份表示 + * + * @author looly + * @since 5.4.1 + */ +public class ChineseMonth { + + private static final String[] MONTH_NAME = {"一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二"}; + private static final String[] MONTH_NAME_TRADITIONAL = {"正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "腊"}; + + /** + * 获得农历月称呼
+ * 当为传统表示时,表示为二月,腊月,或者润正月等 + * 当为非传统表示时,二月,十二月,或者润一月等 + * + * @param isLeep 是否闰月 + * @param month 月份,从1开始 + * @param isTraditional 是否传统表示,例如一月传统表示为正月 + * @return 返回农历月份称呼 + */ + public static String getChineseMonthName(boolean isLeep, int month, boolean isTraditional) { + return (isLeep ? "闰" : "") + (isTraditional ? MONTH_NAME_TRADITIONAL : MONTH_NAME)[month - 1] + "月"; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/date/chinese/GanZhi.java b/hutool-core/src/main/java/cn/hutool/core/date/chinese/GanZhi.java new file mode 100644 index 000000000..1404058f8 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/date/chinese/GanZhi.java @@ -0,0 +1,23 @@ +package cn.hutool.core.date.chinese; + +/** + * 天干地支类 + * + * @author looly + * @since 5.4.1 + */ +public class GanZhi { + + private static final String[] GAN = new String[]{"甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"}; + private static final String[] ZHI = new String[]{"子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"}; + + /** + * 传入 月日的offset 传回干支, 0=甲子 + * + * @param num 月日的offset + * @return 干支 + */ + public static String cyclicalm(int num) { + return (GAN[num % 10] + ZHI[num % 12]); + } +} 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 new file mode 100644 index 000000000..b3771b963 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/date/chinese/LunarFestival.java @@ -0,0 +1,95 @@ +package cn.hutool.core.date.chinese; + +import cn.hutool.core.lang.Pair; +import cn.hutool.core.map.TableMap; + +import java.util.List; + +/** + * 节假日(农历)封装 + * + * @author looly + * @since 5.4.1 + */ +public class LunarFestival { + + //农历节日 *表示放假日 + // 来自:https://baike.baidu.com/item/%E4%B8%AD%E5%9B%BD%E4%BC%A0%E7%BB%9F%E8%8A%82%E6%97%A5/396100 + private static final TableMap, String> lFtv = new TableMap<>(16); + static{ + lFtv.put(new Pair<>(1, 1), "春节"); + lFtv.put(new Pair<>(1, 2), "犬日"); + 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, 7), "人日 人胜节"); + lFtv.put(new Pair<>(1, 8), "谷日 八仙日"); + lFtv.put(new Pair<>(1, 9), "天日 九皇会"); + lFtv.put(new Pair<>(1, 10), "地日 石头生日"); + lFtv.put(new Pair<>(1, 12), "火日 老鼠娶媳妇日"); + lFtv.put(new Pair<>(1, 13), "上(试)灯日 关公升天日"); + lFtv.put(new Pair<>(1, 15), "元宵节"); + lFtv.put(new Pair<>(1, 18), "落灯日"); + + // 二月 + lFtv.put(new Pair<>(2, 1), "中和节 太阳生日"); + lFtv.put(new Pair<>(2, 2), "龙抬头"); + lFtv.put(new Pair<>(2, 12), "花朝节"); + lFtv.put(new Pair<>(2, 19), "观世音圣诞"); + + // 三月 + lFtv.put(new Pair<>(3, 3), "上巳节"); + + // 四月 + lFtv.put(new Pair<>(4, 1), "祭雹神"); + lFtv.put(new Pair<>(4, 4), "文殊菩萨诞辰"); + lFtv.put(new Pair<>(4, 8), "佛诞节"); + + // 五月 + lFtv.put(new Pair<>(5, 5), "端午节"); + + // 六月 + lFtv.put(new Pair<>(6, 6), "晒衣节 姑姑节"); + lFtv.put(new Pair<>(6, 6), "天贶节"); + lFtv.put(new Pair<>(6, 24), "彝族火把节"); + + // 七月 + lFtv.put(new Pair<>(7, 7), "七夕"); + lFtv.put(new Pair<>(7, 14), "鬼节(南方)"); + lFtv.put(new Pair<>(7, 15), "中元节"); + lFtv.put(new Pair<>(7, 15), "盂兰盆节"); + lFtv.put(new Pair<>(7, 30), "地藏节"); + + // 八月 + lFtv.put(new Pair<>(8, 15), "中秋节"); + + // 九月 + lFtv.put(new Pair<>(9, 9), "重阳节"); + + // 十月 + lFtv.put(new Pair<>(10, 1), "祭祖节"); + lFtv.put(new Pair<>(10, 15), "下元节"); + + // 十一月 + lFtv.put(new Pair<>(11, 17), "阿弥陀佛圣诞"); + + // 腊月 + lFtv.put(new Pair<>(12, 8), "腊八节"); + lFtv.put(new Pair<>(12, 16), "尾牙"); + lFtv.put(new Pair<>(12, 23), "小年"); + lFtv.put(new Pair<>(12, 29), "除夕"); + lFtv.put(new Pair<>(12, 30), "除夕"); + } + + /** + * 获得节日列表 + * + * @param month 月 + * @param day 日 + * @return 获得农历节日 + */ + public static List getFestivals(int month, int day) { + return lFtv.getValues(new Pair<>(month, day)); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/date/chinese/LunarInfo.java b/hutool-core/src/main/java/cn/hutool/core/date/chinese/LunarInfo.java new file mode 100644 index 000000000..a088d0c05 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/date/chinese/LunarInfo.java @@ -0,0 +1,111 @@ +package cn.hutool.core.date.chinese; + +/** + * 阴历(农历)信息 + * + * @author looly + * @since 5.4.1 + */ +public class LunarInfo { + + public static final int BASE_YEAR = 1900; + + /** + * 此表来自:https://github.com/jjonline/calendar.js/blob/master/calendar.js + * 农历表示: + * 1. 表示当年有无闰年,有的话,为闰月的月份,没有的话,为0。 + * 2-4.为除了闰月外的正常月份是大月还是小月,1为30天,0为29天。 + * 5. 表示闰月是大月还是小月,仅当存在闰月的情况下有意义。 + */ + private static final long[] LUNAR_CODE = new long[]{ + 0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,//1900-1909 + 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,//1910-1919 + 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,//1920-1929 + 0x06566, 0x0d4a0, 0x0ea50, 0x16a95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,//1930-1939 + 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,//1940-1949 + 0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0,//1950-1959 + 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,//1960-1969 + 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6,//1970-1979 + 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,//1980-1989 + 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0,//1990-1999 + 0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,//2000-2009 + 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,//2010-2019 + 0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,//2020-2029 + 0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,//2030-2039 + 0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0,//2040-2049 + 0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0,//2050-2059 + 0x092e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4,//2060-2069 + 0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0,//2070-2079 + 0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160,//2080-2089 + 0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252,//2090-2099 + }; + + /** + * 获取支持的最大年(包括) + * + * @return 最大年(包括) + */ + public static int getMaxYear() { + return BASE_YEAR + LUNAR_CODE.length - 1; + } + + /** + * 传回农历 y年的总天数 + * + * @param y 年 + * @return 总天数 + */ + public static int yearDays(int y) { + int i, sum = 348; + for (i = 0x8000; i > 0x8; i >>= 1) { + if ((getCode(y) & i) != 0) + sum += 1; + } + return (sum + leapDays(y)); + } + + /** + * 传回农历 y年闰月的天数 + * + * @param y 年 + * @return 闰月的天数 + */ + public static int leapDays(int y) { + if (leapMonth(y) != 0) { + return (getCode(y) & 0x10000) != 0 ? 30 : 29; + } + + return 0; + } + + /** + * 传回农历 y年m月的总天数 + * + * @param y 年 + * @param m 月 + * @return 总天数 + */ + public static int monthDays(int y, int m) { + return (getCode(y) & (0x10000 >> m)) == 0 ? 29 : 30; + } + + /** + * 传回农历 y年闰哪个月 1-12 , 没闰传回 0 + * + * @param y 年 + * @return 润的月, 没闰传回 0 + */ + public static int leapMonth(int y) { + return (int) (getCode(y) & 0xf); + } + + /** + * 获取对应年的农历信息 + * + * @param year 年 + * @return 农历信息 + */ + private static long getCode(int year) { + return LUNAR_CODE[year - BASE_YEAR]; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/date/chinese/SolarTerms.java b/hutool-core/src/main/java/cn/hutool/core/date/chinese/SolarTerms.java new file mode 100644 index 000000000..b4f1cc934 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/date/chinese/SolarTerms.java @@ -0,0 +1,115 @@ +package cn.hutool.core.date.chinese; + +import cn.hutool.core.util.NumberUtil; + +/** + * 24节气相关信息 + * + * @author looly + * @since 5.4.1 + */ +public class SolarTerms { + + /** + * 根据节气修正干支月 + * + * @param y 月 + * @param n 节气 + * @return 干支月 + */ + public static int getTerm(int y, int n) { + if (y < 1900 || y > 2100) { + return -1; + } + if (n < 1 || n > 24) { + return -1; + } + + String _table = sTermInfo[y - 1900]; + Integer[] _info = new Integer[6]; + for (int i = 0; i < 6; i++) { + _info[i] = NumberUtil.parseInt("0x" + _table.substring(i * 5, 5 * (i + 1))); + } + String[] _calday = new String[24]; + for (int i = 0; i < 6; i++) { + _calday[4 * i] = _info[i].toString().substring(0, 1); + _calday[4 * i + 1] = _info[i].toString().substring(1, 3); + _calday[4 * i + 2] = _info[i].toString().substring(3, 4); + _calday[4 * i + 3] = _info[i].toString().substring(4, 6); + } + return NumberUtil.parseInt(_calday[n - 1]); + } + + /** + * 1900-2100各年的24节气日期速查表 + * 此表来自:https://github.com/jjonline/calendar.js/blob/master/calendar.js + */ + private static final String[] sTermInfo = new String[]{ + "9778397bd097c36b0b6fc9274c91aa", "97b6b97bd19801ec9210c965cc920e", "97bcf97c3598082c95f8c965cc920f", + "97bd0b06bdb0722c965ce1cfcc920f", "b027097bd097c36b0b6fc9274c91aa", "97b6b97bd19801ec9210c965cc920e", + "97bcf97c359801ec95f8c965cc920f", "97bd0b06bdb0722c965ce1cfcc920f", "b027097bd097c36b0b6fc9274c91aa", + "97b6b97bd19801ec9210c965cc920e", "97bcf97c359801ec95f8c965cc920f", "97bd0b06bdb0722c965ce1cfcc920f", + "b027097bd097c36b0b6fc9274c91aa", "9778397bd19801ec9210c965cc920e", "97b6b97bd19801ec95f8c965cc920f", + "97bd09801d98082c95f8e1cfcc920f", "97bd097bd097c36b0b6fc9210c8dc2", "9778397bd197c36c9210c9274c91aa", + "97b6b97bd19801ec95f8c965cc920e", "97bd09801d98082c95f8e1cfcc920f", "97bd097bd097c36b0b6fc9210c8dc2", + "9778397bd097c36c9210c9274c91aa", "97b6b97bd19801ec95f8c965cc920e", "97bcf97c3598082c95f8e1cfcc920f", + "97bd097bd097c36b0b6fc9210c8dc2", "9778397bd097c36c9210c9274c91aa", "97b6b97bd19801ec9210c965cc920e", + "97bcf97c3598082c95f8c965cc920f", "97bd097bd097c35b0b6fc920fb0722", "9778397bd097c36b0b6fc9274c91aa", + "97b6b97bd19801ec9210c965cc920e", "97bcf97c3598082c95f8c965cc920f", "97bd097bd097c35b0b6fc920fb0722", + "9778397bd097c36b0b6fc9274c91aa", "97b6b97bd19801ec9210c965cc920e", "97bcf97c359801ec95f8c965cc920f", + "97bd097bd097c35b0b6fc920fb0722", "9778397bd097c36b0b6fc9274c91aa", "97b6b97bd19801ec9210c965cc920e", + "97bcf97c359801ec95f8c965cc920f", "97bd097bd097c35b0b6fc920fb0722", "9778397bd097c36b0b6fc9274c91aa", + "97b6b97bd19801ec9210c965cc920e", "97bcf97c359801ec95f8c965cc920f", "97bd097bd07f595b0b6fc920fb0722", + "9778397bd097c36b0b6fc9210c8dc2", "9778397bd19801ec9210c9274c920e", "97b6b97bd19801ec95f8c965cc920f", + "97bd07f5307f595b0b0bc920fb0722", "7f0e397bd097c36b0b6fc9210c8dc2", "9778397bd097c36c9210c9274c920e", + "97b6b97bd19801ec95f8c965cc920f", "97bd07f5307f595b0b0bc920fb0722", "7f0e397bd097c36b0b6fc9210c8dc2", + "9778397bd097c36c9210c9274c91aa", "97b6b97bd19801ec9210c965cc920e", "97bd07f1487f595b0b0bc920fb0722", + "7f0e397bd097c36b0b6fc9210c8dc2", "9778397bd097c36b0b6fc9274c91aa", "97b6b97bd19801ec9210c965cc920e", + "97bcf7f1487f595b0b0bb0b6fb0722", "7f0e397bd097c35b0b6fc920fb0722", "9778397bd097c36b0b6fc9274c91aa", + "97b6b97bd19801ec9210c965cc920e", "97bcf7f1487f595b0b0bb0b6fb0722", "7f0e397bd097c35b0b6fc920fb0722", + "9778397bd097c36b0b6fc9274c91aa", "97b6b97bd19801ec9210c965cc920e", "97bcf7f1487f531b0b0bb0b6fb0722", + "7f0e397bd097c35b0b6fc920fb0722", "9778397bd097c36b0b6fc9274c91aa", "97b6b97bd19801ec9210c965cc920e", + "97bcf7f1487f531b0b0bb0b6fb0722", "7f0e397bd07f595b0b6fc920fb0722", "9778397bd097c36b0b6fc9274c91aa", + "97b6b97bd19801ec9210c9274c920e", "97bcf7f0e47f531b0b0bb0b6fb0722", "7f0e397bd07f595b0b0bc920fb0722", + "9778397bd097c36b0b6fc9210c91aa", "97b6b97bd197c36c9210c9274c920e", "97bcf7f0e47f531b0b0bb0b6fb0722", + "7f0e397bd07f595b0b0bc920fb0722", "9778397bd097c36b0b6fc9210c8dc2", "9778397bd097c36c9210c9274c920e", + "97b6b7f0e47f531b0723b0b6fb0722", "7f0e37f5307f595b0b0bc920fb0722", "7f0e397bd097c36b0b6fc9210c8dc2", + "9778397bd097c36b0b70c9274c91aa", "97b6b7f0e47f531b0723b0b6fb0721", "7f0e37f1487f595b0b0bb0b6fb0722", + "7f0e397bd097c35b0b6fc9210c8dc2", "9778397bd097c36b0b6fc9274c91aa", "97b6b7f0e47f531b0723b0b6fb0721", + "7f0e27f1487f595b0b0bb0b6fb0722", "7f0e397bd097c35b0b6fc920fb0722", "9778397bd097c36b0b6fc9274c91aa", + "97b6b7f0e47f531b0723b0b6fb0721", "7f0e27f1487f531b0b0bb0b6fb0722", "7f0e397bd097c35b0b6fc920fb0722", + "9778397bd097c36b0b6fc9274c91aa", "97b6b7f0e47f531b0723b0b6fb0721", "7f0e27f1487f531b0b0bb0b6fb0722", + "7f0e397bd097c35b0b6fc920fb0722", "9778397bd097c36b0b6fc9274c91aa", "97b6b7f0e47f531b0723b0b6fb0721", + "7f0e27f1487f531b0b0bb0b6fb0722", "7f0e397bd07f595b0b0bc920fb0722", "9778397bd097c36b0b6fc9274c91aa", + "97b6b7f0e47f531b0723b0787b0721", "7f0e27f0e47f531b0b0bb0b6fb0722", "7f0e397bd07f595b0b0bc920fb0722", + "9778397bd097c36b0b6fc9210c91aa", "97b6b7f0e47f149b0723b0787b0721", "7f0e27f0e47f531b0723b0b6fb0722", + "7f0e397bd07f595b0b0bc920fb0722", "9778397bd097c36b0b6fc9210c8dc2", "977837f0e37f149b0723b0787b0721", + "7f07e7f0e47f531b0723b0b6fb0722", "7f0e37f5307f595b0b0bc920fb0722", "7f0e397bd097c35b0b6fc9210c8dc2", + "977837f0e37f14998082b0787b0721", "7f07e7f0e47f531b0723b0b6fb0721", "7f0e37f1487f595b0b0bb0b6fb0722", + "7f0e397bd097c35b0b6fc9210c8dc2", "977837f0e37f14998082b0787b06bd", "7f07e7f0e47f531b0723b0b6fb0721", + "7f0e27f1487f531b0b0bb0b6fb0722", "7f0e397bd097c35b0b6fc920fb0722", "977837f0e37f14998082b0787b06bd", + "7f07e7f0e47f531b0723b0b6fb0721", "7f0e27f1487f531b0b0bb0b6fb0722", "7f0e397bd097c35b0b6fc920fb0722", + "977837f0e37f14998082b0787b06bd", "7f07e7f0e47f531b0723b0b6fb0721", "7f0e27f1487f531b0b0bb0b6fb0722", + "7f0e397bd07f595b0b0bc920fb0722", "977837f0e37f14998082b0787b06bd", "7f07e7f0e47f531b0723b0b6fb0721", + "7f0e27f1487f531b0b0bb0b6fb0722", "7f0e397bd07f595b0b0bc920fb0722", "977837f0e37f14998082b0787b06bd", + "7f07e7f0e47f149b0723b0787b0721", "7f0e27f0e47f531b0b0bb0b6fb0722", "7f0e397bd07f595b0b0bc920fb0722", + "977837f0e37f14998082b0723b06bd", "7f07e7f0e37f149b0723b0787b0721", "7f0e27f0e47f531b0723b0b6fb0722", + "7f0e397bd07f595b0b0bc920fb0722", "977837f0e37f14898082b0723b02d5", "7ec967f0e37f14998082b0787b0721", + "7f07e7f0e47f531b0723b0b6fb0722", "7f0e37f1487f595b0b0bb0b6fb0722", "7f0e37f0e37f14898082b0723b02d5", + "7ec967f0e37f14998082b0787b0721", "7f07e7f0e47f531b0723b0b6fb0722", "7f0e37f1487f531b0b0bb0b6fb0722", + "7f0e37f0e37f14898082b0723b02d5", "7ec967f0e37f14998082b0787b06bd", "7f07e7f0e47f531b0723b0b6fb0721", + "7f0e37f1487f531b0b0bb0b6fb0722", "7f0e37f0e37f14898082b072297c35", "7ec967f0e37f14998082b0787b06bd", + "7f07e7f0e47f531b0723b0b6fb0721", "7f0e27f1487f531b0b0bb0b6fb0722", "7f0e37f0e37f14898082b072297c35", + "7ec967f0e37f14998082b0787b06bd", "7f07e7f0e47f531b0723b0b6fb0721", "7f0e27f1487f531b0b0bb0b6fb0722", + "7f0e37f0e366aa89801eb072297c35", "7ec967f0e37f14998082b0787b06bd", "7f07e7f0e47f149b0723b0787b0721", + "7f0e27f1487f531b0b0bb0b6fb0722", "7f0e37f0e366aa89801eb072297c35", "7ec967f0e37f14998082b0723b06bd", + "7f07e7f0e47f149b0723b0787b0721", "7f0e27f0e47f531b0723b0b6fb0722", "7f0e37f0e366aa89801eb072297c35", + "7ec967f0e37f14998082b0723b06bd", "7f07e7f0e37f14998083b0787b0721", "7f0e27f0e47f531b0723b0b6fb0722", + "7f0e37f0e366aa89801eb072297c35", "7ec967f0e37f14898082b0723b02d5", "7f07e7f0e37f14998082b0787b0721", + "7f07e7f0e47f531b0723b0b6fb0722", "7f0e36665b66aa89801e9808297c35", "665f67f0e37f14898082b0723b02d5", + "7ec967f0e37f14998082b0787b0721", "7f07e7f0e47f531b0723b0b6fb0722", "7f0e36665b66a449801e9808297c35", + "665f67f0e37f14898082b0723b02d5", "7ec967f0e37f14998082b0787b06bd", "7f07e7f0e47f531b0723b0b6fb0721", + "7f0e36665b66a449801e9808297c35", "665f67f0e37f14898082b072297c35", "7ec967f0e37f14998082b0787b06bd", + "7f07e7f0e47f531b0723b0b6fb0721", "7f0e26665b66a449801e9808297c35", "665f67f0e37f1489801eb072297c35", + "7ec967f0e37f14998082b0787b06bd", "7f07e7f0e47f531b0723b0b6fb0721", "7f0e27f1487f531b0b0bb0b6fb0722"}; +} diff --git a/hutool-core/src/main/java/cn/hutool/core/date/chinese/package-info.java b/hutool-core/src/main/java/cn/hutool/core/date/chinese/package-info.java new file mode 100644 index 000000000..b4914f752 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/date/chinese/package-info.java @@ -0,0 +1,7 @@ +/** + * 农历相关类汇总,包括农历月、天干地支、农历节日、24节气等 + * + * @author looly + * + */ +package cn.hutool.core.date.chinese; \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/img/Img.java b/hutool-core/src/main/java/cn/hutool/core/img/Img.java index a369b620b..0d4b0bdab 100644 --- a/hutool-core/src/main/java/cn/hutool/core/img/Img.java +++ b/hutool-core/src/main/java/cn/hutool/core/img/Img.java @@ -13,6 +13,7 @@ import javax.imageio.ImageIO; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageOutputStream; import java.awt.AlphaComposite; +import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; @@ -20,6 +21,7 @@ import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; +import java.awt.Stroke; import java.awt.Toolkit; import java.awt.color.ColorSpace; import java.awt.geom.AffineTransform; @@ -554,6 +556,45 @@ public class Img implements Serializable { return this; } + /** + * 描边,此方法为向内描边,会覆盖图片相应的位置 + * + * @param color 描边颜色,默认黑色 + * @param width 边框粗细 + * @return this + * @since 5.4.1 + */ + public Img stroke(Color color, float width){ + return stroke(color, new BasicStroke(width)); + } + + /** + * 描边,此方法为向内描边,会覆盖图片相应的位置 + * + * @param color 描边颜色,默认黑色 + * @param stroke 描边属性,包括粗细、线条类型等,见{@link BasicStroke} + * @return this + * @since 5.4.1 + */ + public Img stroke(Color color, Stroke stroke){ + final BufferedImage image = ImgUtil.toBufferedImage(getValidSrcImg()); + int width = image.getWidth(null); + int height = image.getHeight(null); + Graphics2D g = image.createGraphics(); + + g.setColor(ObjectUtil.defaultIfNull(color, Color.BLACK)); + if(null != stroke){ + g.setStroke(stroke); + } + + g.drawRect(0, 0, width -1 , height - 1); + + g.dispose(); + this.targetImage = image; + + return this; + } + // ----------------------------------------------------------------------------------------------------------------- Write /** diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/UUID.java b/hutool-core/src/main/java/cn/hutool/core/lang/UUID.java index 44abcc3d3..0735769ab 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/UUID.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/UUID.java @@ -119,12 +119,14 @@ public class UUID implements java.io.Serializable, Comparable { public static UUID randomUUID(boolean isSecure) { final Random ng = isSecure ? Holder.numberGenerator : RandomUtil.getRandom(); - byte[] randomBytes = new byte[16]; + final byte[] randomBytes = new byte[16]; ng.nextBytes(randomBytes); + randomBytes[6] &= 0x0f; /* clear version */ randomBytes[6] |= 0x40; /* set to version 4 */ randomBytes[8] &= 0x3f; /* clear variant */ randomBytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(randomBytes); } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java b/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java index 916859dec..867f23b42 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java @@ -282,6 +282,8 @@ public class MapUtil { /** * 根据给定的Pair数组创建Map对象 * + * @param 键类型 + * @param 值类型 * @param pairs 键值对 * @return Map * @since 5.4.1 diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvRow.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvRow.java index aaeedee39..524bbfeaa 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvRow.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvRow.java @@ -107,7 +107,7 @@ public final class CsvRow implements List { * @since 5.3.6 */ public T toBean(Class clazz){ - return BeanUtil.mapToBean(getFieldMap(), clazz, true); + return BeanUtil.toBeanIgnoreError(getFieldMap(), clazz); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/DelegatedExecutorService.java b/hutool-core/src/main/java/cn/hutool/core/thread/DelegatedExecutorService.java index 84fbf66a8..b7d47b05b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/DelegatedExecutorService.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/DelegatedExecutorService.java @@ -1,5 +1,7 @@ package cn.hutool.core.thread; +import cn.hutool.core.lang.Assert; + import java.util.Collection; import java.util.List; import java.util.concurrent.AbstractExecutorService; @@ -18,69 +20,79 @@ import java.util.concurrent.TimeoutException; public class DelegatedExecutorService extends AbstractExecutorService { private final ExecutorService e; + /** + * 构造 + * + * @param executor {@link ExecutorService} + */ DelegatedExecutorService(ExecutorService executor) { + Assert.notNull(executor, "executor must be not null !"); e = executor; } - @SuppressWarnings("NullableProblems") + @Override public void execute(Runnable command) { e.execute(command); } + @Override public void shutdown() { e.shutdown(); } - @SuppressWarnings("NullableProblems") + @Override public List shutdownNow() { return e.shutdownNow(); } + @Override public boolean isShutdown() { return e.isShutdown(); } + @Override public boolean isTerminated() { return e.isTerminated(); } - @SuppressWarnings("NullableProblems") + @Override public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { return e.awaitTermination(timeout, unit); } - @SuppressWarnings("NullableProblems") + @Override public Future submit(Runnable task) { return e.submit(task); } - @SuppressWarnings("NullableProblems") + @Override public Future submit(Callable task) { return e.submit(task); } - @SuppressWarnings("NullableProblems") + @Override public Future submit(Runnable task, T result) { return e.submit(task, result); } - @SuppressWarnings("NullableProblems") + @Override public List> invokeAll(Collection> tasks) throws InterruptedException { return e.invokeAll(tasks); } - @SuppressWarnings("NullableProblems") + @Override public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException { return e.invokeAll(tasks, timeout, unit); } - @SuppressWarnings("NullableProblems") + @Override public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { return e.invokeAny(tasks); } + @Override public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return e.invokeAny(tasks, timeout, unit); diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/ExecutorBuilder.java b/hutool-core/src/main/java/cn/hutool/core/thread/ExecutorBuilder.java index d6f634f9a..228951a2e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/ExecutorBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/ExecutorBuilder.java @@ -1,5 +1,8 @@ package cn.hutool.core.thread; +import cn.hutool.core.builder.Builder; +import cn.hutool.core.util.ObjectUtil; + import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; @@ -11,12 +14,16 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import cn.hutool.core.builder.Builder; -import cn.hutool.core.util.ObjectUtil; - /** * {@link ThreadPoolExecutor} 建造者 * + *
+ *     1. 如果池中任务数 < corePoolSize     -》 放入立即执行
+ *     2. 如果池中任务数 > corePoolSize     -》 放入队列等待
+ *     3. 队列满                              -》 新建线程立即执行
+ *     4. 执行中的线程 > maxPoolSize        -》 触发handler(RejectedExecutionHandler)异常
+ * 
+ * * @author looly * @since 4.1.9 */ @@ -39,7 +46,7 @@ public class ExecutorBuilder implements Builder { */ private long keepAliveTime = TimeUnit.SECONDS.toNanos(60); /** - * 队列,用于存在未执行的线程 + * 队列,用于存放未执行的线程 */ private BlockingQueue workQueue; /** @@ -105,7 +112,7 @@ public class ExecutorBuilder implements Builder { * *
 	 * 1. {@link SynchronousQueue}    它将任务直接提交给线程而不保持它们。当运行线程小于maxPoolSize时会创建新线程,否则触发异常策略
-	 * 2. {@link LinkedBlockingQueue} 默认无界队列,当运行线程大于corePoolSize时始终放入此队列,此时maximumPoolSize无效。
+	 * 2. {@link LinkedBlockingQueue} 默认无界队列,当运行线程大于corePoolSize时始终放入此队列,此时maxPoolSize无效。
 	 *                        当构造LinkedBlockingQueue对象时传入参数,变为有界队列,队列满时,运行线程小于maxPoolSize时会创建新线程,否则触发异常策略
 	 * 3. {@link ArrayBlockingQueue}  有界队列,相对无界队列有利于控制队列大小,队列满时,运行线程小于maxPoolSize时会创建新线程,否则触发异常策略
 	 * 
diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/FinalizableDelegatedExecutorService.java b/hutool-core/src/main/java/cn/hutool/core/thread/FinalizableDelegatedExecutorService.java index 29749783d..d51d6e52b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/FinalizableDelegatedExecutorService.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/FinalizableDelegatedExecutorService.java @@ -8,6 +8,12 @@ import java.util.concurrent.ExecutorService; * @author loolly */ public class FinalizableDelegatedExecutorService extends DelegatedExecutorService { + + /** + * 构造 + * + * @param executor {@link ExecutorService} + */ FinalizableDelegatedExecutorService(ExecutorService executor) { super(executor); } diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/SyncFinisher.java b/hutool-core/src/main/java/cn/hutool/core/thread/SyncFinisher.java index 8555a7dbc..e922795ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/SyncFinisher.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/SyncFinisher.java @@ -16,9 +16,10 @@ import java.util.concurrent.ExecutorService; * ps: * //模拟1000个线程并发 * SyncFinisher sf = new SyncFinisher(1000); - * concurrencyTestUtil.run(() -> { + * sf.addWorker(() -> { * // 需要并发测试的业务代码 * }); + * sf.start() * * * diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java b/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java index ab8ba9ef1..b7a5fe9db 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java @@ -31,7 +31,7 @@ public class ThreadUtil { * @param corePoolSize 同时执行的线程数大小 * @return ExecutorService */ - public static ExecutorService newExecutor(int corePoolSize) { + public static ExecutorService newExecutor(int corePoolSize) { ExecutorBuilder builder = ExecutorBuilder.create(); if (corePoolSize > 0) { builder.setCorePoolSize(corePoolSize); diff --git a/hutool-core/src/main/java/cn/hutool/core/util/EnumUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/EnumUtil.java index 9fa54b2b3..fd33465bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/EnumUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/EnumUtil.java @@ -61,7 +61,7 @@ public class EnumUtil { */ public static > E getEnumAt(Class enumClass, int index) { final E[] enumConstants = enumClass.getEnumConstants(); - return index < enumConstants.length ? enumConstants[index] : null; + return index >= 0 && index < enumConstants.length ? enumConstants[index] : null; } /** 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 19b3469de..867b54d42 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 @@ -2731,14 +2731,14 @@ public class StrUtil { } /** - * 包装多个字符串 + * 使用单个字符包装多个字符串 * * @param prefixAndSuffix 前缀和后缀 * @param strs 多个字符串 * @return 包装的字符串数组 - * @since 4.0.7 + * @since 5.4.1 */ - public static String[] wrapAll(CharSequence prefixAndSuffix, CharSequence... strs) { + public static String[] wrapAllWithPair(CharSequence prefixAndSuffix, CharSequence... strs) { return wrapAll(prefixAndSuffix, prefixAndSuffix, strs); } @@ -2792,14 +2792,14 @@ public class StrUtil { } /** - * 包装多个字符串,如果已经包装,则不再包装 + * 使用成对的字符包装多个字符串,如果已经包装,则不再包装 * * @param prefixAndSuffix 前缀和后缀 * @param strs 多个字符串 * @return 包装的字符串数组 - * @since 4.0.7 + * @since 5.4.1 */ - public static String[] wrapAllIfMissing(CharSequence prefixAndSuffix, CharSequence... strs) { + public static String[] wrapAllWithPairIfMissing(CharSequence prefixAndSuffix, CharSequence... strs) { return wrapAllIfMissing(prefixAndSuffix, prefixAndSuffix, strs); } @@ -3427,7 +3427,7 @@ public class StrUtil { * @param start 起始位置,如果小于0,从0开始查找 * @return 位置 */ - public static int indexOf(final CharSequence str, char searchChar, int start) { + public static int indexOf(CharSequence str, char searchChar, int start) { if (str instanceof String) { return ((String) str).indexOf(searchChar, start); } else { @@ -3445,6 +3445,9 @@ public class StrUtil { * @return 位置 */ public static int indexOf(final CharSequence str, char searchChar, int start, int end) { + if(isEmpty(str)){ + return INDEX_NOT_FOUND; + } final int len = str.length(); if (start < 0 || start > len) { start = 0; @@ -3457,7 +3460,7 @@ public class StrUtil { return i; } } - return -1; + return INDEX_NOT_FOUND; } /** diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index f78d2259a..210282e10 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -45,4 +45,27 @@ public class ChineseDateTest { date = new ChineseDate(DateUtil.parseDate("1996-07-15")); Assert.assertEquals("丙子鼠年 五月三十", date.toString()); } + @Test + public void getCyclicalYMDTest(){ + //通过公历构建 + ChineseDate chineseDate = new ChineseDate(DateUtil.parseDate("1993-01-06")); + String cyclicalYMD = chineseDate.getCyclicalYMD(); + Assert.assertEquals("壬申年癸丑月丁亥日",cyclicalYMD); + } + + @Test + public void getCyclicalYMDTest2(){ + //通过农历构建 + ChineseDate chineseDate = new ChineseDate(1992,12,14); + String cyclicalYMD = chineseDate.getCyclicalYMD(); + Assert.assertEquals("壬申年癸丑月丁亥日",cyclicalYMD); + } + + @Test + public void getCyclicalYMDTest3(){ + //通过公历构建 + ChineseDate chineseDate = new ChineseDate(DateUtil.parseDate("2020-08-28")); + String cyclicalYMD = chineseDate.getCyclicalYMD(); + Assert.assertEquals("庚子年甲申月癸卯日",cyclicalYMD); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/img/ImgTest.java b/hutool-core/src/test/java/cn/hutool/core/img/ImgTest.java index dc47cdcd5..15682b41a 100644 --- a/hutool-core/src/test/java/cn/hutool/core/img/ImgTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/img/ImgTest.java @@ -55,4 +55,12 @@ public class ImgTest { .pressImage(ImgUtil.read("d:/test/617180969474805871.jpg"), new Rectangle(0, 0, 800, 800), 1f) .write(FileUtil.file("d:/test/pressImg_result.jpg")); } + + @Test + @Ignore + public void strokeTest(){ + Img.from(FileUtil.file("d:/test/公章3.png")) + .stroke(null, 2f) + .write(FileUtil.file("d:/test/stroke_result.png")); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/UUIDTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/UUIDTest.java new file mode 100644 index 000000000..6cae317a2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/UUIDTest.java @@ -0,0 +1,21 @@ +package cn.hutool.core.lang; + +import cn.hutool.core.collection.ConcurrentHashSet; +import cn.hutool.core.thread.ThreadUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Set; + +public class UUIDTest { + + /** + * 测试UUID是否存在重复问题 + */ + @Test + public void fastUUIDTest(){ + Set set = new ConcurrentHashSet<>(100); + ThreadUtil.concurrencyTest(100, ()-> set.add(UUID.fastUUID().toString())); + Assert.assertEquals(100, set.size()); + } +} 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 87f019945..a022b28ab 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 @@ -458,4 +458,13 @@ public class StrUtilTest { String cleanBlank = StrUtil.filter(" 你 好 ", c -> !CharUtil.isBlankChar(c)); Assert.assertEquals("你好", cleanBlank); } + + @Test + 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)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/TypeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/TypeUtilTest.java index 94ef42954..6b7cd97ec 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/TypeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/TypeUtilTest.java @@ -56,4 +56,5 @@ public class TypeUtilTest { public void service(String string) { } } + } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index d8be510af..4270b6b34 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java b/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java index 242c6e075..27daf2948 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java @@ -52,8 +52,8 @@ public class TaskTable implements Serializable { */ public TaskTable add(String id, CronPattern pattern, Task task) { final Lock writeLock = lock.writeLock(); + writeLock.lock(); try { - writeLock.lock(); if (ids.contains(id)) { throw new CronException("Id [{}] has been existed!", id); } @@ -75,8 +75,8 @@ public class TaskTable implements Serializable { */ public List getIds() { final Lock readLock = lock.readLock(); + readLock.lock(); try { - readLock.lock(); return Collections.unmodifiableList(this.ids); } finally { readLock.unlock(); @@ -91,8 +91,8 @@ public class TaskTable implements Serializable { */ public List getPatterns() { final Lock readLock = lock.readLock(); + readLock.lock(); try { - readLock.lock(); return Collections.unmodifiableList(this.patterns); } finally { readLock.unlock(); @@ -107,8 +107,8 @@ public class TaskTable implements Serializable { */ public List getTasks() { final Lock readLock = lock.readLock(); + readLock.lock(); try { - readLock.lock(); return Collections.unmodifiableList(this.tasks); } finally { readLock.unlock(); @@ -122,8 +122,8 @@ public class TaskTable implements Serializable { */ public void remove(String id) { final Lock writeLock = lock.writeLock(); + writeLock.lock(); try { - writeLock.lock(); final int index = ids.indexOf(id); if (index > -1) { tasks.remove(index); @@ -146,8 +146,8 @@ public class TaskTable implements Serializable { */ public boolean updatePattern(String id, CronPattern pattern) { final Lock writeLock = lock.writeLock(); + writeLock.lock(); try { - writeLock.lock(); final int index = ids.indexOf(id); if (index > -1) { patterns.set(index, pattern); @@ -168,8 +168,8 @@ public class TaskTable implements Serializable { */ public Task getTask(int index) { final Lock readLock = lock.readLock(); + readLock.lock(); try { - readLock.lock(); return tasks.get(index); } finally { readLock.unlock(); @@ -200,8 +200,8 @@ public class TaskTable implements Serializable { */ public CronPattern getPattern(int index) { final Lock readLock = lock.readLock(); + readLock.lock(); try { - readLock.lock(); return patterns.get(index); } finally { readLock.unlock(); @@ -250,8 +250,8 @@ public class TaskTable implements Serializable { */ public void executeTaskIfMatch(long millis) { final Lock readLock = lock.readLock(); + readLock.lock(); try { - readLock.lock(); executeTaskIfMatchInternal(millis); } finally { readLock.unlock(); diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 46f8e5247..f16d74ac6 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-crypto 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 1a3feb214..2cf2e477e 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 @@ -140,8 +140,8 @@ public class RC4 implements Serializable { */ public byte[] crypt(final byte[] msg) { final ReadLock readLock = this.lock.readLock(); - readLock.lock(); byte[] code; + readLock.lock(); try { final int[] sbox = this.sbox.clone(); code = new byte[msg.length]; diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java index 27261779d..fdc994ecb 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java @@ -204,7 +204,7 @@ public class SymmetricCrypto implements Serializable { } else { cipher.init(Cipher.ENCRYPT_MODE, secretKey, params); } - return cipher.doFinal(paddingDataWithZero(data, cipher.getBlockSize())); + return cipher.doFinal(paddingDataWithZero(data, cipher.getBlockSize())); } catch (Exception e) { throw new CryptoException(e); } finally { diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/ECIESTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/ECIESTest.java similarity index 90% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/ECIESTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/ECIESTest.java index ca83c61ad..1b83fe4f0 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/ECIESTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/ECIESTest.java @@ -1,4 +1,4 @@ -package cn.hutool.crypto.test; +package cn.hutool.crypto.test.asymmetric; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.asymmetric.ECIES; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/RSATest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/RSATest.java similarity index 97% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/RSATest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/RSATest.java index fcb9faa70..f8c309612 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/RSATest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/RSATest.java @@ -1,4 +1,4 @@ -package cn.hutool.crypto.test; +package cn.hutool.crypto.test.asymmetric; import cn.hutool.core.codec.Base64; import cn.hutool.core.util.ArrayUtil; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SM2Test.java similarity index 90% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SM2Test.java index a5ed710d8..56abb282d 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SM2Test.java @@ -1,4 +1,4 @@ -package cn.hutool.crypto.test; +package cn.hutool.crypto.test.asymmetric; import cn.hutool.core.codec.Base64; import cn.hutool.core.util.CharsetUtil; @@ -19,9 +19,8 @@ import java.security.PublicKey; /** * SM2算法单元测试 - * - * @author Looly, Gsealy * + * @author Looly, Gsealy */ public class SM2Test { @@ -71,7 +70,7 @@ public class SM2Test { byte[] decrypt = sm2.decrypt(encrypt, KeyType.PrivateKey); Assert.assertEquals("我是一段测试aaaa", StrUtil.str(decrypt, CharsetUtil.CHARSET_UTF_8)); } - + @Test public void sm2BcdTest() { String text = "我是一段测试aaaa"; @@ -138,6 +137,19 @@ public class SM2Test { KeyPair pair = SecureUtil.generateKeyPair("SM2"); + final SM2 sm2 = new SM2(pair.getPrivate(), pair.getPublic()); + + byte[] sign = sm2.sign(content.getBytes()); + boolean verify = sm2.verify(content.getBytes(), sign); + Assert.assertTrue(verify); + } + + @Test + public void sm2SignAndVerifyUseKeyTest2() { + String content = "我是Hanley."; + + KeyPair pair = SecureUtil.generateKeyPair("SM2"); + final SM2 sm2 = new SM2(// HexUtil.encodeHexStr(pair.getPrivate().getEncoded()), // HexUtil.encodeHexStr(pair.getPublic().getEncoded())// @@ -162,7 +174,7 @@ public class SM2Test { } @Test - public void sm2WithPointTest(){ + public void sm2WithPointTest() { String d = "FAB8BBE670FAE338C9E9382B9FB6485225C11A3ECB84C938F10F20A93B6215F0"; String x = "9EF573019D9A03B16B0BE44FC8A5B4E8E098F56034C97B312282DD0B4810AFC3"; String y = "CC759673ED0FC9B9DC7E6FA38F0E2B121E02654BF37EA6B63FAF2A0D6013EADF"; @@ -176,7 +188,7 @@ public class SM2Test { } @Test - public void sm2PlainWithPointTest(){ + public void sm2PlainWithPointTest() { // 测试地址:https://i.goto327.top/CryptTools/SM2.aspx?tdsourcetag=s_pctim_aiomsg String d = "FAB8BBE670FAE338C9E9382B9FB6485225C11A3ECB84C938F10F20A93B6215F0"; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SignTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SignTest.java similarity index 96% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/SignTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SignTest.java index 33d88203e..a16d4f562 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SignTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SignTest.java @@ -1,4 +1,4 @@ -package cn.hutool.crypto.test; +package cn.hutool.crypto.test.asymmetric; import cn.hutool.core.map.MapUtil; import org.junit.Assert; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/HmacTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/HmacTest.java similarity index 94% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/HmacTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/HmacTest.java index 1fd247926..b29e6d47a 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/HmacTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/HmacTest.java @@ -1,4 +1,4 @@ -package cn.hutool.crypto.test; +package cn.hutool.crypto.test.digest; import org.junit.Assert; import org.junit.Test; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/AESTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/AESTest.java similarity index 88% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/AESTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/AESTest.java index 87dd38f2f..f89c9cfc2 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/AESTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/AESTest.java @@ -1,4 +1,4 @@ -package cn.hutool.crypto.test; +package cn.hutool.crypto.test.symmetric; import cn.hutool.crypto.Mode; import cn.hutool.crypto.Padding; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/DesTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/DesTest.java similarity index 92% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/DesTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/DesTest.java index 649c35d2c..34554aeda 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/DesTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/DesTest.java @@ -1,4 +1,4 @@ -package cn.hutool.crypto.test; +package cn.hutool.crypto.test.symmetric; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.Mode; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/RC4Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/RC4Test.java similarity index 92% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/RC4Test.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/RC4Test.java index 5785182af..7869edc6a 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/RC4Test.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/RC4Test.java @@ -1,4 +1,4 @@ -package cn.hutool.crypto.test; +package cn.hutool.crypto.test.symmetric; import org.junit.Assert; import org.junit.Test; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SymmetricTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/SymmetricTest.java similarity index 95% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/SymmetricTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/SymmetricTest.java index 0580ac3fd..7b490c816 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SymmetricTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/SymmetricTest.java @@ -1,4 +1,4 @@ -package cn.hutool.crypto.test; +package cn.hutool.crypto.test.symmetric; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.RandomUtil; diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 9d59554bc..0c1e421ac 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-db 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 382c44ac7..3cf9bdadf 100644 --- a/hutool-db/src/main/java/cn/hutool/db/AbstractDb.java +++ b/hutool-db/src/main/java/cn/hutool/db/AbstractDb.java @@ -248,6 +248,25 @@ public abstract class AbstractDb implements Serializable { } } + /** + * 批量执行非查询语句 + * + * @param sql SQL + * @param paramsBatch 批量的参数 + * @return 每个SQL执行影响的行数 + * @throws SQLException SQL执行异常 + * @since 5.4.2 + */ + public int[] executeBatch(String sql, Iterable paramsBatch) throws SQLException { + Connection conn = null; + try { + conn = this.getConnection(); + return SqlExecutor.executeBatch(conn, sql, paramsBatch); + } finally { + this.closeConnection(conn); + } + } + /** * 批量执行非查询语句 * @@ -266,6 +285,24 @@ public abstract class AbstractDb implements Serializable { } } + /** + * 批量执行非查询语句 + * + * @param sqls SQL列表 + * @return 每个SQL执行影响的行数 + * @throws SQLException SQL执行异常 + * @since 5.4.2 + */ + public int[] executeBatch(Iterable sqls) throws SQLException { + Connection conn = null; + try { + conn = this.getConnection(); + return SqlExecutor.executeBatch(conn, sqls); + } finally { + this.closeConnection(conn); + } + } + // ---------------------------------------------------------------------------- CRUD start /** diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index fa3625e17..4a21e99dc 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index b1363d249..821d1b86d 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-extra diff --git a/hutool-extra/src/main/java/cn/hutool/extra/cglib/BeanCopierCache.java b/hutool-extra/src/main/java/cn/hutool/extra/cglib/BeanCopierCache.java index 7378b9090..91928c548 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/cglib/BeanCopierCache.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/cglib/BeanCopierCache.java @@ -1,11 +1,9 @@ package cn.hutool.extra.cglib; import cn.hutool.core.lang.SimpleCache; -import cn.hutool.core.lang.func.Func0; import cn.hutool.core.util.StrUtil; import net.sf.cglib.beans.BeanCopier; - -import java.beans.PropertyDescriptor; +import net.sf.cglib.core.Converter; /** * BeanCopier属性缓存
@@ -15,20 +13,39 @@ import java.beans.PropertyDescriptor; * @since 5.4.1 */ public enum BeanCopierCache { + /** + * BeanCopier属性缓存单例 + */ INSTANCE; private final SimpleCache cache = new SimpleCache<>(); /** - * 获得属性名和{@link PropertyDescriptor}Map映射 + * 获得类与转换器生成的key在{@link BeanCopier}的Map中对应的元素 * * @param srcClass 源Bean的类 * @param targetClass 目标Bean的类 - * @param supplier 缓存对象产生函数 - * @return 属性名和{@link PropertyDescriptor}Map映射 - * @since 5.4.1 + * @param converter 转换器 + * @return Map中对应的BeanCopier */ - public BeanCopier get(Class srcClass, Class targetClass, Func0 supplier) { - return this.cache.get(StrUtil.format("{}_{}", srcClass.getName(), srcClass.getName()), supplier); + public BeanCopier get(Class srcClass, Class targetClass, Converter converter) { + final String key = genKey(srcClass, targetClass, converter); + return cache.get(key, () -> BeanCopier.create(srcClass, targetClass, converter != null)); + } + + /** + * 获得类与转换器生成的key + * + * @param srcClass 源Bean的类 + * @param targetClass 目标Bean的类 + * @param converter 转换器 + * @return 属性名和Map映射的key + */ + private String genKey(Class srcClass, Class targetClass, Converter converter) { + String key = StrUtil.format("{}#{}", srcClass.getName(), targetClass.getName()); + if(null != converter){ + key += "#" + converter.getClass().getName(); + } + return key; } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/cglib/CglibUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/cglib/CglibUtil.java index cc0e56b0d..086aa2516 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/cglib/CglibUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/cglib/CglibUtil.java @@ -6,6 +6,12 @@ import net.sf.cglib.beans.BeanCopier; import net.sf.cglib.beans.BeanMap; import net.sf.cglib.core.Converter; +import java.util.Collection; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + /** * Cglib工具类 * @@ -39,7 +45,7 @@ public class CglibUtil { */ public static T copy(Object source, Class targetClass, Converter converter) { final T target = ReflectUtil.newInstanceIfPossible(targetClass); - copy(source, target); + copy(source, target, converter); return target; } @@ -66,18 +72,82 @@ public class CglibUtil { final Class sourceClass = source.getClass(); final Class targetClass = target.getClass(); - final BeanCopier beanCopier = BeanCopierCache.INSTANCE.get( - sourceClass, targetClass, - () -> BeanCopier.create(sourceClass, targetClass, null != converter)); + final BeanCopier beanCopier = BeanCopierCache.INSTANCE.get(sourceClass, targetClass, converter); beanCopier.copy(source, target, converter); } + /** + * 拷贝List Bean对象属性 + * + * @param 源bean类型 + * @param 目标bean类型 + * @param source 源bean对象list + * @param target 目标bean对象 + * @return 目标bean对象list + */ + public static List copyList(Collection source, Supplier target) { + return copyList(source, target, null, null); + } + + /** + * 拷贝List Bean对象属性 + * + * @param source 源bean对象list + * @param target 目标bean对象 + * @param converter 转换器,无需可传{@code null} + * @param 源bean类型 + * @param 目标bean类型 + * @return 目标bean对象list + * @since 5.4.1 + */ + public static List copyList(Collection source, Supplier target, Converter converter) { + return copyList(source, target, converter, null); + } + + /** + * 拷贝List Bean对象属性 + * + * @param source 源bean对象list + * @param target 目标bean对象 + * @param callback 回调对象 + * @param 源bean类型 + * @param 目标bean类型 + * @return 目标bean对象list + * @since 5.4.1 + */ + public static List copyList(Collection source, Supplier target, BiConsumer callback) { + return copyList(source, target, null, callback); + } + + /** + * 拷贝List Bean对象属性 + * + * @param source 源bean对象list + * @param target 目标bean对象 + * @param converter 转换器,无需可传{@code null} + * @param callback 回调对象 + * @param 源bean类型 + * @param 目标bean类型 + * @return 目标bean对象list + */ + public static List copyList(Collection source, Supplier target, Converter converter, BiConsumer callback) { + return source.stream().map(s -> { + T t = target.get(); + copy(source, t, converter); + if (callback != null) { + callback.accept(s, t); + } + return t; + }).collect(Collectors.toList()); + } + /** * 将Bean转换为Map * * @param bean Bean对象 * @return {@link BeanMap} + * @since 5.4.1 */ public static BeanMap toMap(Object bean) { return BeanMap.create(bean); diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java index fa8a40b52..51a875f35 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java @@ -11,9 +11,11 @@ import javax.activation.DataHandler; import javax.activation.DataSource; import javax.activation.FileDataSource; import javax.activation.FileTypeMap; +import javax.mail.Address; import javax.mail.Authenticator; import javax.mail.MessagingException; import javax.mail.Multipart; +import javax.mail.SendFailedException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.MimeBodyPart; @@ -355,6 +357,12 @@ public class Mail { try { return doSend(); } catch (MessagingException e) { + if(e instanceof SendFailedException){ + // 当地址无效时,显示更加详细的无效地址信息 + final Address[] invalidAddresses = ((SendFailedException) e).getInvalidAddresses(); + final String msg = StrUtil.format("Invalid Addresses: {}", ArrayUtil.toString(invalidAddresses)); + throw new MailException(msg, e); + } throw new MailException(e); } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/EnableSpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/EnableSpringUtil.java new file mode 100644 index 000000000..5710b1c07 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/EnableSpringUtil.java @@ -0,0 +1,19 @@ +package cn.hutool.extra.spring; + +import org.springframework.context.annotation.Import; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 启用SpringUtil, 即注入SpringUtil到容器中 + * + * @author sidian + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Import(SpringUtil.class) +public @interface EnableSpringUtil { +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschUtil.java index 62211cab1..31d7f309d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschUtil.java @@ -6,13 +6,7 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.net.LocalPortGenerater; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; -import com.jcraft.jsch.Channel; -import com.jcraft.jsch.ChannelExec; -import com.jcraft.jsch.ChannelSftp; -import com.jcraft.jsch.ChannelShell; -import com.jcraft.jsch.JSch; -import com.jcraft.jsch.JSchException; -import com.jcraft.jsch.Session; +import com.jcraft.jsch.*; import java.io.IOException; import java.io.InputStream; @@ -232,6 +226,31 @@ public class JschUtil { return false; } + /** + * 绑定ssh服务端的serverPort端口, 到host主机的port端口上.
+ * 即数据从ssh服务端的serverPort端口, 流经ssh客户端, 达到host:port上. + * + * @param session 与ssh服务端建立的会话 + * @param bindPort ssh服务端上要被绑定的端口 + * @param host 转发到的host + * @param port host上的端口 + * @return 成功与否 + * @throws JschRuntimeException 端口绑定失败异常 + * @since 5.4.2 + */ + public static boolean bindRemotePort(Session session, int bindPort, String host, int port) throws JschRuntimeException { + if (session != null && session.isConnected()) { + try { + session.setPortForwardingR(bindPort, host, port); + } catch (JSchException e) { + throw new JschRuntimeException(e, "From [{}] mapping to [{}] error!", bindPort, port); + } + return true; + } + return false; + } + + /** * 解除端口映射 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityEngine.java index f4415d789..73a97efb1 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityEngine.java @@ -1,11 +1,10 @@ package cn.hutool.extra.template.engine.velocity; -import org.apache.velocity.app.Velocity; - import cn.hutool.core.util.StrUtil; import cn.hutool.extra.template.Template; import cn.hutool.extra.template.TemplateConfig; import cn.hutool.extra.template.TemplateEngine; +import org.apache.velocity.app.Velocity; /** * Velocity模板引擎 @@ -16,6 +15,7 @@ import cn.hutool.extra.template.TemplateEngine; public class VelocityEngine implements TemplateEngine { private org.apache.velocity.app.VelocityEngine engine; + private TemplateConfig config; // --------------------------------------------------------------------------------- Constructor start /** @@ -47,6 +47,7 @@ public class VelocityEngine implements TemplateEngine { if(null == config){ config = TemplateConfig.DEFAULT; } + this.config = config; init(createEngine(config)); return this; } @@ -74,7 +75,25 @@ public class VelocityEngine implements TemplateEngine { if(null == this.engine){ init(TemplateConfig.DEFAULT); } - return VelocityTemplate.wrap(engine.getTemplate(resource)); + + // 目录前缀 + String root; + // 自定义编码 + String charsetStr = null; + if(null != this.config){ + root = this.config.getPath(); + charsetStr = this.config.getCharsetStr(); + + // 修正template目录,在classpath或者web_root模式下,按照配置添加默认前缀 + // 如果用户已经自行添加了前缀,则忽略之 + final TemplateConfig.ResourceMode resourceMode = this.config.getResourceMode(); + if(TemplateConfig.ResourceMode.CLASSPATH == resourceMode + || TemplateConfig.ResourceMode.WEB_ROOT == resourceMode){ + resource = StrUtil.addPrefixIfNot(resource, StrUtil.addSuffixIfNot(root, "/")); + } + } + + return VelocityTemplate.wrap(engine.getTemplate(resource, charsetStr)); } /** @@ -98,7 +117,9 @@ public class VelocityEngine implements TemplateEngine { // loader switch (config.getResourceMode()) { case CLASSPATH: - ve.setProperty("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + // 新版Velocity弃用 +// ve.setProperty("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + ve.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); break; case FILE: // path diff --git a/hutool-extra/src/test/java/cn/hutool/extra/spring/EnableSprintUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/spring/EnableSprintUtilTest.java new file mode 100644 index 000000000..cf202dc8d --- /dev/null +++ b/hutool-extra/src/test/java/cn/hutool/extra/spring/EnableSprintUtilTest.java @@ -0,0 +1,24 @@ +package cn.hutool.extra.spring; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author sidian + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = EnableSprintUtilTest.class) +@EnableSpringUtil +public class EnableSprintUtilTest { + + @Test + public void test() { + // 使用@EnableSpringUtil注解后, 能获取上下文 + Assert.assertNotNull(SpringUtil.getApplicationContext()); + // 不使用时, 为null +// Assert.assertNull(SpringUtil.getApplicationContext()); + } +} diff --git a/hutool-extra/src/test/java/cn/hutool/extra/ssh/JschUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/ssh/JschUtilTest.java index 7d29f1cd7..30cdf0c90 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/ssh/JschUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/ssh/JschUtilTest.java @@ -3,6 +3,7 @@ package cn.hutool.extra.ssh; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Console; import com.jcraft.jsch.Session; +import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -13,7 +14,7 @@ import org.junit.Test; * */ public class JschUtilTest { - + @Test @Ignore public void bindPortTest() { @@ -22,7 +23,22 @@ public class JschUtilTest { // 将堡垒机保护的内网8080端口映射到localhost,我们就可以通过访问http://localhost:8080/访问内网服务了 JschUtil.bindPort(session, "172.20.12.123", 8080, 8080); } - + + + @Test + @Ignore + public void bindRemotePort() throws InterruptedException { + // 建立会话 + Session session = JschUtil.getSession("looly.centos", 22, "test", "123456"); + // 绑定ssh服务端8089端口到本机的8000端口上 + boolean b = JschUtil.bindRemotePort(session, 8089, "localhost", 8000); + Assert.assertTrue(b); + // 保证一直运行 +// while (true){ +// Thread.sleep(3000); +// } + } + @Test @Ignore public void sftpTest() { diff --git a/hutool-extra/src/test/java/cn/hutool/extra/template/TemplateUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/template/TemplateUtilTest.java index b299f5b9b..77a273fe1 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/template/TemplateUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/template/TemplateUtilTest.java @@ -1,13 +1,5 @@ package cn.hutool.extra.template; -import java.io.File; -import java.util.HashMap; -import java.util.Map; - -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.lang.Dict; import cn.hutool.extra.template.TemplateConfig.ResourceMode; import cn.hutool.extra.template.engine.beetl.BeetlEngine; @@ -16,6 +8,13 @@ import cn.hutool.extra.template.engine.freemarker.FreemarkerEngine; import cn.hutool.extra.template.engine.rythm.RythmEngine; import cn.hutool.extra.template.engine.thymeleaf.ThymeleafEngine; import cn.hutool.extra.template.engine.velocity.VelocityEngine; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; /** * 模板引擎单元测试 @@ -99,10 +98,13 @@ public class TemplateUtilTest { //ClassPath模板 engine = TemplateUtil.createEngine( new TemplateConfig("templates", ResourceMode.CLASSPATH).setCustomEngine(VelocityEngine.class)); + template = engine.getTemplate("velocity_test.vtl"); + result = template.render(Dict.create().set("name", "hutool")); + Assert.assertEquals("你好,hutool", result); + template = engine.getTemplate("templates/velocity_test.vtl"); result = template.render(Dict.create().set("name", "hutool")); Assert.assertEquals("你好,hutool", result); - } @Test diff --git a/hutool-extra/src/test/java/cn/hutool/extra/template/VelocityTest.java b/hutool-extra/src/test/java/cn/hutool/extra/template/VelocityTest.java new file mode 100644 index 000000000..a042c2775 --- /dev/null +++ b/hutool-extra/src/test/java/cn/hutool/extra/template/VelocityTest.java @@ -0,0 +1,21 @@ +package cn.hutool.extra.template; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.extra.template.engine.velocity.VelocityEngine; +import org.junit.Assert; +import org.junit.Test; + +public class VelocityTest { + + @Test + public void charsetTest(){ + final TemplateConfig config = new TemplateConfig("templates", TemplateConfig.ResourceMode.CLASSPATH); + config.setCustomEngine(VelocityEngine.class); + config.setCharset(CharsetUtil.CHARSET_GBK); + final TemplateEngine engine = TemplateUtil.createEngine(config); + Template template = engine.getTemplate("velocity_test_gbk.vtl"); + String result = template.render(Dict.create().set("name", "hutool")); + Assert.assertEquals("你好,hutool", result); + } +} diff --git a/hutool-extra/src/test/resources/templates/velocity_test_gbk.vtl b/hutool-extra/src/test/resources/templates/velocity_test_gbk.vtl new file mode 100644 index 000000000..491be1b4f --- /dev/null +++ b/hutool-extra/src/test/resources/templates/velocity_test_gbk.vtl @@ -0,0 +1 @@ +,$name \ No newline at end of file diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index ac70bd00d..4069c806e 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 2637a30c5..875805e21 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -30,6 +30,7 @@ import java.net.HttpCookie; import java.net.HttpURLConnection; import java.net.Proxy; import java.net.URLStreamHandler; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -413,6 +414,18 @@ public class HttpRequest extends HttpBase { return this; } + /** + * 设置Cookie
+ * 自定义Cookie后会覆盖Hutool的默认Cookie行为 + * + * @param cookies Cookie值数组,如果为{@code null}则设置无效,使用默认Cookie行为 + * @return this + * @since 5.4.1 + */ + public HttpRequest cookie(Collection cookies) { + return cookie(CollUtil.isEmpty(cookies) ? null : cookies.toArray(new HttpCookie[0])); + } + /** * 设置Cookie
* 自定义Cookie后会覆盖Hutool的默认Cookie行为 diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 8525734ed..b865ed759 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-json diff --git a/hutool-json/src/test/java/cn/hutool/json/XMLTest.java b/hutool-json/src/test/java/cn/hutool/json/XMLTest.java index 8de0efaa1..dfd5e215e 100644 --- a/hutool-json/src/test/java/cn/hutool/json/XMLTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/XMLTest.java @@ -5,10 +5,11 @@ import org.junit.Test; public class XMLTest { - @SuppressWarnings("ConstantConditions") @Test public void toXmlTest(){ - final JSONObject put = JSONUtil.createObj().put("aaa", "你好").put("键2", "test"); + final JSONObject put = JSONUtil.createObj() + .set("aaa", "你好") + .set("键2", "test"); final String s = JSONUtil.toXmlStr(put); Assert.assertEquals("你好<键2>test", s); } diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 1b69f0cbd..8b550b067 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 360e90544..09caa0e37 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index b1b1f6e07..2c04fc90e 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -1,14 +1,11 @@ package cn.hutool.poi.excel; +import cn.hutool.core.io.IORuntimeException; +import org.apache.poi.poifs.filesystem.FileMagic; + import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.PushbackInputStream; - -import org.apache.poi.poifs.filesystem.FileMagic; - -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; /** * Excel文件工具类 @@ -26,9 +23,18 @@ public class ExcelFileUtil { * @return 是否为XLS格式的Excel文件(HSSF) */ public static boolean isXls(InputStream in) { - final PushbackInputStream pin = IoUtil.toPushbackStream(in, 8); + /* + * {@link java.io.PushbackInputStream} + * PushbackInputStream的markSupported()为false,并不支持mark和reset + * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 + * {@link FileMagic} + * 报错内容:getFileMagic() only operates on streams which support mark(int) + * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * @author kefan.qu + */ + final InputStream inputStream = FileMagic.prepareToCheckMagic(in); try { - return FileMagic.valueOf(pin) == FileMagic.OLE2; + return FileMagic.valueOf(inputStream) == FileMagic.OLE2; } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelUtil.java index 1c8cddd27..3d814749f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelUtil.java @@ -289,7 +289,7 @@ public class ExcelUtil { */ public static ExcelReader getReader(InputStream bookStream, int sheetIndex) { try { - return new ExcelReader(bookStream, sheetIndex, true); + return new ExcelReader(bookStream, sheetIndex); } catch (NoClassDefFoundError e) { throw new DependencyException(ObjectUtil.defaultIfNull(e.getCause(), e), PoiChecker.NO_POI_ERROR_MSG); } @@ -303,10 +303,12 @@ public class ExcelUtil { * @param closeAfterRead 读取结束是否关闭流 * @return {@link ExcelReader} * @since 4.0.3 + * @deprecated 使用完毕无论是否closeAfterRead,poi会关闭流,此参数无意义。 */ + @Deprecated public static ExcelReader getReader(InputStream bookStream, int sheetIndex, boolean closeAfterRead) { try { - return new ExcelReader(bookStream, sheetIndex, closeAfterRead); + return new ExcelReader(bookStream, sheetIndex); } catch (NoClassDefFoundError e) { throw new DependencyException(ObjectUtil.defaultIfNull(e.getCause(), e), PoiChecker.NO_POI_ERROR_MSG); } @@ -322,7 +324,7 @@ public class ExcelUtil { */ public static ExcelReader getReader(InputStream bookStream, String sheetName) { try { - return new ExcelReader(bookStream, sheetName, true); + return new ExcelReader(bookStream, sheetName); } catch (NoClassDefFoundError e) { throw new DependencyException(ObjectUtil.defaultIfNull(e.getCause(), e), PoiChecker.NO_POI_ERROR_MSG); } @@ -335,10 +337,12 @@ public class ExcelUtil { * @param sheetName sheet名,第一个默认是sheet1 * @param closeAfterRead 读取结束是否关闭流 * @return {@link ExcelReader} + * @deprecated 使用完毕无论是否closeAfterRead,poi会关闭流,此参数无意义。 */ + @Deprecated public static ExcelReader getReader(InputStream bookStream, String sheetName, boolean closeAfterRead) { try { - return new ExcelReader(bookStream, sheetName, closeAfterRead); + return new ExcelReader(bookStream, sheetName); } catch (NoClassDefFoundError e) { throw new DependencyException(ObjectUtil.defaultIfNull(e.getCause(), e), PoiChecker.NO_POI_ERROR_MSG); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/RowUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/RowUtil.java index cbeb149db..c7a9f0d94 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/RowUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/RowUtil.java @@ -7,9 +7,14 @@ import cn.hutool.poi.excel.cell.CellUtil; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellRangeUtil; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * Excel中的行{@link Row}封装工具类 @@ -97,4 +102,88 @@ public class RowUtil { i++; } } + + /** + * 插入行 + * + * @param sheet 工作表 + * @param startRow 插入的起始行 + * @param insertNumber 插入的行数 + * @since 5.4.2 + */ + public static void insertRow(Sheet sheet, int startRow, int insertNumber) { + if (insertNumber <= 0) { + return; + } + // 插入位置的行,如果插入的行不存在则创建新行 + Row sourceRow = Optional.ofNullable(sheet.getRow(startRow)).orElseGet(() -> sheet.createRow(insertNumber)); + // 从插入行开始到最后一行向下移动 + sheet.shiftRows(startRow, sheet.getLastRowNum(), insertNumber, true, false); + + // 填充移动后留下的空行 + IntStream.range(startRow, startRow + insertNumber).forEachOrdered(i -> { + Row row = sheet.createRow(i); + row.setHeightInPoints(sourceRow.getHeightInPoints()); + short lastCellNum = sourceRow.getLastCellNum(); + IntStream.range(0, lastCellNum).forEachOrdered(j -> { + Cell cell = row.createCell(j); + cell.setCellStyle(sourceRow.getCell(j).getCellStyle()); + }); + }); + } + + /** + * 从工作表中删除指定的行,此方法修复sheet.shiftRows删除行时会拆分合并的单元格的问题 + * + * @param row 需要删除的行 + * @see sheet.shiftRows的bug + * @since 5.4.2 + */ + public static void removeRow(Row row) { + if (row == null) { + return; + } + int rowIndex = row.getRowNum(); + Sheet sheet = row.getSheet(); + int lastRow = sheet.getLastRowNum(); + if (rowIndex >= 0 && rowIndex < lastRow) { + List updateMergedRegions = new ArrayList<>(); + // 找出需要调整的合并单元格 + IntStream.range(0, sheet.getNumMergedRegions()) + .forEach(i -> { + CellRangeAddress mr = sheet.getMergedRegion(i); + if (!mr.containsRow(rowIndex)) { + return; + } + // 缩减以后变成单个单元格则删除合并单元格 + if (mr.getFirstRow() == mr.getLastRow() - 1 && mr.getFirstColumn() == mr.getLastColumn()) { + return; + } + updateMergedRegions.add(mr); + }); + + // 将行上移 + sheet.shiftRows(rowIndex + 1, lastRow, -1); + + // 找出删除行所在的合并单元格 + List removeMergedRegions = IntStream.range(0, sheet.getNumMergedRegions()) + .filter(i -> updateMergedRegions.stream(). + anyMatch(umr -> CellRangeUtil.contains(umr, sheet.getMergedRegion(i)))) + .boxed() + .collect(Collectors.toList()); + + sheet.removeMergedRegions(removeMergedRegions); + updateMergedRegions.forEach(mr -> { + mr.setLastRow(mr.getLastRow() - 1); + sheet.addMergedRegion(mr); + }); + sheet.validateMergedRegions(); + } + if (rowIndex == lastRow) { + Row removingRow = sheet.getRow(rowIndex); + if (removingRow != null) { + sheet.removeRow(removingRow); + } + } + } } diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index eda32f472..57703bb9a 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 5dd97d6ef..6379c76f6 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 26632051b..42b7fe691 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index c2233b7d6..6683d7789 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 307d662f6..e39ec8435 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.4.1-SNAPSHOT + 5.4.2-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/looly/hutool