mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
Merge branch 'v5-dev' of https://gitee.com/shadowli_admin/hutool into v5-dev
This commit is contained in:
commit
6d52779006
28
CHANGELOG.md
28
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)
|
||||
|
@ -121,19 +121,19 @@ Each module can be introduced individually, or all modules can be introduced by
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.4.1</version>
|
||||
<version>5.4.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
@ -120,21 +120,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.4.1</version>
|
||||
<version>5.4.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 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平台没有测试,不能保证所有工具类或工具方法可用。
|
||||
|
@ -1 +1 @@
|
||||
5.4.1
|
||||
5.4.2
|
||||
|
@ -1 +1 @@
|
||||
var version = '5.4.1'
|
||||
var version = '5.4.2'
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-all</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bom</artifactId>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-core</artifactId>
|
||||
|
@ -333,7 +333,7 @@ public class BeanDesc implements Serializable {
|
||||
// ------------------------------------------------------------------------------------------------------ Private method end
|
||||
|
||||
/**
|
||||
* 属性描述
|
||||
* 属性描述,包括了字段、getter、setter和相应的方法执行
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
|
@ -105,7 +105,7 @@ public class ConcurrentHashSet<E> extends AbstractSet<E> implements java.io.Seri
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return map.remove(o) == PRESENT;
|
||||
return PRESENT.equals(map.remove(o));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -16,9 +16,6 @@ public class AtomicBooleanConverter extends AbstractConverter<AtomicBoolean> {
|
||||
|
||||
@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);
|
||||
}
|
||||
|
@ -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,9 +71,9 @@ 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;
|
||||
@ -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<String> getFestivalsList = new ArrayList<>();
|
||||
for (String fv : lFtv) {
|
||||
final List<String> 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=甲子
|
||||
* 这里同步处理年月日的天干地支信息
|
||||
*
|
||||
* @param Y 年
|
||||
* @param M 月
|
||||
* @param D 日
|
||||
* @return 天干地支信息
|
||||
*/
|
||||
private static String cyclicalm(int num) {
|
||||
final String[] Gan = new String[]{"甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"};
|
||||
final String[] Zhi = new String[]{"子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"};
|
||||
return (Gan[num % 10] + Zhi[num % 12]);
|
||||
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);
|
||||
}
|
||||
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 yearDays(int y) {
|
||||
int i, sum = 348;
|
||||
for (i = 0x8000; i > 0x8; i >>= 1) {
|
||||
if ((lunarInfo[y - 1900] & i) != 0)
|
||||
sum += 1;
|
||||
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 (sum + leapDays(y));
|
||||
int day = LunarInfo.monthDays(chineseYear, chineseMonth);
|
||||
int _day = day;
|
||||
if (isLeapMonth) {
|
||||
_day = LunarInfo.leapDays(chineseYear);
|
||||
}
|
||||
|
||||
/**
|
||||
* 传回农历 y年闰月的天数
|
||||
*
|
||||
* @param y 年
|
||||
* @return 闰月的天数
|
||||
*/
|
||||
private int leapDays(int y) {
|
||||
if (leapMonth(y) != 0) {
|
||||
return (lunarInfo[y - 1900] & 0x10000) != 0 ? 30 : 29;
|
||||
//参数合法性效验
|
||||
if (chineseYear < LunarInfo.BASE_YEAR || chineseYear > 2100 || chineseDay > _day) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 0;
|
||||
//计算农历的时间差
|
||||
int offset = 0;
|
||||
for (int i = LunarInfo.BASE_YEAR; i < chineseYear; i++) {
|
||||
offset += LunarInfo.yearDays(i);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 传回农历 y年m月的总天数
|
||||
*
|
||||
* @param y 年
|
||||
* @param m 月
|
||||
* @return 总天数
|
||||
*/
|
||||
private int monthDays(int y, int m) {
|
||||
return (lunarInfo[y - 1900] & (0x10000 >> m)) == 0 ? 29 : 30;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 传回农历 y年闰哪个月 1-12 , 没闰传回 0
|
||||
*
|
||||
* @param y 年
|
||||
* @return 润的月
|
||||
*/
|
||||
private int leapMonth(int y) {
|
||||
return (int) (lunarInfo[y - 1900] & 0xf);
|
||||
}
|
||||
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
|
||||
|
||||
}
|
@ -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开始计数<br>
|
||||
* 由于{@link Calendar} 中的月份按照0开始计数,导致某些需求容易误解,因此如果想用1表示一月,2表示二月则调用此方法
|
||||
@ -446,18 +455,18 @@ public class DateTime extends Date {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定日期是这个日期所在月份的第几天<br>
|
||||
* 获得指定日期是这个日期所在月份的第几天,从1开始
|
||||
*
|
||||
* @return 天
|
||||
* @return 天,1表示第一天
|
||||
*/
|
||||
public int dayOfMonth() {
|
||||
return getField(DateField.DAY_OF_MONTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定日期是这个日期所在年份的第几天<br>
|
||||
* 获得指定日期是这个日期所在年份的第几天,从1开始
|
||||
*
|
||||
* @return 天
|
||||
* @return 天,1表示第一天
|
||||
* @since 5.3.6
|
||||
*/
|
||||
public int dayOfYear() {
|
||||
|
@ -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 = {"正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "腊"};
|
||||
|
||||
/**
|
||||
* 获得农历月称呼<br>
|
||||
* 当为传统表示时,表示为二月,腊月,或者润正月等
|
||||
* 当为非传统表示时,二月,十二月,或者润一月等
|
||||
*
|
||||
* @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] + "月";
|
||||
}
|
||||
}
|
@ -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]);
|
||||
}
|
||||
}
|
@ -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<Pair<Integer, Integer>, 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<String> getFestivals(int month, int day) {
|
||||
return lFtv.getValues(new Pair<>(month, day));
|
||||
}
|
||||
}
|
@ -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];
|
||||
}
|
||||
}
|
@ -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"};
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 农历相关类汇总,包括农历月、天干地支、农历节日、24节气等
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.core.date.chinese;
|
@ -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
|
||||
|
||||
/**
|
||||
|
@ -119,12 +119,14 @@ public class UUID implements java.io.Serializable, Comparable<UUID> {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -282,6 +282,8 @@ public class MapUtil {
|
||||
/**
|
||||
* 根据给定的Pair数组创建Map对象
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @param pairs 键值对
|
||||
* @return Map
|
||||
* @since 5.4.1
|
||||
|
@ -107,7 +107,7 @@ public final class CsvRow implements List<String> {
|
||||
* @since 5.3.6
|
||||
*/
|
||||
public <T> T toBean(Class<T> clazz){
|
||||
return BeanUtil.mapToBean(getFieldMap(), clazz, true);
|
||||
return BeanUtil.toBeanIgnoreError(getFieldMap(), clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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<Runnable> 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 <T> Future<T> submit(Callable<T> task) {
|
||||
return e.submit(task);
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
@Override
|
||||
public <T> Future<T> submit(Runnable task, T result) {
|
||||
return e.submit(task, result);
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
@Override
|
||||
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
|
||||
return e.invokeAll(tasks);
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
@Override
|
||||
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
|
||||
throws InterruptedException {
|
||||
return e.invokeAll(tasks, timeout, unit);
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
@Override
|
||||
public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
|
||||
throws InterruptedException, ExecutionException {
|
||||
return e.invokeAny(tasks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return e.invokeAny(tasks, timeout, unit);
|
||||
|
@ -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} 建造者
|
||||
*
|
||||
* <pre>
|
||||
* 1. 如果池中任务数 < corePoolSize -》 放入立即执行
|
||||
* 2. 如果池中任务数 > corePoolSize -》 放入队列等待
|
||||
* 3. 队列满 -》 新建线程立即执行
|
||||
* 4. 执行中的线程 > maxPoolSize -》 触发handler(RejectedExecutionHandler)异常
|
||||
* </pre>
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.9
|
||||
*/
|
||||
@ -39,7 +46,7 @@ public class ExecutorBuilder implements Builder<ThreadPoolExecutor> {
|
||||
*/
|
||||
private long keepAliveTime = TimeUnit.SECONDS.toNanos(60);
|
||||
/**
|
||||
* 队列,用于存在未执行的线程
|
||||
* 队列,用于存放未执行的线程
|
||||
*/
|
||||
private BlockingQueue<Runnable> workQueue;
|
||||
/**
|
||||
@ -105,7 +112,7 @@ public class ExecutorBuilder implements Builder<ThreadPoolExecutor> {
|
||||
*
|
||||
* <pre>
|
||||
* 1. {@link SynchronousQueue} 它将任务直接提交给线程而不保持它们。当运行线程小于maxPoolSize时会创建新线程,否则触发异常策略
|
||||
* 2. {@link LinkedBlockingQueue} 默认无界队列,当运行线程大于corePoolSize时始终放入此队列,此时maximumPoolSize无效。
|
||||
* 2. {@link LinkedBlockingQueue} 默认无界队列,当运行线程大于corePoolSize时始终放入此队列,此时maxPoolSize无效。
|
||||
* 当构造LinkedBlockingQueue对象时传入参数,变为有界队列,队列满时,运行线程小于maxPoolSize时会创建新线程,否则触发异常策略
|
||||
* 3. {@link ArrayBlockingQueue} 有界队列,相对无界队列有利于控制队列大小,队列满时,运行线程小于maxPoolSize时会创建新线程,否则触发异常策略
|
||||
* </pre>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -16,9 +16,10 @@ import java.util.concurrent.ExecutorService;
|
||||
* ps:
|
||||
* //模拟1000个线程并发
|
||||
* SyncFinisher sf = new SyncFinisher(1000);
|
||||
* concurrencyTestUtil.run(() -> {
|
||||
* sf.addWorker(() -> {
|
||||
* // 需要并发测试的业务代码
|
||||
* });
|
||||
* sf.start()
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
|
@ -61,7 +61,7 @@ public class EnumUtil {
|
||||
*/
|
||||
public static <E extends Enum<E>> E getEnumAt(Class<E> enumClass, int index) {
|
||||
final E[] enumConstants = enumClass.getEnumConstants();
|
||||
return index < enumConstants.length ? enumConstants[index] : null;
|
||||
return index >= 0 && index < enumConstants.length ? enumConstants[index] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
21
hutool-core/src/test/java/cn/hutool/core/lang/UUIDTest.java
Normal file
21
hutool-core/src/test/java/cn/hutool/core/lang/UUIDTest.java
Normal file
@ -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<String> set = new ConcurrentHashSet<>(100);
|
||||
ThreadUtil.concurrencyTest(100, ()-> set.add(UUID.fastUUID().toString()));
|
||||
Assert.assertEquals(100, set.size());
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -56,4 +56,5 @@ public class TypeUtilTest {
|
||||
public void service(String string) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
|
@ -52,8 +52,8 @@ public class TaskTable implements Serializable {
|
||||
*/
|
||||
public TaskTable add(String id, CronPattern pattern, Task task) {
|
||||
final Lock writeLock = lock.writeLock();
|
||||
try {
|
||||
writeLock.lock();
|
||||
try {
|
||||
if (ids.contains(id)) {
|
||||
throw new CronException("Id [{}] has been existed!", id);
|
||||
}
|
||||
@ -75,8 +75,8 @@ public class TaskTable implements Serializable {
|
||||
*/
|
||||
public List<String> getIds() {
|
||||
final Lock readLock = lock.readLock();
|
||||
try {
|
||||
readLock.lock();
|
||||
try {
|
||||
return Collections.unmodifiableList(this.ids);
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
@ -91,8 +91,8 @@ public class TaskTable implements Serializable {
|
||||
*/
|
||||
public List<CronPattern> getPatterns() {
|
||||
final Lock readLock = lock.readLock();
|
||||
try {
|
||||
readLock.lock();
|
||||
try {
|
||||
return Collections.unmodifiableList(this.patterns);
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
@ -107,8 +107,8 @@ public class TaskTable implements Serializable {
|
||||
*/
|
||||
public List<Task> getTasks() {
|
||||
final Lock readLock = lock.readLock();
|
||||
try {
|
||||
readLock.lock();
|
||||
try {
|
||||
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();
|
||||
try {
|
||||
writeLock.lock();
|
||||
try {
|
||||
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();
|
||||
try {
|
||||
writeLock.lock();
|
||||
try {
|
||||
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();
|
||||
try {
|
||||
readLock.lock();
|
||||
try {
|
||||
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();
|
||||
try {
|
||||
readLock.lock();
|
||||
try {
|
||||
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();
|
||||
try {
|
||||
readLock.lock();
|
||||
try {
|
||||
executeTaskIfMatchInternal(millis);
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
|
@ -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];
|
||||
|
@ -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;
|
@ -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;
|
@ -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;
|
||||
@ -21,7 +21,6 @@ import java.security.PublicKey;
|
||||
* SM2算法单元测试
|
||||
*
|
||||
* @author Looly, Gsealy
|
||||
*
|
||||
*/
|
||||
public class SM2Test {
|
||||
|
||||
@ -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())//
|
@ -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;
|
@ -1,4 +1,4 @@
|
||||
package cn.hutool.crypto.test;
|
||||
package cn.hutool.crypto.test.digest;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
@ -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;
|
@ -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;
|
@ -1,4 +1,4 @@
|
||||
package cn.hutool.crypto.test;
|
||||
package cn.hutool.crypto.test.symmetric;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
@ -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;
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-db</artifactId>
|
||||
|
@ -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<Object[]> 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<String> sqls) throws SQLException {
|
||||
Connection conn = null;
|
||||
try {
|
||||
conn = this.getConnection();
|
||||
return SqlExecutor.executeBatch(conn, sqls);
|
||||
} finally {
|
||||
this.closeConnection(conn);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------- CRUD start
|
||||
|
||||
/**
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-dfa</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
|
@ -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属性缓存<br>
|
||||
@ -15,20 +13,39 @@ import java.beans.PropertyDescriptor;
|
||||
* @since 5.4.1
|
||||
*/
|
||||
public enum BeanCopierCache {
|
||||
/**
|
||||
* BeanCopier属性缓存单例
|
||||
*/
|
||||
INSTANCE;
|
||||
|
||||
private final SimpleCache<String, BeanCopier> 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<BeanCopier> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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> T copy(Object source, Class<T> 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 <S> 源bean类型
|
||||
* @param <T> 目标bean类型
|
||||
* @param source 源bean对象list
|
||||
* @param target 目标bean对象
|
||||
* @return 目标bean对象list
|
||||
*/
|
||||
public static <S, T> List<T> copyList(Collection<S> source, Supplier<T> target) {
|
||||
return copyList(source, target, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝List Bean对象属性
|
||||
*
|
||||
* @param source 源bean对象list
|
||||
* @param target 目标bean对象
|
||||
* @param converter 转换器,无需可传{@code null}
|
||||
* @param <S> 源bean类型
|
||||
* @param <T> 目标bean类型
|
||||
* @return 目标bean对象list
|
||||
* @since 5.4.1
|
||||
*/
|
||||
public static <S, T> List<T> copyList(Collection<S> source, Supplier<T> target, Converter converter) {
|
||||
return copyList(source, target, converter, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝List Bean对象属性
|
||||
*
|
||||
* @param source 源bean对象list
|
||||
* @param target 目标bean对象
|
||||
* @param callback 回调对象
|
||||
* @param <S> 源bean类型
|
||||
* @param <T> 目标bean类型
|
||||
* @return 目标bean对象list
|
||||
* @since 5.4.1
|
||||
*/
|
||||
public static <S, T> List<T> copyList(Collection<S> source, Supplier<T> target, BiConsumer<S, T> callback) {
|
||||
return copyList(source, target, null, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝List Bean对象属性
|
||||
*
|
||||
* @param source 源bean对象list
|
||||
* @param target 目标bean对象
|
||||
* @param converter 转换器,无需可传{@code null}
|
||||
* @param callback 回调对象
|
||||
* @param <S> 源bean类型
|
||||
* @param <T> 目标bean类型
|
||||
* @return 目标bean对象list
|
||||
*/
|
||||
public static <S, T> List<T> copyList(Collection<S> source, Supplier<T> target, Converter converter, BiConsumer<S, T> 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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
@ -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端口上. <br>
|
||||
* 即数据从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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解除端口映射
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
@ -23,6 +24,21 @@ public class JschUtilTest {
|
||||
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() {
|
||||
|
@ -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("templates/velocity_test.vtl");
|
||||
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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
ÄăşĂ,$name
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-http</artifactId>
|
||||
|
@ -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<HttpRequest> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Cookie<br>
|
||||
* 自定义Cookie后会覆盖Hutool的默认Cookie行为
|
||||
*
|
||||
* @param cookies Cookie值数组,如果为{@code null}则设置无效,使用默认Cookie行为
|
||||
* @return this
|
||||
* @since 5.4.1
|
||||
*/
|
||||
public HttpRequest cookie(Collection<HttpCookie> cookies) {
|
||||
return cookie(CollUtil.isEmpty(cookies) ? null : cookies.toArray(new HttpCookie[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Cookie<br>
|
||||
* 自定义Cookie后会覆盖Hutool的默认Cookie行为
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-json</artifactId>
|
||||
|
@ -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("<aaa>你好</aaa><键2>test</键2>", s);
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-log</artifactId>
|
||||
|
@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-poi</artifactId>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 <a href="https://bz.apache.org/bugzilla/show_bug.cgi?id=56454">sheet.shiftRows的bug</a>
|
||||
* @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<CellRangeAddress> 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<Integer> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-script</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-socket</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-system</artifactId>
|
||||
|
2
pom.xml
2
pom.xml
@ -8,7 +8,7 @@
|
||||
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.1-SNAPSHOT</version>
|
||||
<version>5.4.2-SNAPSHOT</version>
|
||||
<name>hutool</name>
|
||||
<description>Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。</description>
|
||||
<url>https://github.com/looly/hutool</url>
|
||||
|
Loading…
x
Reference in New Issue
Block a user