Merge remote-tracking branch 'origin/v5-dev' into v5-dev

This commit is contained in:
achao 2022-02-07 20:55:09 +08:00
commit 7242d6992f
53 changed files with 1229 additions and 182 deletions

View File

@ -2,12 +2,28 @@
# 🚀Changelog
-------------------------------------------------------------------------------------------------------------
# 5.7.21 (2022-01-21)
# 5.7.21 (2022-02-07)
### 🐣新特性
* 【extra 】 增加jetbrick模板支持
* 【extra 】 EmojiUtil增加方法pr#519@Gitee
* 【core 】 DateUtil 添加两个日期是否同一周方法pr#516@Gitee
* 【db 】 新增条件组用于处理复杂的where条件pr#514@Gitee
* 【core 】 新增LocalDateTimeUtil.weekOfYearissue#I4RWXC@Gitee
* 【core 】 Month增加toJdkMonth、getValueBaseOne
* 【core 】 CsvWriter修改规则去除末尾多余换行符issue#I4RSQY@Gitee
* 【core 】 DateUtil增加rangeFunc和rangeConsumeissue#I4RSQY@Gitee
* 【core 】 DateTime增加setUseJdkToStringStyle方法
* 【core 】 CharSequenceUtil增加replace重载(issue#2122@Github)
* 【core 】 IntMap和LongMap使用位运算快速求解取余运算(pr#2123@Github)
* 【core 】 新增通用builder类GenericBuilder(pr#526@Gitee)
* 【core 】 新增copySafely方法与mkdirsSafely方法(pr#527@Gitee)
### 🐞Bug修复
* 【core 】 修复ChineseDate农历获取正月出现数组越界BUGissue#2112@Github
* 【extra 】 修复EmojiUtil.toHtmlHex()方法pr#519@Gitee
* 【system 】 修复CpuInfo.getUsed()方法issue#2116@Github
* 【dfa 】 修复密集匹配和贪婪匹配冲突问题issue#2126@Github
-------------------------------------------------------------------------------------------------------------
# 5.7.20 (2022-01-20)

View File

@ -7,5 +7,5 @@ echo ' / /_/ // / / // __// __ \ / __ \ / / '
echo ' / __ // /_/ // /_ / /_/ // /_/ // / '
echo '/_/ /_/ \____/ \__/ \____/ \____//_/ '
echo ''
echo '-----------http://hutool.cn/------------'
echo '-----------https://hutool.cn/-----------'
echo '========================================'

View File

@ -32,22 +32,22 @@ public class IntMap implements BitMap, Serializable {
@Override
public void add(long i) {
int r = (int) (i / BitMap.MACHINE32);
int c = (int) (i % BitMap.MACHINE32);
int c = (int) (i & (BitMap.MACHINE32 - 1));
ints[r] = ints[r] | (1 << c);
}
@Override
public boolean contains(long i) {
int r = (int) (i / BitMap.MACHINE32);
int c = (int) (i % BitMap.MACHINE32);
int c = (int) (i & (BitMap.MACHINE32 - 1));
return ((ints[r] >>> c) & 1) == 1;
}
@Override
public void remove(long i) {
int r = (int) (i / BitMap.MACHINE32);
int c = (int) (i % BitMap.MACHINE32);
int c = (int) (i & (BitMap.MACHINE32 - 1));
ints[r] &= ~(1 << c);
}
}
}

View File

@ -32,22 +32,22 @@ public class LongMap implements BitMap, Serializable {
@Override
public void add(long i) {
int r = (int) (i / BitMap.MACHINE64);
long c = i % BitMap.MACHINE64;
long c = i & (BitMap.MACHINE64 - 1);
longs[r] = longs[r] | (1L << c);
}
@Override
public boolean contains(long i) {
int r = (int) (i / BitMap.MACHINE64);
long c = i % BitMap.MACHINE64;
long c = i & (BitMap.MACHINE64 - 1);
return ((longs[r] >>> c) & 1) == 1;
}
@Override
public void remove(long i) {
int r = (int) (i / BitMap.MACHINE64);
long c = i % BitMap.MACHINE64;
long c = i & (BitMap.MACHINE64 - 1);
longs[r] &= ~(1L << c);
}
}
}

View File

@ -6,8 +6,8 @@ import cn.hutool.core.lang.func.Func0;
/**
* Bean属性缓存<br>
* 缓存用于防止多次反射造成的性能问题
* @author Looly
*
* @author Looly
*/
public enum BeanDescCache {
INSTANCE;
@ -16,12 +16,22 @@ public enum BeanDescCache {
/**
* 获得属性名和{@link BeanDesc}Map映射
*
* @param beanClass Bean的类
* @param supplier 对象不存在时创建对象的函数
* @param supplier 对象不存在时创建对象的函数
* @return 属性名和{@link BeanDesc}映射
* @since 5.4.2
*/
public BeanDesc getBeanDesc(Class<?> beanClass, Func0<BeanDesc> supplier){
public BeanDesc getBeanDesc(Class<?> beanClass, Func0<BeanDesc> supplier) {
return bdCache.get(beanClass, supplier);
}
/**
* 清空全局的Bean属性缓存
*
* @since 5.7.21
*/
public void clear() {
this.bdCache.clear();
}
}

View File

@ -56,6 +56,16 @@ public enum BeanInfoCache {
getCache(ignoreCase).put(beanClass, fieldNamePropertyDescriptorMap);
}
/**
* 清空缓存
*
* @since 5.7.21
*/
public void clear() {
this.pdCache.clear();
this.ignoreCasePdCache.clear();
}
/**
* 根据是否忽略字段名的大小写返回不用Cache对象
*

View File

@ -0,0 +1,208 @@
package cn.hutool.core.builder;
import cn.hutool.core.lang.func.Supplier1;
import cn.hutool.core.lang.func.Supplier2;
import cn.hutool.core.lang.func.Supplier3;
import cn.hutool.core.lang.func.Supplier4;
import cn.hutool.core.lang.func.Supplier5;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* <p>通用Builder</p>
* 参考: <a href="https://blog.csdn.net/weixin_43935907/article/details/105003719">一看就会的java8通用Builder</a>
* <p>使用方法如下</p>
* <pre>
* Box box = GenericBuilder
* .of(Box::new)
* .with(Box::setId, 1024L)
* .with(Box::setTitle, "Hello World!")
* .with(Box::setLength, 9)
* .with(Box::setWidth, 8)
* .with(Box::setHeight, 7)
* .build();
*
* </pre>
*
* <p> 我们也可以对已创建的对象进行修改</p>
* <pre>
* Box boxModified = GenericBuilder
* .of(() -&gt; box)
* .with(Box::setTitle, "Hello Friend!")
* .with(Box::setLength, 3)
* .with(Box::setWidth, 4)
* .with(Box::setHeight, 5)
* .build();
* </pre>
* <p> 我们还可以对这样调用有参构造这对于创建一些在有参构造中包含初始化函数的对象是有意义的</p>
* <pre>
* Box box1 = GenericBuilder
* .of(Box::new, 2048L, "Hello Partner!", 222, 333, 444)
* .with(Box::alis)
* .build();
* </pre>
* <p>注意本工具类支持调用的方法的参数数量不超过1个更多的参数不利于阅读和维护</p>
*
* @author TomXin
* @since 5.7.21
*/
public class GenericBuilder<T> implements Builder<T> {
/**
* 实例化器
*/
private final Supplier<T> instant;
/**
* 修改器列表
*/
private final List<Consumer<T>> modifiers = new ArrayList<>();
/**
* 构造
*
* @param instant 实例化器
*/
public GenericBuilder(Supplier<T> instant) {
this.instant = instant;
}
/**
* 通过无参数实例化器创建GenericBuilder
*
* @param instant 实例化器
* @param <T> 目标类型
* @return GenericBuilder对象
*/
public static <T> GenericBuilder<T> of(Supplier<T> instant) {
return new GenericBuilder<>(instant);
}
/**
* 通过1参数实例化器创建GenericBuilder
*
* @param instant 实例化器
* @param p1 参数一
* @param <T> 目标类型
* @param <P1> 参数一类型
* @return GenericBuilder对象
*/
public static <T, P1> GenericBuilder<T> of(Supplier1<T, P1> instant, P1 p1) {
return of(instant.toSupplier(p1));
}
/**
* 通过2参数实例化器创建GenericBuilder
*
* @param instant 实例化器
* @param p1 参数一
* @param p2 参数二
* @param <T> 目标类型
* @param <P1> 参数一类型
* @param <P2> 参数二类型
* @return GenericBuilder对象
*/
public static <T, P1, P2> GenericBuilder<T> of(Supplier2<T, P1, P2> instant, P1 p1, P2 p2) {
return of(instant.toSupplier(p1, p2));
}
/**
* 通过3参数实例化器创建GenericBuilder
*
* @param instant 实例化器
* @param p1 参数一
* @param p2 参数二
* @param p3 参数三
* @param <T> 目标类型
* @param <P1> 参数一类型
* @param <P2> 参数二类型
* @param <P3> 参数三类型
* @return GenericBuilder对象
*/
public static <T, P1, P2, P3> GenericBuilder<T> of(Supplier3<T, P1, P2, P3> instant, P1 p1, P2 p2, P3 p3) {
return of(instant.toSupplier(p1, p2, p3));
}
/**
* 通过4参数实例化器创建GenericBuilder
*
* @param instant 实例化器
* @param p1 参数一
* @param p2 参数二
* @param p3 参数三
* @param p4 参数四
* @param <T> 目标类型
* @param <P1> 参数一类型
* @param <P2> 参数二类型
* @param <P3> 参数三类型
* @param <P4> 参数四类型
* @return GenericBuilder对象
*/
public static <T, P1, P2, P3, P4> GenericBuilder<T> of(Supplier4<T, P1, P2, P3, P4> instant, P1 p1, P2 p2, P3 p3, P4 p4) {
return of(instant.toSupplier(p1, p2, p3, p4));
}
/**
* 通过5参数实例化器创建GenericBuilder
*
* @param instant 实例化器
* @param p1 参数一
* @param p2 参数二
* @param p3 参数三
* @param p4 参数四
* @param p5 参数五
* @param <T> 目标类型
* @param <P1> 参数一类型
* @param <P2> 参数二类型
* @param <P3> 参数三类型
* @param <P4> 参数四类型
* @param <P5> 参数五类型
* @return GenericBuilder对象
*/
public static <T, P1, P2, P3, P4, P5> GenericBuilder<T> of(Supplier5<T, P1, P2, P3, P4, P5> instant, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) {
return of(instant.toSupplier(p1, p2, p3, p4, p5));
}
/**
* 调用无参数方法
*
* @param consumer 无参数Consumer
* @return GenericBuilder对象
*/
public GenericBuilder<T> with(Consumer<T> consumer) {
modifiers.add(consumer);
return this;
}
/**
* 调用1参数方法
*
* @param <P1> 参数一类型
* @param consumer 1参数Consumer一般为Setter方法引用
* @param p1 参数一
* @return GenericBuilder对象
*/
public <P1> GenericBuilder<T> with(BiConsumer<T, P1> consumer, P1 p1) {
modifiers.add(instant -> consumer.accept(instant, p1));
return this;
}
/**
* 构建
*
* @return 目标对象
*/
@Override
public T build() {
T value = instant.get();
modifiers.forEach(modifier -> modifier.accept(value));
modifiers.clear();
return value;
}
}

View File

@ -132,7 +132,7 @@ public class CalendarUtil {
}
/**
* 获取秒级别的开始时间即忽略毫秒部分
* 修改秒级别的开始时间即忽略毫秒部分
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@ -143,7 +143,7 @@ public class CalendarUtil {
}
/**
* 获取秒级别的结束时间即毫秒设置为999
* 修改秒级别的结束时间即毫秒设置为999
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@ -154,7 +154,7 @@ public class CalendarUtil {
}
/**
* 获取某小时的开始时间
* 修改某小时的开始时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@ -164,7 +164,7 @@ public class CalendarUtil {
}
/**
* 获取某小时的结束时间
* 修改某小时的结束时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@ -174,7 +174,7 @@ public class CalendarUtil {
}
/**
* 获取某分钟的开始时间
* 修改某分钟的开始时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@ -184,7 +184,7 @@ public class CalendarUtil {
}
/**
* 获取某分钟的结束时间
* 修改某分钟的结束时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@ -194,7 +194,7 @@ public class CalendarUtil {
}
/**
* 获取某天的开始时间
* 修改某天的开始时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@ -204,7 +204,7 @@ public class CalendarUtil {
}
/**
* 获取某天的结束时间
* 修改某天的结束时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@ -214,7 +214,7 @@ public class CalendarUtil {
}
/**
* 获取给定日期当前周的开始时间周一定为一周的开始时间
* 修改给定日期当前周的开始时间周一定为一周的开始时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@ -224,7 +224,7 @@ public class CalendarUtil {
}
/**
* 获取给定日期当前周的开始时间
* 修改给定日期当前周的开始时间
*
* @param calendar 日期 {@link Calendar}
* @param isMondayAsFirstDay 是否周一做为一周的第一天false表示周日做为第一天
@ -238,7 +238,7 @@ public class CalendarUtil {
}
/**
* 获取某周的结束时间周日定为一周的结束
* 修改某周的结束时间周日定为一周的结束
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@ -248,7 +248,7 @@ public class CalendarUtil {
}
/**
* 获取某周的结束时间
* 修改某周的结束时间
*
* @param calendar 日期 {@link Calendar}
* @param isSundayAsLastDay 是否周日做为一周的最后一天false表示周六做为最后一天
@ -261,7 +261,7 @@ public class CalendarUtil {
}
/**
* 获取某月的开始时间
* 修改某月的开始时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@ -271,7 +271,7 @@ public class CalendarUtil {
}
/**
* 获取某月的结束时间
* 修改某月的结束时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@ -281,7 +281,7 @@ public class CalendarUtil {
}
/**
* 获取某季度的开始时间
* 修改某季度的开始时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@ -313,7 +313,7 @@ public class CalendarUtil {
}
/**
* 获取某年的开始时间
* 修改某年的开始时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@ -323,7 +323,7 @@ public class CalendarUtil {
}
/**
* 获取某年的结束时间
* 修改某年的结束时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@ -348,6 +348,40 @@ public class CalendarUtil {
cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA);
}
/**
* 比较两个日期是否为同一周
*
* @param cal1 日期1
* @param cal2 日期2
* @param isMon 是否为周一国内第一天为星期一国外第一天为星期日
* @return 是否为同一周
* @since 5.7.21
*/
public static boolean isSameWeek(Calendar cal1, Calendar cal2, boolean isMon) {
if (cal1 == null || cal2 == null) {
throw new IllegalArgumentException("The date must not be null");
}
// 防止比较前修改原始Calendar对象
cal1 = (Calendar) cal1.clone();
cal2 = (Calendar) cal2.clone();
// 把所传日期设置为其当前周的第一天
// 比较设置后的两个日期是否是同一天true 代表同一周
if (isMon) {
cal1.setFirstDayOfWeek(Calendar.MONDAY);
cal1.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
cal2.setFirstDayOfWeek(Calendar.MONDAY);
cal2.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
} else {
cal1.setFirstDayOfWeek(Calendar.SUNDAY);
cal1.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
cal2.setFirstDayOfWeek(Calendar.SUNDAY);
cal2.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
}
return isSameDay(cal1, cal2);
}
/**
* 比较两个日期是否为同一月
*

View File

@ -24,13 +24,30 @@ import java.util.Locale;
import java.util.TimeZone;
/**
* 包装java.util.Date
* 包装{@link Date}<br>
* 此类继承了{@link Date}并提供扩展方法如时区等<br>
* 此类重写了父类的{@code toString()}方法返回值为"yyyy-MM-dd HH:mm:ss"格式
*
* @author xiaoleilu
*/
public class DateTime extends Date {
private static final long serialVersionUID = -5395712593979185936L;
private static boolean useJdkToStringStyle = false;
/**
* 设置全局的是否使用{@link Date}默认的toString()格式<br>
* 如果为{@code true}则调用toString()时返回"EEE MMM dd HH:mm:ss zzz yyyy"格式<br>
* 如果为{@code false}则返回"yyyy-MM-dd HH:mm:ss"<br>
* 默认为{@code false}
*
* @param customUseJdkToStringStyle 是否使用{@link Date}默认的toString()格式
* @since 5.7.21
*/
public static void setUseJdkToStringStyle(boolean customUseJdkToStringStyle){
useJdkToStringStyle = customUseJdkToStringStyle;
}
/**
* 是否可变对象
*/
@ -932,13 +949,18 @@ public class DateTime extends Date {
// -------------------------------------------------------------------- toString start
/**
* 转为"yyyy-MM-dd HH:mm:ss" 格式字符串<br>
* 如果时区被设置会转换为其时区对应的时间否则转换为当前地点对应的时区
* 转为字符串如果时区被设置会转换为其时区对应的时间否则转换为当前地点对应的时区<br>
* 可以调用{@link DateTime#setUseJdkToStringStyle(boolean)} 方法自定义默认的风格<br>
* 如果{@link #useJdkToStringStyle}{@code true}返回"EEE MMM dd HH:mm:ss zzz yyyy"格式<br>
* 如果为{@code false}则返回"yyyy-MM-dd HH:mm:ss"
*
* @return "yyyy-MM-dd HH:mm:ss" 格式字符串
* @return 格式字符串
*/
@Override
public String toString() {
if(useJdkToStringStyle){
return super.toString();
}
return toString(this.timeZone);
}

View File

@ -20,7 +20,9 @@ import java.time.LocalDateTime;
import java.time.Year;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.LinkedHashSet;
@ -28,6 +30,8 @@ import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* 时间工具类
@ -1586,6 +1590,21 @@ public class DateUtil extends CalendarUtil {
return CalendarUtil.isSameDay(calendar(date1), calendar(date2));
}
/**
* 比较两个日期是否为同一周
*
* @param date1 日期1
* @param date2 日期2
* @param isMon 是否为周一国内第一天为星期一国外第一天为星期日
* @return 是否为同一周
*/
public static boolean isSameWeek(final Date date1, final Date date2, boolean isMon) {
if (date1 == null || date2 == null) {
throw new IllegalArgumentException("The date must not be null");
}
return CalendarUtil.isSameWeek(calendar(date1), calendar(date2), isMon);
}
/**
* 比较两个日期是否为同一月
*
@ -1870,6 +1889,44 @@ public class DateUtil extends CalendarUtil {
return new DateRange(start, end, unit);
}
/**
* 按日期范围遍历执行 function
*
* @param start 起始日期时间包括
* @param end 结束日期时间
* @param unit 步进单位
* @param func 每次遍历要执行的 function
* @param <T> Date经过函数处理结果类型
* @return 结果列表
* @since 5.7.21
*/
public static <T> List<T> rangeFunc(Date start, Date end, final DateField unit, Function<Date, T> func) {
if (start == null || end == null || start.after(end)) {
return Collections.emptyList();
}
ArrayList<T> list = new ArrayList<>();
for(DateTime date : range(start, end, unit)){
list.add(func.apply(date));
}
return list;
}
/**
* 按日期范围遍历执行 consumer
*
* @param start 起始日期时间包括
* @param end 结束日期时间
* @param unit 步进单位
* @param consumer 每次遍历要执行的 consumer
* @since 5.7.21
*/
public static void rangeConsume(Date start, Date end, final DateField unit, Consumer<Date> consumer) {
if (start == null || end == null || start.after(end)) {
return;
}
range(start, end, unit).forEach(consumer);
}
/**
* 创建日期范围生成器
*
@ -1878,7 +1935,7 @@ public class DateUtil extends CalendarUtil {
* @param unit 步进单位
* @return {@link DateRange}
*/
public static List<DateTime> rangeToList(Date start, Date end, final DateField unit) {
public static List<DateTime> rangeToList(Date start, Date end, DateField unit) {
return CollUtil.newArrayList((Iterable<DateTime>) range(start, end, unit));
}

View File

@ -22,6 +22,7 @@ import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalUnit;
import java.time.temporal.WeekFields;
import java.util.Date;
import java.util.TimeZone;
@ -562,4 +563,21 @@ public class LocalDateTimeUtil {
return startTime.isAfter(realEndTime) || endTime.isBefore(realStartTime);
}
/**
* 获得指定日期是所在年份的第几周
* <ul>
* <li>如果一年的第一天是星期一则第一周从第一天开始没有零周</li>
* <li>如果一年的第二天是星期一则第一周从第二天开始而第一天在零周</li>
* <li>如果一年中的第4天是星期一则第1周从第4周开始第1至第3周在零周开始</li>
* <li>如果一年中的第5天是星期一则第二周从第5周开始第1至第4周在第1周</li>
* </ul>
*
*
* @param date 日期{@link LocalDate} 或者 {@link LocalDateTime}
* @return 所在年的第几周
* @since 5.7.21
*/
public static int weekOfYear(TemporalAccessor date){
return TemporalAccessorUtil.get(date, WeekFields.ISO.weekOfYear());
}
}

View File

@ -1,5 +1,7 @@
package cn.hutool.core.date;
import cn.hutool.core.lang.Assert;
import java.util.Calendar;
/**
@ -93,14 +95,27 @@ public enum Month {
}
/**
* 获取{@link Calendar}中的对应值
* 获取{@link Calendar}中的对应值<br>
* 此值从0开始即0表示一月
*
* @return {@link Calendar}中的对应
* @return {@link Calendar}中的对应月份从0开始计数
*/
public int getValue() {
return this.value;
}
/**
* 获取月份值此值与{@link java.time.Month}对应<br>
* 此值从1开始即1表示一月
*
* @return 月份值对应{@link java.time.Month}从1开始计数
* @since 5.7.21
*/
public int getValueBaseOne() {
Assert.isFalse(this == UNDECIMBER, "Unsupported UNDECIMBER Field");
return getValue() + 1;
}
/**
* 获取此月份最后一天的值不支持的月份例如UNDECIMBER返回-1
*
@ -165,17 +180,28 @@ public enum Month {
/**
* 获得指定月的最后一天
* @param month 月份从0开始
*
* @param month 月份从0开始
* @param isLeapYear 是否为闰年闰年只对二月有影响
* @return 最后一天可能为28,29,30,31
* @since 5.4.7
*/
public static int getLastDay(int month, boolean isLeapYear){
public static int getLastDay(int month, boolean isLeapYear) {
int lastDay = DAYS_OF_MONTH[month];
if (isLeapYear && Calendar.FEBRUARY == month){
if (isLeapYear && Calendar.FEBRUARY == month) {
// 二月
lastDay += 1;
}
return lastDay;
}
/**
* 转换为{@link java.time.Month}
*
* @return {@link java.time.Month}
* @since 5.7.21
*/
public java.time.Month toJdkMonth() {
return java.time.Month.of(getValueBaseOne());
}
}

View File

@ -51,7 +51,7 @@ public class CheckedUtil {
* @param expression Lambda表达式
* @param <P> 运行时传入的参数类型
* @param <R> 最终返回的数据类型
* @return cn.hutool.core.lang.func.Func
* @return {@link FuncRt}
*/
public static <P, R> FuncRt<P, R> uncheck(Func<P, R> expression) {
return uncheck(expression, new RuntimeException());
@ -63,7 +63,7 @@ public class CheckedUtil {
*
* @param expression 运行时传入的参数类型
* @param <R> 最终返回的数据类型
* @return cn.hutool.core.lang.func.Func0
* @return {@link Func0Rt}
*/
public static <R> Func0Rt<R> uncheck(Func0<R> expression) {
return uncheck(expression, new RuntimeException());
@ -76,7 +76,7 @@ public class CheckedUtil {
* @param expression 运行时传入的参数类型
* @param <P> 运行时传入的参数类型
* @param <R> 最终返回的数据类型
* @return cn.hutool.core.lang.func.Func1
* @return {@link Func1Rt}
*/
public static <P, R> Func1Rt<P, R> uncheck(Func1<P, R> expression) {
return uncheck(expression, new RuntimeException());
@ -89,7 +89,7 @@ public class CheckedUtil {
*
* @param expression 运行时传入的参数类型
* @param <P> 运行时传入的参数类型
* @return cn.hutool.core.lang.func.VoidFunc
* @return {@link VoidFuncRt}
*/
public static <P> VoidFuncRt<P> uncheck(VoidFunc<P> expression) {
return uncheck(expression, new RuntimeException());
@ -100,7 +100,7 @@ public class CheckedUtil {
* 如此一来代码中就不用显示的try-catch转化成运行时异常
*
* @param expression 运行时传入的参数类型
* @return cn.hutool.core.lang.func.VoidFunc0
* @return {@link VoidFunc0Rt}
*/
public static VoidFunc0Rt uncheck(VoidFunc0 expression) {
return uncheck(expression, new RuntimeException());
@ -112,7 +112,7 @@ public class CheckedUtil {
*
* @param expression 运行时传入的参数类型
* @param <P> 运行时传入的参数类型
* @return cn.hutool.core.lang.func.VoidFunc1
* @return {@link VoidFunc1Rt}
*/
public static <P> VoidFunc1Rt<P> uncheck(VoidFunc1<P> expression) {
return uncheck(expression, new RuntimeException());
@ -127,7 +127,7 @@ public class CheckedUtil {
* @param rte 期望抛出的运行时异常
* @param <P> 运行时传入的参数类型
* @param <R> 最终返回的数据类型
* @return cn.hutool.core.lang.func.Func
* @return {@link FuncRt}
*/
public static <P, R> FuncRt<P, R> uncheck(Func<P, R> expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null");
@ -152,7 +152,7 @@ public class CheckedUtil {
* @param expression Lambda表达式
* @param rte 期望抛出的运行时异常
* @param <R> 最终返回的数据类型
* @return cn.hutool.core.lang.func.Func0
* @return {@link Func0Rt}
*/
public static <R> Func0Rt<R> uncheck(Func0<R> expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null");
@ -178,7 +178,7 @@ public class CheckedUtil {
* @param rte 期望抛出的运行时异常
* @param <P> 运行时传入的参数类型
* @param <R> 最终返回的数据类型
* @return cn.hutool.core.lang.func.Func1
* @return {@link Func1Rt}
*/
public static <P, R> Func1Rt<P, R> uncheck(Func1<P, R> expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null");
@ -203,7 +203,7 @@ public class CheckedUtil {
* @param expression Lambda表达式
* @param rte 期望抛出的运行时异常
* @param <P> 运行时传入的参数类型
* @return cn.hutool.core.lang.func.VoidFunc
* @return {@link VoidFuncRt}
*/
public static <P> VoidFuncRt<P> uncheck(VoidFunc<P> expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null");
@ -228,7 +228,7 @@ public class CheckedUtil {
*
* @param expression Lambda表达式
* @param rte 期望抛出的运行时异常
* @return cn.hutool.core.lang.func.VoidFunc0
* @return {@link VoidFunc0Rt}
*/
public static VoidFunc0Rt uncheck(VoidFunc0 expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null");
@ -254,7 +254,7 @@ public class CheckedUtil {
* @param expression Lambda表达式
* @param rte 期望抛出的运行时异常
* @param <P> 运行时传入的参数类型
* @return cn.hutool.core.lang.func.VoidFunc1
* @return {@link VoidFunc1Rt}
*/
public static <P> VoidFunc1Rt<P> uncheck(VoidFunc1<P> expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null");

View File

@ -13,6 +13,7 @@ import cn.hutool.core.io.file.Tailer;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.io.unit.DataSizeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.CharsetUtil;
@ -526,24 +527,39 @@ public class FileUtil extends PathUtil {
/**
* 计算目录或文件的总大小<br>
* 当给定对象为文件时直接调用 {@link File#length()}<br>
* 当给定对象为目录时遍历目录下的所有文件和目录递归计算其大小求和返回
* 当给定对象为目录时遍历目录下的所有文件和目录递归计算其大小求和返回<br>
* 此方法不包括目录本身的占用空间大小
*
* @param file 目录或文件,null或者文件不存在返回0
* @return 总大小bytes长度
*/
public static long size(File file) {
return size(file, false);
}
/**
* 计算目录或文件的总大小<br>
* 当给定对象为文件时直接调用 {@link File#length()}<br>
* 当给定对象为目录时遍历目录下的所有文件和目录递归计算其大小求和返回
*
* @param file 目录或文件,null或者文件不存在返回0
* @param includeDirSize 是否包括每层目录本身的大小
* @return 总大小bytes长度
* @since 5.7.21
*/
public static long size(File file, boolean includeDirSize) {
if (null == file || false == file.exists() || isSymlink(file)) {
return 0;
}
if (file.isDirectory()) {
long size = 0L;
long size = includeDirSize ? file.length() : 0;
File[] subFiles = file.listFiles();
if (ArrayUtil.isEmpty(subFiles)) {
return 0L;// empty directory
}
for (File subFile : subFiles) {
size += size(subFile);
size += size(subFile, includeDirSize);
}
return size;
} else {
@ -811,7 +827,7 @@ public class FileUtil extends PathUtil {
/**
* 创建文件夹会递归自动创建其不存在的父文件夹如果存在直接返回此文件夹<br>
* 此方法不对File对象类型做判断如果File不存在无法判断其类型
* 此方法不对File对象类型做判断如果File不存在无法判断其类型<br>
*
* @param dir 目录
* @return 创建的目录
@ -821,12 +837,48 @@ public class FileUtil extends PathUtil {
return null;
}
if (false == dir.exists()) {
//noinspection ResultOfMethodCallIgnored
dir.mkdirs();
mkdirsSafely(dir, 5, 1);
}
return dir;
}
/**
* 安全地级联创建目录 (确保并发环境下能创建成功)
*
* <pre>
* 并发环境下假设 test 目录不存在如果线程A mkdirs "test/A" 目录线程B mkdirs "test/B"目录
* 其中一个线程可能会失败进而导致以下代码抛出 FileNotFoundException 异常
*
* file.getParentFile().mkdirs(); // 父目录正在被另一个线程创建中返回 false
* file.createNewFile(); // 抛出 IO 异常因为该线程无法感知到父目录已被创建
* </pre>
*
* @param dir 待创建的目录
* @param tryCount 最大尝试次数
* @param sleepMillis 线程等待的毫秒数
* @return true表示创建成功false表示创建失败
* @since 5.7.21
* @author z8g
*/
public static boolean mkdirsSafely(File dir, int tryCount, long sleepMillis) {
if (dir == null) {
return false;
}
if (dir.isDirectory()) {
return true;
}
for (int i = 1; i <= tryCount; i++) { // 高并发场景下可以看到 i 处于 1 ~ 3 之间
// 如果文件已存在也会返回 false所以该值不能作为是否能创建的依据因此不对其进行处理
//noinspection ResultOfMethodCallIgnored
dir.mkdirs();
if (dir.exists()) {
return true;
}
ThreadUtil.sleep(sleepMillis);
}
return dir.exists();
}
/**
* 创建临时文件<br>
* 创建后的文件名为 prefix[Randon].tmp

View File

@ -87,12 +87,49 @@ public class NioUtil {
Assert.notNull(outChannel, "Out channel is null!");
try {
return inChannel.transferTo(0, inChannel.size(), outChannel);
return copySafely(inChannel, outChannel);
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
/**
* 文件拷贝实现
*
* <pre>
* FileChannel#transferTo FileChannel#transferFrom 的实现是平台相关的需要确保低版本平台的兼容性
* 例如 android 7以下平台在使用 ZipInputStream 解压文件的过程中
* 通过 FileChannel#transferFrom 传输到文件时其返回值可能小于 totalBytes不处理将导致文件内容缺失
*
* // 错误写法dstChannel.transferFrom 返回值小于 zipEntry.getSize()导致解压后文件内容缺失
* try (InputStream srcStream = zipFile.getInputStream(zipEntry);
* ReadableByteChannel srcChannel = Channels.newChannel(srcStream);
* FileOutputStream fos = new FileOutputStream(saveFile);
* FileChannel dstChannel = fos.getChannel()) {
* dstChannel.transferFrom(srcChannel, 0, zipEntry.getSize());
* }
* </pre>
*
* @param inChannel 输入通道
* @param outChannel 输出通道
* @return 输入通道的字节数
* @throws IOException 发生IO错误
* @link http://androidxref.com/6.0.1_r10/xref/libcore/luni/src/main/java/java/nio/FileChannelImpl.java
* @link http://androidxref.com/7.0.0_r1/xref/libcore/ojluni/src/main/java/sun/nio/ch/FileChannelImpl.java
* @link http://androidxref.com/7.0.0_r1/xref/libcore/ojluni/src/main/native/FileChannelImpl.c
* @author z8g
* @since 5.7.21
*/
private static long copySafely(FileChannel inChannel, FileChannel outChannel) throws IOException {
final long totalBytes = inChannel.size();
for (long pos = 0, remaining = totalBytes; remaining > 0; ) { // 确保文件内容不会缺失
final long writeBytes = inChannel.transferTo(pos, remaining, outChannel); // 实际传输的字节数
pos += writeBytes;
remaining -= writeBytes;
}
return totalBytes;
}
/**
* 拷贝流使用NIO不会关闭channel
*

View File

@ -0,0 +1,32 @@
package cn.hutool.core.lang.func;
import java.util.function.Supplier;
/**
* 1参数Supplier
*
* @param <T> 目标 类型
* @param <P1> 参数一 类型
* @author TomXin
* @since 5.7.21
*/
@FunctionalInterface
public interface Supplier1<T, P1> {
/**
* 生成实例的方法
*
* @param p1 参数一
* @return 目标对象
*/
T get(P1 p1);
/**
* 将带有参数的Supplier转换为无参{@link Supplier}
*
* @param p1 参数1
* @return {@link Supplier}
*/
default Supplier<T> toSupplier(P1 p1) {
return () -> get(p1);
}
}

View File

@ -0,0 +1,36 @@
package cn.hutool.core.lang.func;
import java.util.function.Supplier;
/**
* 两个参数的Supplier
*
* @param <T> 目标 类型
* @param <P1> 参数一 类型
* @param <P2> 参数二 类型
* @author TomXin
* @since 5.7.21
*/
@FunctionalInterface
public interface Supplier2<T, P1, P2> {
/**
* 生成实例的方法
*
* @param p1 参数一
* @param p2 参数二
* @return 目标对象
*/
T get(P1 p1, P2 p2);
/**
* 将带有参数的Supplier转换为无参{@link Supplier}
*
* @param p1 参数1
* @param p2 参数2
* @return {@link Supplier}
*/
default Supplier<T> toSupplier(P1 p1, P2 p2) {
return () -> get(p1, p2);
}
}

View File

@ -0,0 +1,39 @@
package cn.hutool.core.lang.func;
import java.util.function.Supplier;
/**
* 3参数Supplier
*
* @param <T> 目标类型
* @param <P1> 参数一类型
* @param <P2> 参数二类型
* @param <P3> 参数三类型
* @author TomXin
* @since 5.7.21
*/
@FunctionalInterface
public interface Supplier3<T, P1, P2, P3> {
/**
* 生成实例的方法
*
* @param p1 参数一
* @param p2 参数二
* @param p3 参数三
* @return 目标对象
*/
T get(P1 p1, P2 p2, P3 p3);
/**
* 将带有参数的Supplier转换为无参{@link Supplier}
*
* @param p1 参数1
* @param p2 参数2
* @param p3 参数3
* @return {@link Supplier}
*/
default Supplier<T> toSupplier(P1 p1, P2 p2, P3 p3) {
return () -> get(p1, p2, p3);
}
}

View File

@ -0,0 +1,42 @@
package cn.hutool.core.lang.func;
import java.util.function.Supplier;
/**
* 4参数Supplier
*
* @param <T> 目标 类型
* @param <P1> 参数一 类型
* @param <P2> 参数二 类型
* @param <P3> 参数三 类型
* @param <P4> 参数四 类型
* @author TomXin
* @since 5.7.21
*/
@FunctionalInterface
public interface Supplier4<T, P1, P2, P3, P4> {
/**
* 生成实例的方法
*
* @param p1 参数一
* @param p2 参数二
* @param p3 参数三
* @param p4 参数四
* @return 目标对象
*/
T get(P1 p1, P2 p2, P3 p3, P4 p4);
/**
* 将带有参数的Supplier转换为无参{@link Supplier}
*
* @param p1 参数1
* @param p2 参数2
* @param p3 参数3
* @param p4 参数4
* @return {@link Supplier}
*/
default Supplier<T> toSupplier(P1 p1, P2 p2, P3 p3, P4 p4) {
return () -> get(p1, p2, p3, p4);
}
}

View File

@ -0,0 +1,45 @@
package cn.hutool.core.lang.func;
import java.util.function.Supplier;
/**
* 5参数Supplier
*
* @param <T> 目标 类型
* @param <P1> 参数一 类型
* @param <P2> 参数二 类型
* @param <P3> 参数三 类型
* @param <P4> 参数四 类型
* @param <P5> 参数五 类型
* @author TomXin
* @since 5.7.21
*/
@FunctionalInterface
public interface Supplier5<T, P1, P2, P3, P4, P5> {
/**
* 生成实例的方法
*
* @param p1 参数一
* @param p2 参数二
* @param p3 参数三
* @param p4 参数四
* @param p5 参数五
* @return 目标对象
*/
T get(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5);
/**
* 将带有参数的Supplier转换为无参{@link Supplier}
*
* @param p1 参数1
* @param p2 参数2
* @param p3 参数3
* @param p4 参数4
* @param p5 参数5
* @return {@link Supplier}
*/
default Supplier<T> toSupplier(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) {
return () -> get(p1, p2, p3, p4, p5);
}
}

View File

@ -3601,6 +3601,46 @@ public class CharSequenceUtil {
return stringBuilder.toString();
}
/**
* 替换指定字符串的指定区间内字符为指定字符串字符串只重复一次<br>
* 此方法使用{@link String#codePoints()}完成拆分替换
*
* @param str 字符串
* @param startInclude 开始位置包含
* @param endExclude 结束位置不包含
* @param replacedStr 被替换的字符串
* @return 替换后的字符串
* @since 3.2.1
*/
public static String replace(CharSequence str, int startInclude, int endExclude, CharSequence replacedStr) {
if (isEmpty(str)) {
return str(str);
}
final String originalStr = str(str);
int[] strCodePoints = originalStr.codePoints().toArray();
final int strLength = strCodePoints.length;
if (startInclude > strLength) {
return originalStr;
}
if (endExclude > strLength) {
endExclude = strLength;
}
if (startInclude > endExclude) {
// 如果起始位置大于结束位置不替换
return originalStr;
}
final StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < startInclude; i++) {
stringBuilder.append(new String(strCodePoints, i, 1));
}
stringBuilder.append(replacedStr);
for (int i = endExclude; i < strLength; i++) {
stringBuilder.append(new String(strCodePoints, i, 1));
}
return stringBuilder.toString();
}
/**
* 替换所有正则匹配的文本并使用自定义函数决定如何替换<br>
* replaceFun可以通过{@link Matcher}提取出匹配到的内容的不同部分然后经过重新处理组装变成新的内容放回原位

View File

@ -1,6 +1,7 @@
package cn.hutool.core.text.csv;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.ArrayIter;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
@ -45,6 +46,10 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
* 是否处于新行开始
*/
private boolean newline = true;
/**
* 是否首行即CSV开始的位置当初始化时默认为true一旦写入内容为false
*/
private boolean isFirstLine = true;
// --------------------------------------------------------------------------------------------------- Constructor start
@ -183,13 +188,7 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
* @throws IORuntimeException IO异常
*/
public CsvWriter write(String[]... lines) throws IORuntimeException {
if (ArrayUtil.isNotEmpty(lines)) {
for (final String[] values : lines) {
appendLine(values);
}
flush();
}
return this;
return write(new ArrayIter<>(lines));
}
/**
@ -320,9 +319,14 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
public CsvWriter writeComment(String comment) {
Assert.notNull(this.config.commentCharacter, "Comment is disable!");
try {
if(isFirstLine){
// 首行不补换行符
isFirstLine = false;
}else {
writer.write(config.lineDelimiter);
}
writer.write(this.config.commentCharacter);
writer.write(comment);
writer.write(config.lineDelimiter);
newline = true;
} catch (IOException e) {
throw new IORuntimeException(e);
@ -366,12 +370,17 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
* @param fields 字段列表 ({@code null} 值会被做为空值追加)
* @throws IOException IO异常
*/
private void doAppendLine(final String... fields) throws IOException {
private void doAppendLine(String... fields) throws IOException {
if (null != fields) {
if(isFirstLine){
// 首行不补换行符
isFirstLine = false;
}else {
writer.write(config.lineDelimiter);
}
for (String field : fields) {
appendField(field);
}
writer.write(config.lineDelimiter);
newline = true;
}
}

View File

@ -0,0 +1,93 @@
package cn.hutool.core.builder;
import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.junit.Assert;
import org.junit.Test;
/**
* {@link GenericBuilder} 单元测试类
*
* @author TomXin
*/
public class GenericBuilderTest {
@Test
public void test() {
Box box = GenericBuilder
.of(Box::new)
.with(Box::setId, 1024L)
.with(Box::setTitle, "Hello World!")
.with(Box::setLength, 9)
.with(Box::setWidth, 8)
.with(Box::setHeight, 7)
.build();
Assert.assertEquals(1024L, box.getId().longValue());
Assert.assertEquals("Hello World!", box.getTitle());
Assert.assertEquals(9, box.getLength().intValue());
Assert.assertEquals(8, box.getWidth().intValue());
Assert.assertEquals(7, box.getHeight().intValue());
// 对象修改
Box boxModified = GenericBuilder
.of(() -> box)
.with(Box::setTitle, "Hello Friend!")
.with(Box::setLength, 3)
.with(Box::setWidth, 4)
.with(Box::setHeight, 5)
.build();
Assert.assertEquals(1024L, boxModified.getId().longValue());
Assert.assertEquals("Hello Friend!", box.getTitle());
Assert.assertEquals(3, boxModified.getLength().intValue());
Assert.assertEquals(4, boxModified.getWidth().intValue());
Assert.assertEquals(5, boxModified.getHeight().intValue());
// 多参数构造
Box box1 = GenericBuilder
.of(Box::new, 2048L, "Hello Partner!", 222, 333, 444)
.with(Box::alis)
.build();
Assert.assertEquals(2048L, box1.getId().longValue());
Assert.assertEquals("Hello Partner!", box1.getTitle());
Assert.assertEquals(222, box1.getLength().intValue());
Assert.assertEquals(333, box1.getWidth().intValue());
Assert.assertEquals(444, box1.getHeight().intValue());
}
@Getter
@Setter
@ToString
@Accessors(chain = true)
public static class Box {
private Long id;
private String title;
private Integer length;
private Integer width;
private Integer height;
private String titleAlias;
public Box() {
}
public Box(Long id, String title, Integer length, Integer width, Integer height) {
this.id = id;
this.title = title;
this.length = length;
this.width = width;
this.height = height;
}
public void alis() {
if (StrUtil.isNotBlank(this.title)) {
this.titleAlias = "TomXin:\"" + title + "\"";
}
}
}
}

View File

@ -19,7 +19,7 @@ import java.util.Map;
public class ListUtilTest {
@Test
public void splitTest(){
public void splitTest() {
List<List<Object>> lists = ListUtil.split(null, 3);
Assert.assertEquals(ListUtil.empty(), lists);
@ -60,7 +60,7 @@ public class ListUtilTest {
}
@Test
public void splitAvgTest(){
public void splitAvgTest() {
List<List<Object>> lists = ListUtil.splitAvg(null, 3);
Assert.assertEquals(ListUtil.empty(), lists);
@ -80,13 +80,13 @@ public class ListUtilTest {
}
@Test(expected = IllegalArgumentException.class)
public void splitAvgNotZero(){
public void splitAvgNotZero() {
// limit不能小于等于0
ListUtil.splitAvg(Arrays.asList(1, 2, 3, 4), 0);
}
@Test
public void editTest(){
public void editTest() {
List<String> a = ListUtil.toLinkedList("1", "2", "3");
final List<String> filter = (List<String>) CollUtil.edit(a, str -> "edit" + str);
Assert.assertEquals("edit1", filter.get(0));
@ -104,7 +104,7 @@ public class ListUtilTest {
}
@Test
public void pageTest(){
public void pageTest() {
List<Integer> a = ListUtil.toLinkedList(1, 2, 3,4,5);
PageUtil.setFirstPageNo(1);
@ -167,10 +167,13 @@ public class ListUtilTest {
Assert.assertArrayEquals(new int[]{}, pageListData.get(0).stream().mapToInt(Integer::valueOf).toArray());
Assert.assertArrayEquals(new int[]{3, 4}, pageListData.get(1).stream().mapToInt(Integer::valueOf).toArray());
Assert.assertArrayEquals(new int[]{5}, pageListData.get(2).stream().mapToInt(Integer::valueOf).toArray());
// 恢复默认值避免影响其他测试用例
PageUtil.setFirstPageNo(0);
}
@Test
public void subTest(){
public void subTest() {
final List<Integer> of = ListUtil.of(1, 2, 3, 4);
final List<Integer> sub = ListUtil.sub(of, 2, 4);
sub.remove(0);
@ -181,10 +184,10 @@ public class ListUtilTest {
}
@Test
public void sortByPropertyTest(){
public void sortByPropertyTest() {
@Data
@AllArgsConstructor
class TestBean{
class TestBean {
private int order;
private String name;
}

View File

@ -1,15 +1,14 @@
package cn.hutool.core.convert;
import cn.hutool.core.date.DateUtil;
import org.junit.Assert;
import org.junit.Test;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import org.junit.Assert;
import org.junit.Test;
import cn.hutool.core.date.DateUtil;
public class DateConvertTest {
@Test
@ -28,7 +27,7 @@ public class DateConvertTest {
int dateLong = -1497600000;
Date value = Convert.toDate(dateLong);
Assert.assertNotNull(value);
Assert.assertEquals("Mon Dec 15 00:00:00 CST 1969", value.toString());
Assert.assertEquals("Mon Dec 15 00:00:00 CST 1969", value.toString().replace("GMT+08:00", "CST"));
final java.sql.Date sqlDate = Convert.convert(java.sql.Date.class, dateLong);
Assert.assertNotNull(sqlDate);
@ -53,18 +52,18 @@ public class DateConvertTest {
java.sql.Date value2 = Convert.convert(java.sql.Date.class, timeLong);
Assert.assertEquals(timeLong, value2.getTime());
}
@Test
public void toLocalDateTimeTest() {
Date src = new Date();
LocalDateTime ldt = Convert.toLocalDateTime(src);
Assert.assertEquals(ldt, DateUtil.toLocalDateTime(src));
Timestamp ts = Timestamp.from(src.toInstant());
ldt = Convert.toLocalDateTime(ts);
Assert.assertEquals(ldt, DateUtil.toLocalDateTime(src));
String str = "2020-12-12 12:12:12.0";
ldt = Convert.toLocalDateTime(str);
Assert.assertEquals(ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S")), str);

View File

@ -692,8 +692,11 @@ public class DateUtilTest {
@Test
public void parseCSTTest() {
String dateStr = "Wed Sep 16 11:26:23 CST 2009";
Console.log(TimeZone.getDefault().getDisplayName());
SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US);
// Asia/Shanghai是以地区命名的地区标准时在中国叫CST因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00"会导致相差一个小时
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
final DateTime parse = DateUtil.parse(dateStr, sdf);
DateTime dateTime = DateUtil.parseCST(dateStr);
@ -1004,4 +1007,17 @@ public class DateUtilTest {
final DateTime parse = DateUtil.parse("2021-12-01", DatePattern.NORM_DATE_FORMATTER);
Assert.assertEquals("2021-12-01 00:00:00", parse.toString());
}
@Test
public void isSameWeekTest() {
// 周六与周日比较
final boolean isSameWeek = DateUtil.isSameWeek(DateTime.of("2022-01-01", "yyyy-MM-dd"), DateTime.of("2022-01-02", "yyyy-MM-dd"), true);
Assert.assertTrue(isSameWeek);
// 周日与周一比较
final boolean isSameWeek1 = DateUtil.isSameWeek(DateTime.of("2022-01-02", "yyyy-MM-dd"), DateTime.of("2022-01-03", "yyyy-MM-dd"), false);
Assert.assertTrue(isSameWeek1);
// 跨月比较
final boolean isSameWeek2 = DateUtil.isSameWeek(DateTime.of("2021-12-29", "yyyy-MM-dd"), DateTime.of("2022-01-01", "yyyy-MM-dd"), true);
Assert.assertTrue(isSameWeek2);
}
}

View File

@ -197,4 +197,24 @@ public class LocalDateTimeUtilTest {
Assert.assertTrue(LocalDateTimeUtil.isOverlap(oneStartTime2,oneEndTime2,realStartTime,realEndTime));
Assert.assertFalse(LocalDateTimeUtil.isOverlap(oneStartTime3,oneEndTime3,realStartTime,realEndTime));
}
@Test
public void weekOfYearTest(){
LocalDate date1 = LocalDate.of(2021, 12, 31);
final int weekOfYear1 = LocalDateTimeUtil.weekOfYear(date1);
Assert.assertEquals(52, weekOfYear1);
final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay());
Assert.assertEquals(52, weekOfYear2);
}
@Test
public void weekOfYearTest2(){
LocalDate date1 = LocalDate.of(2022, 1, 31);
final int weekOfYear1 = LocalDateTimeUtil.weekOfYear(date1);
Assert.assertEquals(5, weekOfYear1);
final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay());
Assert.assertEquals(5, weekOfYear2);
}
}

View File

@ -37,4 +37,15 @@ public class MonthTest {
lastDay = Month.of(Calendar.DECEMBER).getLastDay(true);
Assert.assertEquals(31, lastDay);
}
@Test
public void toJdkMonthTest(){
final java.time.Month month = Month.AUGUST.toJdkMonth();
Assert.assertEquals(java.time.Month.AUGUST, month);
}
@Test(expected = IllegalArgumentException.class)
public void toJdkMonthTest2(){
Month.UNDECIMBER.toJdkMonth();
}
}

View File

@ -21,11 +21,8 @@ public class CheckedUtilTest {
@Test
public void sleepTest() {
VoidFunc0 func = () -> Thread.sleep(1000L);
func.callWithRuntimeException();
}
@ -39,7 +36,6 @@ public class CheckedUtilTest {
} catch (Exception re) {
Assert.assertTrue(re instanceof RuntimeException);
}
}
@SuppressWarnings("ConstantConditions")

View File

@ -36,6 +36,32 @@ public class RangeTest {
Assert.assertFalse(range.hasNext());
}
@Test
public void dateRangeFuncTest() {
DateTime start = DateUtil.parse("2021-01-01");
DateTime end = DateUtil.parse("2021-01-03");
List<Integer> dayOfMonthList = DateUtil.rangeFunc(start, end, DateField.DAY_OF_YEAR, a -> DateTime.of(a).dayOfMonth());
Assert.assertArrayEquals(dayOfMonthList.toArray(new Integer[]{}), new Integer[]{1, 2, 3});
List<Integer> dayOfMonthList2 = DateUtil.rangeFunc(null, null, DateField.DAY_OF_YEAR, a -> DateTime.of(a).dayOfMonth());
Assert.assertArrayEquals(dayOfMonthList2.toArray(new Integer[]{}), new Integer[]{});
}
@Test
public void dateRangeConsumeTest() {
DateTime start = DateUtil.parse("2021-01-01");
DateTime end = DateUtil.parse("2021-01-03");
StringBuilder sb = new StringBuilder();
DateUtil.rangeConsume(start, end, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#"));
Assert.assertEquals(sb.toString(), "1#2#3#");
StringBuilder sb2 = new StringBuilder();
DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#"));
Assert.assertEquals(sb2.toString(), "");
}
@Test
public void dateRangeTest2() {
DateTime start = DateUtil.parse("2021-01-31");

View File

@ -22,6 +22,13 @@ public class CharSequenceUtilTest {
Assert.assertEquals(replace, result);
}
@Test
public void replaceByStrTest(){
String replace = "SSM15930297701BeryAllen";
String result = CharSequenceUtil.replace(replace, 5, 12, "***");
Assert.assertEquals("SSM15***01BeryAllen", result);
}
@Test
public void addPrefixIfNotTest(){
String str = "hutool";

View File

@ -12,4 +12,10 @@ public class NamingCaseTest {
.set("customerNickV2", "customer_nick_v2")
.forEach((key, value) -> Assert.assertEquals(value, NamingCase.toUnderlineCase(key)));
}
@Test
public void toUnderLineCaseTest2(){
final String wPRunOZTime = NamingCase.toUnderlineCase("wPRunOZTime");
Assert.assertEquals("w_P_run_OZ_time", wPRunOZTime);
}
}

View File

@ -19,6 +19,8 @@ public class CsvWriterTest {
CharsetUtil.CHARSET_GBK, false, csvWriteConfig);
writer.writeHeaderLine("name", "gender", "address");
writer.writeLine("张三", "", "XX市XX区");
writer.writeLine("李四", "", "XX市XX区,01号");
writer.close();
}
}

View File

@ -5,32 +5,31 @@ import org.junit.Test;
/**
* 分页单元测试
* @author Looly
*
* @author Looly
*/
public class PageUtilTest {
@Test
public void transToStartEndTest(){
PageUtil.setFirstPageNo(0);
public void transToStartEndTest() {
int[] startEnd1 = PageUtil.transToStartEnd(0, 10);
Assert.assertEquals(0, startEnd1[0]);
Assert.assertEquals(10, startEnd1[1]);
int[] startEnd2 = PageUtil.transToStartEnd(1, 10);
Assert.assertEquals(10, startEnd2[0]);
Assert.assertEquals(20, startEnd2[1]);
}
@Test
public void totalPage(){
public void totalPage() {
int totalPage = PageUtil.totalPage(20, 3);
Assert.assertEquals(7, totalPage);
}
@Test
public void rainbowTest() {
int[] rainbow = PageUtil.rainbow(5, 20, 6);
Assert.assertArrayEquals(new int[] {3, 4, 5, 6, 7, 8}, rainbow);
Assert.assertArrayEquals(new int[]{3, 4, 5, 6, 7, 8}, rainbow);
}
}

View File

@ -27,7 +27,7 @@
<sqlite.version>3.36.0.3</sqlite.version>
<!-- 此处固定2.5.x支持到JDK8 -->
<hsqldb.version>2.5.2</hsqldb.version>
<jedis.version>4.0.0</jedis.version>
<jedis.version>4.1.1</jedis.version>
</properties>
<dependencies>
@ -81,7 +81,7 @@
<dependency>
<groupId>com.github.chris2018998</groupId>
<artifactId>beecp</artifactId>
<version>3.3.0</version>
<version>3.3.1</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
@ -143,13 +143,13 @@
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
<version>8.0.28</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.3.1</version>
<version>42.3.2</version>
<scope>test</scope>
</dependency>
<dependency>
@ -167,13 +167,13 @@
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.0.206</version>
<version>2.1.210</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ru.yandex.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId>
<version>0.3.1</version>
<version>0.3.2</version>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -0,0 +1,54 @@
package cn.hutool.db.sql;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import java.util.List;
/**
* 条件组<br>
* 用于构建复杂where条件
*
* @author tjh
* @since 5.7.21
*/
public class ConditionGroup extends Condition {
/**
* 条件列表
*/
private Condition[] conditions;
/**
* 追加条件
*
* @param conditions 条件列表
*/
public void addConditions(Condition... conditions) {
if (null == this.conditions) {
this.conditions = conditions;
} else {
this.conditions = ArrayUtil.addAll(this.conditions, conditions);
}
}
/**
* 将条件组转换为条件字符串使用括号包裹并回填占位符对应的参数值
*
* @param paramValues 参数列表用于回填占位符对应参数值
* @return 条件字符串
*/
@Override
public String toString(List<Object> paramValues) {
if (ArrayUtil.isEmpty(conditions)) {
return StrUtil.EMPTY;
}
final StringBuilder conditionStrBuilder = StrUtil.builder();
conditionStrBuilder.append("(");
// 将组内的条件构造为SQL因为toString会进行递归处理所有的条件组
conditionStrBuilder.append(ConditionBuilder.of(this.conditions).build(paramValues));
conditionStrBuilder.append(")");
return conditionStrBuilder.toString();
}
}

View File

@ -0,0 +1,28 @@
package cn.hutool.db.sql;
import cn.hutool.core.collection.ListUtil;
import org.junit.Assert;
import org.junit.Test;
public class ConditionGroupTest {
@Test
public void ConditionGroupToStringTest() {
Condition condition1 = new Condition("a", "A");
Condition condition2 = new Condition("b", "B");
condition2.setLinkOperator(LogicalOperator.OR);
Condition condition3 = new Condition("c", "C");
Condition condition4 = new Condition("d", "D");
ConditionGroup cg = new ConditionGroup();
cg.addConditions(condition1, condition2);
// 条件组嵌套情况
ConditionGroup cg2 = new ConditionGroup();
cg2.addConditions(cg, condition3);
final ConditionBuilder conditionBuilder = ConditionBuilder.of(cg2, condition4);
Assert.assertEquals("((a = ? OR b = ?) AND c = ?) AND d = ?", conditionBuilder.build());
Assert.assertEquals(ListUtil.of("A", "B", "C", "D"), conditionBuilder.getParamValues());
}
}

View File

@ -17,11 +17,6 @@
<description>Hutool 基于DFA的关键词查找</description>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-json</artifactId>

View File

@ -195,11 +195,21 @@ public final class SensitiveUtil {
*/
public static <T> T sensitiveFilter(T bean, boolean isGreedMatch, SensitiveProcessor sensitiveProcessor) {
String jsonText = JSONUtil.toJsonStr(bean);
@SuppressWarnings("unchecked")
final Class<T> c = (Class<T>) bean.getClass();
@SuppressWarnings("unchecked") final Class<T> c = (Class<T>) bean.getClass();
return JSONUtil.toBean(sensitiveFilter(jsonText, isGreedMatch, sensitiveProcessor), c);
}
/**
* 处理过滤文本中的敏感词默认替换成*
*
* @param text 文本
* @return 敏感词过滤处理后的文本
* @since 5.7.21
*/
public static String sensitiveFilter(String text) {
return sensitiveFilter(text, true, null);
}
/**
* 处理过滤文本中的敏感词默认替换成*
*
@ -214,13 +224,14 @@ public final class SensitiveUtil {
}
//敏感词过滤场景下不需要密集匹配
List<FoundWord> foundWordList = getFoundAllSensitive(text, false, isGreedMatch);
List<FoundWord> foundWordList = getFoundAllSensitive(text, true, isGreedMatch);
if (CollUtil.isEmpty(foundWordList)) {
return text;
}
sensitiveProcessor = sensitiveProcessor == null ? new SensitiveProcessor() {
} : sensitiveProcessor;
Map<Integer, FoundWord> foundWordMap = new HashMap<>(foundWordList.size());
final Map<Integer, FoundWord> foundWordMap = new HashMap<>(foundWordList.size(), 1);
foundWordList.forEach(foundWord -> foundWordMap.put(foundWord.getStartIndex(), foundWord));
int length = text.length();
StringBuilder textStringBuilder = new StringBuilder();

View File

@ -3,7 +3,6 @@ package cn.hutool.dfa;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Filter;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.StrUtil;
import java.util.ArrayList;
@ -247,15 +246,15 @@ public class WordTree extends HashMap<Character, WordTree> {
List<FoundWord> foundWords = new ArrayList<>();
WordTree current = this;
int length = text.length();
final int length = text.length();
final Filter<Character> charFilter = this.charFilter;
//存放查找到的字符缓存完整出现一个词时加到findedWords中否则清空
final StrBuilder wordBuffer = StrUtil.strBuilder();
final StrBuilder keyBuffer = StrUtil.strBuilder();
final StringBuilder wordBuffer = StrUtil.builder();
final StringBuilder keyBuffer = StrUtil.builder();
char currentChar;
for (int i = 0; i < length; i++) {
wordBuffer.reset();
keyBuffer.reset();
wordBuffer.setLength(0);
keyBuffer.setLength(0);
for (int j = i; j < length; j++) {
currentChar = text.charAt(j);
// Console.log("i: {}, j: {}, currentChar: {}", i, j, currentChar);
@ -284,6 +283,7 @@ public class WordTree extends HashMap<Character, WordTree> {
if (false == isDensityMatch) {
//如果非密度匹配跳过匹配到的词
i = j;
break;
}
if (false == isGreedMatch) {
//如果懒惰匹配非贪婪匹配当遇到第一个结尾标记就结束本轮匹配

View File

@ -47,7 +47,7 @@ public class DfaTest {
}
/**
* 贪婪匹配原则测试
* 贪婪非密集匹配原则测试
*/
@Test
public void greedMatchTest() {
@ -56,15 +56,15 @@ public class DfaTest {
// -----------------------------------------------------------------------------------------------------------------------------------
// 情况三匹配到最长关键词跳过已经匹配的关键词
// 匹配到由于到最长匹配因此土豆接着被匹配
// 由于大土豆被匹配土豆被跳过由于刚出锅匹配出锅被跳过
// 匹配到由于非密集匹配因此从下一个字符开始查找匹配到土豆接着被匹配
// 由于刚出锅被匹配由于非密集匹配出锅被跳过
List<String> matchAll = tree.matchAll(text, -1, false, true);
Assert.assertEquals(matchAll, CollUtil.newArrayList("", "土^豆", "刚出锅"));
Assert.assertEquals(matchAll, CollUtil.newArrayList("", "土^豆", "刚出锅"));
}
/**
* 密集匹配原则匹配和贪婪匹配原则测试
* 密集匹配原则匹配和贪婪匹配原则测试
*/
@Test
public void densityAndGreedMatchTest() {
@ -80,6 +80,29 @@ public class DfaTest {
}
@Test
public void densityAndGreedMatchTest2(){
WordTree tree = new WordTree();
tree.addWord("");
tree.addWord("赵阿");
tree.addWord("赵阿三");
final List<FoundWord> result = tree.matchAllWords("赵阿三在做什么", -1, true, true);
Assert.assertEquals(3, result.size());
Assert.assertEquals("", result.get(0).getWord());
Assert.assertEquals(0, result.get(0).getStartIndex().intValue());
Assert.assertEquals(0, result.get(0).getEndIndex().intValue());
Assert.assertEquals("赵阿", result.get(1).getWord());
Assert.assertEquals(0, result.get(1).getStartIndex().intValue());
Assert.assertEquals(1, result.get(1).getEndIndex().intValue());
Assert.assertEquals("赵阿三", result.get(2).getWord());
Assert.assertEquals(0, result.get(2).getStartIndex().intValue());
Assert.assertEquals(2, result.get(2).getEndIndex().intValue());
}
/**
* 停顿词测试
*/

View File

@ -1,5 +1,7 @@
package cn.hutool.dfa;
import cn.hutool.core.collection.ListUtil;
import lombok.Data;
import org.junit.Assert;
import org.junit.Test;
@ -24,25 +26,17 @@ public class SensitiveUtilTest {
Assert.assertEquals(bean.getStr(), "我有一颗$*******的");
}
@Data
public static class TestBean {
private String str;
private Integer num;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
}
@Test
public void issue2126(){
SensitiveUtil.init(ListUtil.of("", "赵阿", "赵阿三"));
String result = SensitiveUtil.sensitiveFilter("赵阿三在做什么。", true, null);
Assert.assertEquals("***在做什么。", result);
}
}

View File

@ -23,7 +23,7 @@
<rythm.version>1.4.1</rythm.version>
<freemarker.version>2.3.31</freemarker.version>
<enjoy.version>4.9.16</enjoy.version>
<thymeleaf.version>3.0.14.RELEASE</thymeleaf.version>
<thymeleaf.version>3.0.15.RELEASE</thymeleaf.version>
<mail.version>1.6.2</mail.version>
<jsch.version>0.1.55</jsch.version>
<sshj.version>0.32.0</sshj.version>
@ -31,7 +31,7 @@
<net.version>3.8.0</net.version>
<emoji-java.version>5.1.1</emoji-java.version>
<servlet-api.version>4.0.1</servlet-api.version>
<spring-boot.version>2.6.2</spring-boot.version>
<spring-boot.version>2.6.3</spring-boot.version>
<cglib.version>3.3.0</cglib.version>
</properties>
@ -340,7 +340,7 @@
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>pinyin</artifactId>
<version>0.2.2</version>
<version>0.3.1</version>
<optional>true</optional>
</dependency>
<dependency>
@ -446,7 +446,7 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.14</version>
<version>5.3.15</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>

View File

@ -126,11 +126,11 @@ public class EmojiUtil {
* @return 替换后的字符串
*/
public static String toHtmlHex(String str) {
return EmojiParser.parseToHtmlHexadecimal(str);
return toHtml(str, true);
}
/**
* 将字符串中的Unicode Emoji字符转换为HTML表现形式
* 将字符串中的Unicode Emoji字符转换为HTML表现形式Hex方式
* <p>
* 例如<code>👦🏿</code> 转换为 <code>&amp;#128102;</code>
*
@ -138,7 +138,24 @@ public class EmojiUtil {
* @return 替换后的字符串
*/
public static String toHtml(String str) {
return EmojiParser.parseToHtmlHexadecimal(str);
return toHtml(str, false);
}
/**
* 将字符串中的Unicode Emoji字符转换为HTML表现形式例如
* <pre>
* 如果为hex形式<code>👦🏿</code> 转换为 <code>&amp;#x1f466;</code>
* 否则<code>👦🏿</code> 转换为 <code>&amp;#128102;</code>
* </pre>
*
* @param str 包含Emoji Unicode字符的字符串
* @param isHex 是否hex形式
* @return 替换后的字符串
* @since 5.7.21
*/
public static String toHtml(String str, boolean isHex) {
return isHex ? EmojiParser.parseToHtmlHexadecimal(str) :
EmojiParser.parseToHtmlDecimal(str);
}
/**

View File

@ -94,4 +94,6 @@ public class QrCodeUtilTest {
final BufferedImage image = QrCodeUtil.generate("content111", BarcodeFormat.PDF_417, QrConfig.create());
Assert.assertNotNull(image);
}
}

View File

@ -249,22 +249,20 @@ public class JSONWriter extends Writer {
* 此方法输出的值不包装引号
*
* @param number 数字
* @return this
*/
private JSONWriter writeNumberValue(Number number) {
private void writeNumberValue(Number number) {
// since 5.6.2可配置是否去除末尾多余0例如如果为true,5.0返回5
final boolean isStripTrailingZeros = null == config || config.isStripTrailingZeros();
return writeRaw(NumberUtil.toStr(number, isStripTrailingZeros));
writeRaw(NumberUtil.toStr(number, isStripTrailingZeros));
}
/**
* 写出Boolean值直接写出true或false,不适用引号包装
*
* @param value Boolean值
* @return this
*/
private JSONWriter writeBooleanValue(Boolean value) {
return writeRaw(value.toString());
private void writeBooleanValue(Boolean value) {
writeRaw(value.toString());
}
/**
@ -273,9 +271,8 @@ public class JSONWriter extends Writer {
* 如果toJSONString()返回null调用toString()方法并使用双引号包装
*
* @param jsonString {@link JSONString}
* @return this
*/
private JSONWriter writeJSONStringValue(JSONString jsonString) {
private void writeJSONStringValue(JSONString jsonString) {
String valueStr;
try {
valueStr = jsonString.toJSONString();
@ -287,7 +284,6 @@ public class JSONWriter extends Writer {
} else {
writeStrValue(jsonString.toString());
}
return this;
}
/**
@ -297,30 +293,26 @@ public class JSONWriter extends Writer {
* JSON字符串中不能包含控制字符和未经转义的引号和反斜杠
*
* @param csq 字符串
* @return this
*/
private JSONWriter writeStrValue(String csq) {
private void writeStrValue(String csq) {
try {
JSONUtil.quote(csq, writer);
} catch (IOException e) {
throw new IORuntimeException(e);
}
return this;
}
/**
* 写出空格
*
* @param count 空格数
* @return this
*/
private JSONWriter writeSpace(int count) {
private void writeSpace(int count) {
if (indentFactor > 0) {
for (int i = 0; i < count; i++) {
writeRaw(CharUtil.SPACE);
}
}
return this;
}
/**

View File

@ -19,7 +19,7 @@ public class Issue677Test {
final String jsonStr = JSONUtil.toJsonStr(dto);
final AuditResultDto auditResultDto = JSONUtil.toBean(jsonStr, AuditResultDto.class);
Assert.assertEquals("Mon Dec 15 00:00:00 CST 1969", auditResultDto.getDate().toString());
Assert.assertEquals("Mon Dec 15 00:00:00 CST 1969", auditResultDto.getDate().toString().replace("GMT+08:00", "CST"));
}
@Data

View File

@ -24,7 +24,7 @@
<log4j2.version>2.17.1</log4j2.version>
<commons-logging.version>1.2</commons-logging.version>
<tinylog.version>1.3.6</tinylog.version>
<jboss-logging.version>3.4.2.Final</jboss-logging.version>
<jboss-logging.version>3.4.3.Final</jboss-logging.version>
<logtube.version>0.43.0</logtube.version>
</properties>

View File

@ -1,8 +1,8 @@
/**
* Hutool-log只是一个日志的通用门面功能类似于Slf4j<br>
* 根据加入ClassPath中的jar包动态检测日志实现的方式使日志使用更加便利灵活
*
* 根据加入ClassPath中的jar包动态检测日志实现的方式使日志使用更加便利灵活
*
* @author looly
*
*/
package cn.hutool.log;
package cn.hutool.log;

View File

@ -45,7 +45,7 @@
<dependency>
<groupId>org.ofdrw</groupId>
<artifactId>ofdrw-full</artifactId>
<version>1.17.6</version>
<version>1.17.8</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>

View File

@ -30,7 +30,7 @@
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>6.0.0</version>
<version>6.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>

View File

@ -1,5 +1,6 @@
package cn.hutool.system.oshi;
import cn.hutool.core.util.NumberUtil;
import oshi.hardware.CentralProcessor;
import java.text.DecimalFormat;
@ -31,7 +32,7 @@ public class CpuInfo {
/**
* CPU用户使用率
*/
private double used;
private double user;
/**
* CPU当前等待率
@ -75,16 +76,16 @@ public class CpuInfo {
* @param cpuNum CPU核心数
* @param toTal CPU总的使用率
* @param sys CPU系统使用率
* @param used CPU用户使用率
* @param user CPU用户使用率
* @param wait CPU当前等待率
* @param free CPU当前空闲率
* @param cpuModel CPU型号信息
*/
public CpuInfo(Integer cpuNum, double toTal, double sys, double used, double wait, double free, String cpuModel) {
public CpuInfo(Integer cpuNum, double toTal, double sys, double user, double wait, double free, String cpuModel) {
this.cpuNum = cpuNum;
this.toTal = toTal;
this.sys = sys;
this.used = used;
this.user = user;
this.wait = wait;
this.free = free;
this.cpuModel = cpuModel;
@ -114,12 +115,12 @@ public class CpuInfo {
this.sys = sys;
}
public double getUsed() {
return used;
public double getUser() {
return user;
}
public void setUsed(double used) {
this.used = used;
public void setUser(double user) {
this.user = user;
}
public double getWait() {
@ -154,16 +155,25 @@ public class CpuInfo {
this.ticks = ticks;
}
/**
* 获取用户+系统的总的CPU使用率
*
* @return 总CPU使用率
*/
public double getUsed() {
return NumberUtil.sub(100, this.free);
}
@Override
public String toString() {
return "CpuInfo{" +
"CPU核心数=" + cpuNum +
", CPU总的使用率=" + toTal +
", CPU系统使用率=" + sys +
", CPU用户使用率=" + used +
", CPU用户使用率=" + user +
", CPU当前等待率=" + wait +
", CPU当前空闲率=" + free +
", CPU利用率=" + LOAD_FORMAT.format(100 - free) +
", CPU利用率=" + getUsed() +
", CPU型号信息='" + cpuModel + '\'' +
'}';
}
@ -185,7 +195,7 @@ public class CpuInfo {
final long totalCpu = ticks.totalCpu();
this.toTal = totalCpu;
this.sys = formatDouble(ticks.cSys, totalCpu);
this.used = formatDouble(ticks.user, totalCpu);
this.user = formatDouble(ticks.user, totalCpu);
this.wait = formatDouble(ticks.ioWait, totalCpu);
this.free = formatDouble(ticks.idle, totalCpu);
}

View File

@ -1,8 +1,10 @@
package cn.hutool.system;
import cn.hutool.core.lang.Console;
import cn.hutool.system.oshi.CpuInfo;
import cn.hutool.system.oshi.OshiUtil;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import oshi.software.os.OSProcess;
@ -28,4 +30,12 @@ public class OshiTest {
final OSProcess currentProcess = OshiUtil.getCurrentProcess();
Assert.assertEquals("java", currentProcess.getName());
}
@Test
@Ignore
public void getUsedTest(){
while (true){
Console.log(OshiUtil.getCpuInfo().getUsed());
}
}
}