Merge branch 'v5-dev' of https://gitee.com/GuoZG0328/hutool into v5-dev

This commit is contained in:
bob.guo
2021-12-01 11:42:58 +08:00
39 changed files with 487 additions and 92 deletions

View File

@@ -121,18 +121,19 @@ public class PropDesc {
/**
* 检查属性是否可读(即是否可以通过{@link #getValue(Object)}获取到值)
*
* @param checkTransient 是否检查Transient关键字或注解
* @return 是否可读
* @since 5.4.2
*/
public boolean isReadable(boolean checkTransient){
public boolean isReadable(boolean checkTransient) {
// 检查是否有getter方法或是否为public修饰
if(null == this.getter && false == ModifierUtil.isPublic(this.field)){
if (null == this.getter && false == ModifierUtil.isPublic(this.field)) {
return false;
}
// 检查transient关键字和@Transient注解
if(checkTransient && isTransientForGet()){
if (checkTransient && isTransientForGet()) {
return false;
}
@@ -164,7 +165,7 @@ public class PropDesc {
* 首先调用字段对应的Getter方法获取值如果Getter方法不存在则判断字段如果为public则直接获取字段值
*
* @param bean Bean对象
* @param targetType 返回属性值需要转换的类型null表示不转换
* @param targetType 返回属性值需要转换的类型null表示不转换
* @param ignoreError 是否忽略错误,包括转换错误和注入错误
* @return this
* @since 5.4.2
@@ -190,18 +191,19 @@ public class PropDesc {
/**
* 检查属性是否可读(即是否可以通过{@link #getValue(Object)}获取到值)
*
* @param checkTransient 是否检查Transient关键字或注解
* @return 是否可读
* @since 5.4.2
*/
public boolean isWritable(boolean checkTransient){
public boolean isWritable(boolean checkTransient) {
// 检查是否有getter方法或是否为public修饰
if(null == this.setter && false == ModifierUtil.isPublic(this.field)){
if (null == this.setter && false == ModifierUtil.isPublic(this.field)) {
return false;
}
// 检查transient关键字和@Transient注解
if(checkTransient && isTransientForSet()){
if (checkTransient && isTransientForSet()) {
return false;
}
@@ -239,7 +241,28 @@ public class PropDesc {
* @since 5.4.2
*/
public PropDesc setValue(Object bean, Object value, boolean ignoreNull, boolean ignoreError) {
if (ignoreNull && null == value) {
return setValue(bean, value, ignoreNull, ignoreError, true);
}
/**
* 设置属性值,可以自动转换字段类型为目标类型
*
* @param bean Bean对象
* @param value 属性值,可以为任意类型
* @param ignoreNull 是否忽略{@code null}值true表示忽略
* @param ignoreError 是否忽略错误,包括转换错误和注入错误
* @param override 是否覆盖目标值如果不覆盖会先读取bean的值非{@code null}则写,否则忽略。如果覆盖,则不判断直接写
* @return this
* @since 5.7.17
*/
public PropDesc setValue(Object bean, Object value, boolean ignoreNull, boolean ignoreError, boolean override) {
if (null == value && ignoreNull) {
return this;
}
// issue#I4JQ1N@Gitee
// 非覆盖模式下,如果目标值存在,则跳过
if (false == override && null != getValue(bean)) {
return this;
}

View File

@@ -140,13 +140,44 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
* Map转Map
*
* @param source 源Map
* @param dest 目标Map
* @param targetMap 目标Map
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private void mapToMap(Map source, Map dest) {
if (null != dest && null != source) {
dest.putAll(source);
}
private void mapToMap(Map source, Map targetMap) {
source.forEach((key, value)->{
final CopyOptions copyOptions = this.copyOptions;
final HashSet<String> ignoreSet = (null != copyOptions.ignoreProperties) ? CollUtil.newHashSet(copyOptions.ignoreProperties) : null;
// issue#I4JQ1N@Gitee
// 非覆盖模式下,如果目标值存在,则跳过
if(false == copyOptions.override && null != targetMap.get(key)){
return;
}
if(key instanceof CharSequence){
if (CollUtil.contains(ignoreSet, key)) {
// 目标属性值被忽略或值提供者无此key时跳过
return;
}
// 对key做映射映射后为null的忽略之
key = copyOptions.editFieldName(copyOptions.getMappedFieldName(key.toString(), false));
if(null == key){
return;
}
value = copyOptions.editFieldValue(key.toString(), value);
}
if ((null == value && copyOptions.ignoreNullValue) || source == value) {
// 当允许跳过空时,跳过
//值不能为bean本身防止循环引用此类也跳过
return;
}
targetMap.put(key, value);
});
}
/**
@@ -158,11 +189,11 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private void beanToMap(Object bean, Map targetMap) {
final HashSet<String> ignoreSet = (null != copyOptions.ignoreProperties) ? CollUtil.newHashSet(copyOptions.ignoreProperties) : null;
final CopyOptions copyOptions = this.copyOptions;
final HashSet<String> ignoreSet = (null != copyOptions.ignoreProperties) ? CollUtil.newHashSet(copyOptions.ignoreProperties) : null;
BeanUtil.descForEach(bean.getClass(), (prop)->{
if(false == prop.isReadable(copyOptions.isTransientSupport())){
if(false == prop.isReadable(copyOptions.transientSupport)){
// 忽略的属性跳过之
return;
}
@@ -178,6 +209,12 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
return;
}
// issue#I4JQ1N@Gitee
// 非覆盖模式下,如果目标值存在,则跳过
if(false == copyOptions.override && null != targetMap.get(key)){
return;
}
Object value;
try {
value = prop.getValue(bean);
@@ -230,7 +267,7 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
// 遍历目标bean的所有属性
BeanUtil.descForEach(actualEditable, (prop)->{
if(false == prop.isWritable(this.copyOptions.isTransientSupport())){
if(false == prop.isWritable(this.copyOptions.transientSupport)){
// 字段不可写,跳过之
return;
}
@@ -270,7 +307,7 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
return;
}
prop.setValue(bean, value, copyOptions.ignoreNullValue, copyOptions.ignoreError);
prop.setValue(bean, value, copyOptions.ignoreNullValue, copyOptions.ignoreError, copyOptions.override);
});
}
}

View File

@@ -65,7 +65,11 @@ public class CopyOptions implements Serializable {
/**
* 是否支持transient关键字修饰和@Transient注解如果支持被修饰的字段或方法对应的字段将被忽略。
*/
private boolean transientSupport = true;
protected boolean transientSupport = true;
/**
* 是否覆盖目标值,如果不覆盖,会先读取目标对象的值,非{@code null}则写,否则忽略。如果覆盖,则不判断直接写
*/
protected boolean override = true;
/**
* 创建拷贝选项
@@ -259,7 +263,9 @@ public class CopyOptions implements Serializable {
*
* @return 是否支持
* @since 5.4.2
* @deprecated 无需此方法,内部使用直接调用属性
*/
@Deprecated
public boolean isTransientSupport() {
return this.transientSupport;
}
@@ -276,6 +282,18 @@ public class CopyOptions implements Serializable {
return this;
}
/**
* 设置是否覆盖目标值,如果不覆盖,会先读取目标对象的值,非{@code null}则写,否则忽略。如果覆盖,则不判断直接写
*
* @param override 是否覆盖目标值
* @return this
* @since 5.7.17
*/
public CopyOptions setOverride(boolean override) {
this.override = override;
return this;
}
/**
* 获得映射后的字段名<br>
* 当非反向,则根据源字段名获取目标字段名,反之根据目标字段名获取源字段名。

View File

@@ -1291,6 +1291,46 @@ public class CollUtil {
return filter(collection, StrUtil::isNotBlank);
}
/**
* 移除集合中的多个元素,并将结果存放到指定的集合
* 此方法直接修改原集合
*
* @param <T> 集合类型
* @param <E> 集合元素类型
* @param resultCollection 存放移除结果的集合
* @param targetCollection 被操作移除元素的集合
* @param predicate 用于是否移除判断的过滤器
*/
public static <T extends Collection<E>, E> T removeWithAddIf(T targetCollection, T resultCollection, Predicate<? super E> predicate) {
Objects.requireNonNull(predicate);
final Iterator<E> each = targetCollection.iterator();
while (each.hasNext()) {
E next = each.next();
if (predicate.test(next)) {
resultCollection.add(next);
each.remove();
}
}
return resultCollection;
}
/**
* 移除集合中的多个元素,并将结果存放到生成的新集合中后返回<br>
* 此方法直接修改原集合
*
* @param <T> 集合类型
* @param <E> 集合元素类型
* @param targetCollection 被操作移除元素的集合
* @param predicate 用于是否移除判断的过滤器
* @return 移除结果的集合
* @since 5.7.17
*/
public static <T extends Collection<E>, E> List<E> removeWithAddIf(T targetCollection, Predicate<? super E> predicate) {
final List<E> removed = new ArrayList<>();
removeWithAddIf(targetCollection, removed, predicate);
return removed;
}
/**
* 通过Editor抽取集合元素中的某些值返回为新列表<br>
* 例如提供的是一个Bean列表通过Editor接口实现获取某个字段值返回这个字段值组成的新列表

View File

@@ -1,7 +1,7 @@
package cn.hutool.core.collection;
/**
* 集合相关工具类,包括数组,是{@link CollUtil} 的别名工具类
* 集合相关工具类,包括数组,是 {@link CollUtil} 的别名工具类
*
* @author xiaoleilu
* @see CollUtil

View File

@@ -56,7 +56,8 @@ public class RingIndexUtil {
}
/**
* 通过cas操作 实现对指定值内的回环累加
* 通过cas操作 实现对指定值内的回环累加<br>
* 此方法一般用于大量数据完成回环累加如数据库中的值大于int最大值
*
* @param modulo 回环周期值
* @param atomicLong 原子操作类

View File

@@ -1624,7 +1624,9 @@ public class DateUtil extends CalendarUtil {
*
* @param date 日期
* @return int
* @deprecated 2022年后结果溢出此方法废弃
*/
@Deprecated
public static int toIntSecond(Date date) {
return Integer.parseInt(DateUtil.format(date, "yyMMddHHmm"));
}

View File

@@ -253,8 +253,10 @@ public class Opt<T> {
* String hutool = Opt.ofBlankAble("hutool").mapOrElse(String::toUpperCase, () -> Console.log("yes")).mapOrElse(String::intern, () -> Console.log("Value is not present~")).get();
* }</pre>
*
* @param <U> map后新的类型
* @param mapper 包裹里的值存在时的操作
* @param emptyAction 包裹里的值不存在时的操作
* @return 新的类型的Opt
* @throws NullPointerException 如果包裹里的值存在时,执行的操作为 {@code null}, 或者包裹里的值不存在时的操作为 {@code null},则抛出{@code NPE}
*/
public <U> Opt<U> mapOrElse(Function<? super T, ? extends U> mapper, VoidFunc0 emptyAction) {

View File

@@ -73,7 +73,7 @@ public class LambdaUtil {
* @param func 需要解析的 lambda 对象
* @return 返回解析后的结果
*/
private static <T> SerializedLambda _resolve(Serializable func) {
private static SerializedLambda _resolve(Serializable func) {
return cache.get(func.getClass().getName(), () -> ReflectUtil.invoke(func, "writeReplace"));
}
}

View File

@@ -154,7 +154,7 @@ public class Calculator {
* @param peek peek
* @return 优先级
*/
public boolean compare(char cur, char peek) {// 如果是peek优先级高于cur返回true默认都是peek优先级要低
private boolean compare(char cur, char peek) {// 如果是peek优先级高于cur返回true默认都是peek优先级要低
final int offset = 40;
if(cur == '%'){
// %优先级最高
@@ -165,7 +165,7 @@ public class Calculator {
peek = 47;
}
return operatPriority[(peek) - offset] >= operatPriority[(cur) - offset];
return operatPriority[peek - offset] >= operatPriority[cur - offset];
}
/**

View File

@@ -1979,19 +1979,32 @@ public class CharSequenceUtil {
* 截取部分字符串这里一个汉字的长度认为是2
*
* @param str 字符串
* @param len 切割的位置
* @param len bytes切割的位置(包含)
* @param suffix 切割后加上后缀
* @return 切割后的字符串
* @since 3.1.1
*/
public static String subPreGbk(CharSequence str, int len, CharSequence suffix) {
return subPreGbk(str, len, true) + suffix;
}
/**
* 截取部分字符串这里一个汉字的长度认为是2<br>
* 可以自定义halfUp如len为10如果截取后最后一个字符是半个字符{@code true}表示保留则长度是11否则长度9
*
* @param str 字符串
* @param len bytes切割到的位置包含
* @param halfUp 遇到截取一半的GBK字符是否保留。
* @return 切割后的字符串
* @since 5.7.17
*/
public static String subPreGbk(CharSequence str, int len, boolean halfUp) {
if (isEmpty(str)) {
return str(str);
}
byte[] b;
int counterOfDoubleByte = 0;
b = str.toString().getBytes(CharsetUtil.CHARSET_GBK);
final byte[] b = bytes(str, CharsetUtil.CHARSET_GBK);
if (b.length <= len) {
return str.toString();
}
@@ -2002,9 +2015,13 @@ public class CharSequenceUtil {
}
if (counterOfDoubleByte % 2 != 0) {
len += 1;
if(halfUp){
len += 1;
}else{
len -= 1;
}
}
return new String(b, 0, len, CharsetUtil.CHARSET_GBK) + suffix;
return new String(b, 0, len, CharsetUtil.CHARSET_GBK);
}
/**
@@ -2534,7 +2551,12 @@ public class CharSequenceUtil {
}
/**
* 比较两个字符串是否相等
* 比较两个字符串是否相等,规则如下
* <ul>
* <li>str1和str2都为{@code null}</li>
* <li>忽略大小写使用{@link String#equalsIgnoreCase(String)}判断相等</li>
* <li>不忽略大小写使用{@link String#contentEquals(CharSequence)}判断相等</li>
* </ul>
*
* @param str1 要比较的字符串1
* @param str2 要比较的字符串2

View File

@@ -470,7 +470,6 @@ public class StrBuilder implements CharSequence, Appendable, Serializable {
/**
* 生成字符串
*/
@SuppressWarnings("NullableProblems")
@Override
public String toString() {
return toString(false);

View File

@@ -30,12 +30,15 @@ public class AsyncUtil {
/**
* 等待任意一个任务执行完毕,包裹了异常
*
* @param <T> 任务返回值类型
* @param tasks 并行任务
* @return 执行结束的任务返回值
* @throws UndeclaredThrowableException 未受检异常
*/
public static void waitAny(CompletableFuture<?>... tasks) {
@SuppressWarnings("unchecked")
public static <T> T waitAny(CompletableFuture<?>... tasks) {
try {
CompletableFuture.anyOf(tasks).get();
return (T) CompletableFuture.anyOf(tasks).get();
} catch (InterruptedException | ExecutionException e) {
throw new ThreadException(e);
}
@@ -44,8 +47,8 @@ public class AsyncUtil {
/**
* 获取异步任务结果,包裹了异常
*
* @param task 异步任务
* @param <T> 任务返回值类型
* @param task 异步任务
* @return 任务返回值
* @throws RuntimeException 未受检异常
*/

View File

@@ -2,6 +2,9 @@ package cn.hutool.core.thread;
import cn.hutool.core.date.TimeInterval;
import java.io.Closeable;
import java.io.IOException;
/**
* 高并发测试工具类
*
@@ -12,11 +15,14 @@ import cn.hutool.core.date.TimeInterval;
* ct.test(() -&gt; {
* // 需要并发测试的业务代码
* });
*
* Console.log(ct.getInterval());
* ct.close();
* </pre>
*
* @author kwer
*/
public class ConcurrencyTester {
public class ConcurrencyTester implements Closeable {
private final SyncFinisher sf;
private final TimeInterval timeInterval;
private long interval;
@@ -31,7 +37,8 @@ public class ConcurrencyTester {
}
/**
* 执行测试
* 执行测试<br>
* 执行测试后不会关闭线程池,可以调用{@link #close()}释放线程池
*
* @param runnable 要测试的内容
* @return this
@@ -40,9 +47,9 @@ public class ConcurrencyTester {
this.sf.clearWorker();
timeInterval.start();
this.sf//
.addRepeatWorker(runnable)//
.setBeginAtSameTime(true)// 同时开始
this.sf
.addRepeatWorker(runnable)
.setBeginAtSameTime(true)
.start();
this.interval = timeInterval.interval();
@@ -74,4 +81,9 @@ public class ConcurrencyTester {
public long getInterval() {
return this.interval;
}
@Override
public void close() throws IOException {
this.sf.close();
}
}

View File

@@ -2,6 +2,8 @@ package cn.hutool.core.thread;
import cn.hutool.core.exceptions.UtilException;
import java.io.Closeable;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@@ -25,7 +27,7 @@ import java.util.concurrent.ExecutorService;
* @author Looly
* @since 4.1.15
*/
public class SyncFinisher {
public class SyncFinisher implements Closeable {
private final Set<Worker> workers;
private final int threadSize;
@@ -173,6 +175,11 @@ public class SyncFinisher {
return endLatch.getCount();
}
@Override
public void close() throws IOException {
stop();
}
/**
* 工作者,为一个线程
*

View File

@@ -543,6 +543,6 @@ public class HashUtil {
* @since 5.2.5
*/
public static long[] cityHash128(byte[] data, Number128 seed) {
return CityHash.hash128(data).getLongArray();
return CityHash.hash128(data, seed).getLongArray();
}
}
}

View File

@@ -154,7 +154,7 @@ public class IdcardUtil {
* @return 是否有效
*/
public static boolean isValidCard(String idCard) {
if(StrUtil.isBlank(idCard)){
if (StrUtil.isBlank(idCard)) {
return false;
}
@@ -201,6 +201,21 @@ public class IdcardUtil {
* <li>余数只可能有0 1 2 3 4 5 6 7 8 9 10这11个数字。其分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3 2</li>
* <li>通过上面得知如果余数是2就会在身份证的第18位数字上出现罗马数字的。如果余数是10身份证的最后一位号码就是2</li>
* </ol>
* <ol>
* <li>香港人在大陆的身份证【810000】开头同样可以直接获取到 性别、出生日期</li>
* <li>81000019980902013X: 文绎循 男 1998-09-02</li>
* <li>810000201011210153: 辛烨 男 2010-11-21</li>
* </ol>
* <ol>
* <li>澳门人在大陆的身份证【820000】开头同样可以直接获取到 性别、出生日期</li>
* <li>820000200009100032: 黄敬杰 男 2000-09-10</li>
* </ol>
* <ol>
* <li>台湾人在大陆的身份证【830000】开头同样可以直接获取到 性别、出生日期</li>
* <li>830000200209060065: 王宜妃 女 2002-09-06</li>
* <li>830000194609150010: 苏建文 男 1946-09-14</li>
* <li>83000019810715006X: 刁婉琇 女 1981-07-15</li>
* </ol>
*
* @param idcard 待验证的身份证
* @return 是否有效的18位身份证忽略x的大小写
@@ -237,7 +252,7 @@ public class IdcardUtil {
* <li>通过上面得知如果余数是2就会在身份证的第18位数字上出现罗马数字的。如果余数是10身份证的最后一位号码就是2</li>
* </ol>
*
* @param idcard 待验证的身份证
* @param idcard 待验证的身份证
* @param ignoreCase 是否忽略大小写。{@code true}则忽略X大小写否则严格匹配大写。
* @return 是否有效的18位身份证
* @since 5.5.7
@@ -558,7 +573,7 @@ public class IdcardUtil {
*/
public static String getProvinceByIdCard(String idcard) {
final String code = getProvinceCodeByIdCard(idcard);
if(StrUtil.isNotBlank(code)){
if (StrUtil.isNotBlank(code)) {
return CITY_CODES.get(code);
}
return null;
@@ -599,7 +614,7 @@ public class IdcardUtil {
* @return {@link Idcard}
* @since 5.4.3
*/
public static Idcard getIdcardInfo(String idcard){
public static Idcard getIdcardInfo(String idcard) {
return new Idcard(idcard);
}
@@ -743,6 +758,7 @@ public class IdcardUtil {
/**
* 获取年龄
*
* @return 年龄
*/
public int getAge() {

View File

@@ -84,7 +84,7 @@ public class ReflectUtil {
@SuppressWarnings("unchecked")
public static <T> Constructor<T>[] getConstructors(Class<T> beanClass) throws SecurityException {
Assert.notNull(beanClass);
return (Constructor<T>[]) CONSTRUCTORS_CACHE.get(beanClass, ()->getConstructorsDirectly(beanClass));
return (Constructor<T>[]) CONSTRUCTORS_CACHE.get(beanClass, () -> getConstructorsDirectly(beanClass));
}
/**
@@ -173,7 +173,7 @@ public class ReflectUtil {
*/
public static Field[] getFields(Class<?> beanClass) throws SecurityException {
Assert.notNull(beanClass);
return FIELDS_CACHE.get(beanClass, ()->getFieldsDirectly(beanClass, true));
return FIELDS_CACHE.get(beanClass, () -> getFieldsDirectly(beanClass, true));
}
@@ -498,11 +498,9 @@ public class ReflectUtil {
}
/**
* 查找指定方法 如果找不到对应的方法则返回{@code null}
*
* <p>
* 此方法为精准获取方法名,即方法名和参数数量和类型必须一致,否则返回{@code null}。
* </p>
* 查找指定方法 如果找不到对应的方法则返回{@code null}<br>
* 此方法为精准获取方法名,即方法名和参数数量和类型必须一致,否则返回{@code null}。<br>
* 如果查找的方法有多个同参数类型重载,查找第一个找到的方法
*
* @param clazz 类,如果为{@code null}返回{@code null}
* @param ignoreCase 是否忽略大小写
@@ -520,10 +518,11 @@ public class ReflectUtil {
final Method[] methods = getMethods(clazz);
if (ArrayUtil.isNotEmpty(methods)) {
for (Method method : methods) {
if (StrUtil.equals(methodName, method.getName(), ignoreCase)) {
if (ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes)) {
return method;
}
if (StrUtil.equals(methodName, method.getName(), ignoreCase)
&& ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes)
//排除桥接方法pr#1965@Github
&& false == method.isBridge()) {
return method;
}
}
}
@@ -586,7 +585,9 @@ public class ReflectUtil {
final Method[] methods = getMethods(clazz);
if (ArrayUtil.isNotEmpty(methods)) {
for (Method method : methods) {
if (StrUtil.equals(methodName, method.getName(), ignoreCase)) {
if (StrUtil.equals(methodName, method.getName(), ignoreCase)
// 排除桥接方法
&& false == method.isBridge()) {
return method;
}
}
@@ -635,7 +636,7 @@ public class ReflectUtil {
*/
public static Method[] getMethods(Class<?> beanClass) throws SecurityException {
Assert.notNull(beanClass);
return METHODS_CACHE.get(beanClass, ()-> getMethodsDirectly(beanClass, true));
return METHODS_CACHE.get(beanClass, () -> getMethodsDirectly(beanClass, true));
}
/**

View File

@@ -0,0 +1,50 @@
package cn.hutool.core.bean.copier;
import lombok.Data;
import org.junit.Assert;
import org.junit.Test;
public class BeanCopierTest {
/**
* 测试在非覆盖模式下,目标对象有值则不覆盖
*/
@Test
public void beanToBeanNotOverrideTest() {
final A a = new A();
a.setValue("123");
final B b = new B();
b.setValue("abc");
final BeanCopier<B> copier = BeanCopier.create(a, b, CopyOptions.create().setOverride(false));
copier.copy();
Assert.assertEquals("abc", b.getValue());
}
/**
* 测试在覆盖模式下,目标对象值被覆盖
*/
@Test
public void beanToBeanOverrideTest() {
final A a = new A();
a.setValue("123");
final B b = new B();
b.setValue("abc");
final BeanCopier<B> copier = BeanCopier.create(a, b, CopyOptions.create());
copier.copy();
Assert.assertEquals("123", b.getValue());
}
@Data
private static class A {
private String value;
}
@Data
private static class B {
private String value;
}
}

View File

@@ -39,6 +39,23 @@ public class CollUtilTest {
Assert.assertFalse(CollUtil.contains(list, s -> s.startsWith("d")));
}
@Test
public void testRemoveWithAddIf() {
ArrayList<Integer> list = CollUtil.newArrayList(1, 2, 3);
ArrayList<Integer> exceptRemovedList = CollUtil.newArrayList(2, 3);
ArrayList<Integer> exceptResultList = CollUtil.newArrayList(1);
List<Integer> resultList = CollUtil.removeWithAddIf(list, ele -> 1 == ele);
Assert.assertEquals(list, exceptRemovedList);
Assert.assertEquals(resultList, exceptResultList);
list = CollUtil.newArrayList(1, 2, 3);
resultList = new ArrayList<>();
CollUtil.removeWithAddIf(list, resultList, ele -> 1 == ele);
Assert.assertEquals(list, exceptRemovedList);
Assert.assertEquals(resultList, exceptResultList);
}
@Test
public void testPadLeft() {
List<String> srcList = CollUtil.newArrayList();
@@ -285,6 +302,27 @@ public class CollUtilTest {
Assert.assertEquals(CollUtil.newArrayList("b", "c"), filtered);
}
@Test
public void filterRemoveTest() {
ArrayList<String> list = CollUtil.newArrayList("a", "b", "c");
List<String> removed = new ArrayList<>();
ArrayList<String> filtered = CollUtil.filter(list, t -> {
if("a".equals(t)){
removed.add(t);
return false;
}
return true;
});
Assert.assertEquals(1, removed.size());
Assert.assertEquals("a", removed.get(0));
// 原地过滤
Assert.assertSame(list, filtered);
Assert.assertEquals(CollUtil.newArrayList("b", "c"), filtered);
}
@Test
public void removeNullTest() {
ArrayList<String> list = CollUtil.newArrayList("a", "b", "c", null, "", " ");

View File

@@ -1,6 +1,7 @@
package cn.hutool.core.math;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
public class CalculatorTest {
@@ -28,4 +29,12 @@ public class CalculatorTest {
final double conversion = Calculator.conversion("(88*66/23)%26+45%9");
Assert.assertEquals((88D * 66 / 23) % 26, conversion, 2);
}
@Test
@Ignore
public void conversationTest5(){
// https://github.com/dromara/hutool/issues/1984
final double conversion = Calculator.conversion("((1/1) / (1/1) -1) * 100");
Assert.assertEquals((88D * 66 / 23) % 26, conversion, 2);
}
}

View File

@@ -1,5 +1,6 @@
package cn.hutool.core.text;
import cn.hutool.core.util.CharsetUtil;
import org.junit.Assert;
import org.junit.Test;
@@ -65,4 +66,16 @@ public class CharSequenceUtilTest {
index = CharSequenceUtil.indexOf("abc123", 'b', 0, 3);
Assert.assertEquals(1, index);
}
@Test
public void subPreGbkTest(){
// https://gitee.com/dromara/hutool/issues/I4JO2E
String s = "华硕K42Intel酷睿i31代2G以下独立显卡不含机械硬盘固态硬盘120GB-192GB4GB-6GB";
String v = CharSequenceUtil.subPreGbk(s, 40, false);
Assert.assertEquals(39, v.getBytes(CharsetUtil.CHARSET_GBK).length);
v = CharSequenceUtil.subPreGbk(s, 40, true);
Assert.assertEquals(41, v.getBytes(CharsetUtil.CHARSET_GBK).length);
}
}

View File

@@ -182,6 +182,12 @@ public class ArrayUtilTest {
Assert.assertEquals(9, range[9]);
}
@Test(expected = NegativeArraySizeException.class)
public void rangeMinTest() {
//noinspection ResultOfMethodCallIgnored
ArrayUtil.range(0, Integer.MIN_VALUE);
}
@Test
public void maxTest() {
int max = ArrayUtil.max(1, 2, 13, 4, 5);

View File

@@ -103,6 +103,18 @@ public class IdcardUtilTest {
Assert.assertTrue(isValidCard18);
isValidCard18 = IdcardUtil.isValidCard18("33010219200403064X");
Assert.assertTrue(isValidCard18);
// 香港人在大陆身份证
isValidCard18 = IdcardUtil.isValidCard18("81000019980902013X");
Assert.assertTrue(isValidCard18);
// 澳门人在大陆身份证
isValidCard18 = IdcardUtil.isValidCard18("820000200009100032");
Assert.assertTrue(isValidCard18);
// 台湾人在大陆身份证
isValidCard18 = IdcardUtil.isValidCard18("830000200209060065");
Assert.assertTrue(isValidCard18);
}
@Test