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

# Conflicts:
#	hutool-core/src/test/java/cn/hutool/core/stream/AbstractEnhancedWrappedStreamTest.java
This commit is contained in:
VampireAchao 2022-10-17 16:49:39 +08:00
commit b47b531b4c
62 changed files with 2846 additions and 404 deletions

View File

@ -3,15 +3,14 @@
-------------------------------------------------------------------------------------------------------------
# 6.0.0.M1 (2022-08-27)
# 6.0.0.M1 (2022-10-09)
### 计划实现
* 【poi 】 PDF相关基于PdfBox
* 【poi 】 HTML、DOCX转换相关
* 【poi 】 Markdown相关如HTML转换等基于commonmark-java
* 【db 】 增加DDL封装
* 【json 】 实现自定义的类型转换,不影响全局的转换器
* 【core 】 Functional接口重新定义
* 【poi 】 CellUtil.getCellIfMergedRegion考虑添加缓存支持增加最大和最小范围判断减少遍历
### ❌不兼容特性

View File

@ -50,6 +50,10 @@ public class MapToMapCopier extends AbsCopier<Map, Map> {
return;
}
sValue = entry.getValue();
// 忽略空值
if (copyOptions.ignoreNullValue && sValue == null) {
return;
}
final Object targetValue = target.get(sKey);
// 非覆盖模式下如果目标值存在则跳过

View File

@ -1,5 +1,6 @@
package cn.hutool.core.cache.impl;
import cn.hutool.core.lang.mutable.Mutable;
import cn.hutool.core.map.FixedLinkedHashMap;
import java.util.Iterator;
@ -42,7 +43,13 @@ public class LRUCache<K, V> extends ReentrantCache<K, V> {
this.timeout = timeout;
//链表key按照访问顺序排序调用get方法后会将这次访问的元素移至头部
cacheMap = new FixedLinkedHashMap<>(capacity);
final FixedLinkedHashMap<Mutable<K>, CacheObj<K, V>> fixedLinkedHashMap = new FixedLinkedHashMap<>(capacity);
fixedLinkedHashMap.setRemoveListener(entry -> {
if(null != listener){
listener.onRemove(entry.getKey().get(), entry.getValue().getValue());
}
});
cacheMap = fixedLinkedHashMap;
}
// ---------------------------------------------------------------- prune

View File

@ -24,6 +24,13 @@ import java.util.Set;
*/
public class CollectionOperation<E> {
/**
* 创建运算对象
*
* @param colls 集合列表
* @param <E> 元素类型
* @return CollectionOperation
*/
@SafeVarargs
public static <E> CollectionOperation<E> of(final Collection<E>... colls) {
return new CollectionOperation<>(colls);
@ -160,14 +167,14 @@ public class CollectionOperation<E> {
// 任意容器为空, 则返回空集
for (final Collection<E> coll : colls) {
if(CollUtil.isEmpty(coll)){
if (CollUtil.isEmpty(coll)) {
return SetUtil.zeroLinked();
}
}
final Set<E> result = SetUtil.of(true, colls[0]);
for (int i = 1; i < colls.length; i++) {
if(CollUtil.isNotEmpty(colls[i])){
if (CollUtil.isNotEmpty(colls[i])) {
result.retainAll(colls[i]);
}
}
@ -215,7 +222,7 @@ public class CollectionOperation<E> {
*/
public List<E> subtract() {
final Collection<E>[] colls = this.colls;
if(ArrayUtil.isEmpty(colls)){
if (ArrayUtil.isEmpty(colls)) {
return ListUtil.zero();
}
final List<E> result = ListUtil.of(colls[0]);
@ -226,6 +233,7 @@ public class CollectionOperation<E> {
}
// region private methods
/**
* 两个集合的并集<br>
* 针对一个集合中存在多个相同元素的情况计算两个集合中此元素的个数保留最多的个数<br>
@ -315,12 +323,12 @@ public class CollectionOperation<E> {
* @return 差集的集合返回 {@link ArrayList}
*/
private static <T> Collection<T> _disjunction(final Collection<T> coll1, final Collection<T> coll2) {
if(CollUtil.isEmpty(coll1)){
if(CollUtil.isEmpty(coll2)){
if (CollUtil.isEmpty(coll1)) {
if (CollUtil.isEmpty(coll2)) {
return ListUtil.zero();
}
return coll2;
} else if(CollUtil.isEmpty(coll2)){
} else if (CollUtil.isEmpty(coll2)) {
return coll1;
}

View File

@ -136,7 +136,7 @@ public class CompareUtil {
* <li>{@code Comparator.nullsFirst(CompareUtil.reverse())}</li>
* </ul>
*
* @param <E> 排序节点类型
* @param <E> 排序节点类型
* @param comparator 排序器
* @return 默认排序器
* @since 6.0.0
@ -326,4 +326,124 @@ public class CompareUtil {
final IndexedComparator<U> indexedComparator = new IndexedComparator<>(atEndIfMiss, objs);
return (o1, o2) -> indexedComparator.compare(keyExtractor.apply(o1), keyExtractor.apply(o2));
}
/**
* 取两个值中的最小值大小相同返回第一个值
*
* @param <T> 值类型
* @param t1 第一个值
* @param t2 第二个值
* @return 最小值
* @since 6.0.0
*/
public static <T extends Comparable<? super T>> T min(final T t1, final T t2) {
return compare(t1, t2) <= 0 ? t1 : t2;
}
/**
* 取两个值中的最大值大小相同返回第一个值
*
* @param <T> 值类型
* @param t1 第一个值
* @param t2 第二个值
* @return 最大值
* @since 6.0.0
*/
public static <T extends Comparable<? super T>> T max(final T t1, final T t2) {
return compare(t1, t2) >= 0 ? t1 : t2;
}
/**
* {@code null}安全的检查两个对象是否相同通过调用{@code compare(c1, c2) == 0}完成
*
* @param <T> 被比较对象类型
* @param c1 对象1可以为{@code null}
* @param c2 对象2可以为{@code null}
* @return 是否相等
* @see java.util.Comparator#compare(Object, Object)
*/
public static <T extends Comparable<? super T>> boolean equals(final T c1, final T c2) {
return compare(c1, c2) == 0;
}
/**
* c1是否大于c2通过调用{@code compare(c1, c2) > 0}完成
*
* @param <T> 被比较对象类型
* @param c1 对象1可以为{@code null}
* @param c2 对象2可以为{@code null}
* @return c1是否大于c2
* @see java.util.Comparator#compare(Object, Object)
*/
public static <T extends Comparable<? super T>> boolean gt(final T c1, final T c2) {
return compare(c1, c2) > 0;
}
/**
* c1是否大于或等于c2通过调用{@code compare(c1, c2) >= 0}完成
*
* @param <T> 被比较对象类型
* @param c1 对象1可以为{@code null}
* @param c2 对象2可以为{@code null}
* @return c1是否大于或等于c2
* @see java.util.Comparator#compare(Object, Object)
*/
public static <T extends Comparable<? super T>> boolean ge(final T c1, final T c2) {
return compare(c1, c2) >= 0;
}
/**
* c1是否大小于c2通过调用{@code compare(c1, c2) < 0}完成
*
* @param <T> 被比较对象类型
* @param c1 对象1可以为{@code null}
* @param c2 对象2可以为{@code null}
* @return c1是否小于c2
* @see java.util.Comparator#compare(Object, Object)
*/
public static <T extends Comparable<? super T>> boolean lt(final T c1, final T c2) {
return compare(c1, c2) < 0;
}
/**
* c1是否小于或等于c2通过调用{@code compare(c1, c2) <= 0}完成
*
* @param <T> 被比较对象类型
* @param c1 对象1可以为{@code null}
* @param c2 对象2可以为{@code null}
* @return c1是否小于或等于c2
* @see java.util.Comparator#compare(Object, Object)
*/
public static <T extends Comparable<? super T>> boolean le(final T c1, final T c2) {
return compare(c1, c2) <= 0;
}
/**
* 给定的{@code value}是否在{@code c1}{@code c2}的范围内<br>
* {@code min(c1,c2) <= value <= max(c1,c2)}
*
* @param <T> 被比较对象类型
* @param value 检查的对象可以为{@code null}
* @param c1 对象1可以为{@code null}
* @param c2 对象2可以为{@code null}
* @return 给定的{@code value}是否在{@code c1}{@code c2}的范围内
*/
public static <T extends Comparable<? super T>> boolean isIn(final T value, final T c1, final T c2) {
return ge(value, min(c1, c2)) && le(value, max(c1, c2));
}
/**
* 给定的{@code value}是否在{@code c1}{@code c2}的范围内但是不包括边界<br>
* {@code min(c1,c2) < value < max(c1,c2)}
*
* @param <T> 被比较对象类型
* @param value 检查的对象可以为{@code null}
* @param c1 对象1可以为{@code null}
* @param c2 对象2可以为{@code null}
* @return c1是否小于或等于c2
* @see java.util.Comparator#compare(Object, Object)
*/
public static <T extends Comparable<? super T>> boolean isInExclusive(final T value, final T c1, final T c2) {
return gt(value, min(c1, c2)) && lt(value, max(c1, c2));
}
}

View File

@ -1,6 +1,6 @@
package cn.hutool.core.date;
import cn.hutool.core.lang.Range;
import cn.hutool.core.lang.range.Range;
import java.util.Date;

View File

@ -126,7 +126,7 @@ public class DateUtil extends CalendarUtil {
/**
* Long类型时间转为{@link DateTime}<br>
* 只支持毫秒级别时间戳如果需要秒级别时间戳请自行×1000
* 只支持毫秒级别时间戳如果需要秒级别时间戳请自行×1000L
*
* @param date Long类型DateUnix时间戳
* @return 时间对象

View File

@ -179,6 +179,14 @@ public class FileNameUtil {
if (0 == len) {
return fileName;
}
//issue#2642多级扩展名的主文件名
for (final CharSequence specialSuffix : SPECIAL_SUFFIX) {
if(StrUtil.endWith(fileName, "." + specialSuffix)){
return StrUtil.subPre(fileName, len - specialSuffix.length() - 1);
}
}
if (CharUtil.isFileSeparator(fileName.charAt(len - 1))) {
len--;
}

View File

@ -0,0 +1,188 @@
package cn.hutool.core.lang.range;
import java.util.Objects;
import java.util.function.Predicate;
/**
* <p>边界对象描述具有特定上界或下界的单侧无界的区间
*
* <p>边界的类型</p>
* <p>边界根据其{@link #getType()}所获得的类型可用于描述基于边界值<em>t</em>的不等式:
* <ul>
* <li>{@link #noneLowerBound()}{@code {x | x > -}}</li>
* <li>{@link #noneUpperBound()}{@code {x | x < +}}</li>
* <li>{@link #greaterThan}{@code {x | x > t}}</li>
* <li>{@link #atLeast}{@code {x | x >= t}}</li>
* <li>{@link #lessThan}{@code {x | x < t}}</li>
* <li>{@link #atMost}{@code {x | x <= t}}</li>
* </ul>
* 当作为{@link Predicate}使用时可用于判断入参对象是否能满足当前实例所对应的不等式
*
* <p>边界的比较</p>
* <p>边界对象本身实现了{@link Comparable}接口
* 当使用{@link Comparable#compareTo}比较两个边界对象时
* 返回的比较值表示两个边界对象对应的点在实数轴上从左到右的先后顺序<br>
* 比如
* 若令当前边界点为<em>t1</em>另一边界点为<em>t2</em>则有
* <ul>
* <li>-1<em>t1</em><em>t2</em>的左侧</li>
* <li>0<em>t1</em><em>t2</em>所表示的点彼此重合</li>
* <li>-1<em>t1</em><em>t2</em>的右侧</li>
* </ul>
*
* @param <T> 边界值类型
* @author huangchengxing
* @see BoundType
* @see BoundedRange
* @since 6.0.0
*/
public interface Bound<T extends Comparable<? super T>> extends Predicate<T>, Comparable<Bound<T>> {
/**
* 无穷小的描述
*/
String INFINITE_MIN = "-\u221e";
/**
* 无穷大的藐视
*/
String INFINITE_MAX = "+\u221e";
// region --------------- static methods
/**
* {@code {x | x > -}}
*
* @param <T> 边界值类型
* @return 区间
*/
@SuppressWarnings("unchecked")
static <T extends Comparable<? super T>> Bound<T> noneLowerBound() {
return NoneLowerBound.INSTANCE;
}
/**
* {@code {x | x < +}}
*
* @param <T> 边界值类型
* @return 区间
*/
@SuppressWarnings("unchecked")
static <T extends Comparable<? super T>> Bound<T> noneUpperBound() {
return NoneUpperBound.INSTANCE;
}
/**
* {@code {x | x > min}}
*
* @param min 最小值
* @param <T> 边界值类型
* @return 区间
*/
static <T extends Comparable<? super T>> Bound<T> greaterThan(final T min) {
return new FiniteBound<>(Objects.requireNonNull(min), BoundType.OPEN_LOWER_BOUND);
}
/**
* {@code {x | x >= min}}
*
* @param min 最小值
* @param <T> 边界值类型
* @return 区间
*/
static <T extends Comparable<? super T>> Bound<T> atLeast(final T min) {
return new FiniteBound<>(Objects.requireNonNull(min), BoundType.CLOSE_LOWER_BOUND);
}
/**
* {@code {x | x < max}}
*
* @param max 最大值
* @param <T> 边界值类型
* @return 区间
*/
static <T extends Comparable<? super T>> Bound<T> lessThan(final T max) {
return new FiniteBound<>(Objects.requireNonNull(max), BoundType.OPEN_UPPER_BOUND);
}
/**
* {@code {x | x <= max}}
*
* @param max 最大值
* @param <T> 边界值类型
* @return 区间
*/
static <T extends Comparable<? super T>> Bound<T> atMost(final T max) {
return new FiniteBound<>(Objects.requireNonNull(max), BoundType.CLOSE_UPPER_BOUND);
}
// endregion --------------- static methods
/**
* 获取边界值
*
* @return 边界值
*/
T getValue();
/**
* 获取边界类型
*
* @return 边界类型
*/
BoundType getType();
/**
* 检验指定值是否在当前边界表示的范围内
*
* @param t 要检验的值不允许为{@code null}
* @return 是否
*/
@SuppressWarnings("AbstractMethodOverridesAbstractMethod")
@Override
boolean test(T t);
/**
* <p>比较另一边界与当前边界在坐标轴上位置的先后顺序<br>
* 若令当前边界为<em>t1</em>另一边界为<em>t2</em>则有
* <ul>
* <li>-1<em>t1</em><em>t2</em>的左侧</li>
* <li>0<em>t1</em><em>t2</em>的重合</li>
* <li>-1<em>t1</em><em>t2</em>的右侧</li>
* </ul>
*
* @param bound 边界
* @return 位置
*/
@SuppressWarnings("AbstractMethodOverridesAbstractMethod")
@Override
int compareTo(final Bound<T> bound);
/**
* 获取{@code "[value"}{@code "(value"}格式的字符串
*
* @return 字符串
*/
String descBound();
/**
* 对当前边界取反
*
* @return 取反后的边界
*/
@Override
Bound<T> negate();
/**
* 将当前实例转为一个区间
*
* @return 区间
*/
BoundedRange<T> toRange();
/**
* 获得当前实例对应的{@code {x| x >= xxx}}格式的不等式字符串
*
* @return 字符串
*/
@Override
String toString();
}

View File

@ -0,0 +1,143 @@
package cn.hutool.core.lang.range;
/**
* 边界类型枚举
*
* @author huangchengxing
* @since 6.0.0
*/
public enum BoundType {
/**
* 表示一个左闭区间等同于{@code {x | x >= a}}
*/
CLOSE_LOWER_BOUND("[", ">=", -2),
/**
* 表示一个左开区间等同于{@code {x | x > a}}
*/
OPEN_LOWER_BOUND("(", ">", -1),
/**
* 表示一个右开区间等同于{@code {x | x < a}}
*/
OPEN_UPPER_BOUND(")", "<", 1),
/**
* 表示一个右闭区间等同于{@code {x | x <= a}}
*/
CLOSE_UPPER_BOUND("]", "<=", 2);
/**
* 符号
*/
private final String symbol;
/**
* 运算符
*/
private final String operator;
/**
* 是否为开区间
*/
private final int code;
/**
* 构造
* @param symbol 符号`[``(`
* @param operator 运算符`<`
* @param code 是否为开区间
*/
BoundType(final String symbol, final String operator, final int code) {
this.symbol = symbol;
this.operator = operator;
this.code = code;
}
/**
* 获取符号
*
* @return 符号
*/
public String getSymbol() {
return symbol;
}
/**
* 获取code
*
* @return code
*/
public int getCode() {
return code;
}
/**
* 获取运算符
*
* @return 运算符
*/
public String getOperator() {
return operator;
}
/**
* 该边界类型是否与当前边界错位即一个的左边界一个是右边界
*
* @param boundType 另一边界类型
* @return 是否
*/
public boolean isDislocated(final BoundType boundType) {
return code * boundType.code < 0;
}
/**
* 是下界
*
* @return 是否
*/
public boolean isLowerBound() {
return code < 0;
}
/**
* 是上界
*
* @return 是否
*/
public boolean isUpperBound() {
return code > 0;
}
/**
* 是闭区间
*
* @return 是否
*/
public boolean isClose() {
return (code & 1) == 0;
}
/**
* 是开区间
*
* @return 是否
*/
public boolean isOpen() {
return (code & 1) == 1;
}
/**
* 对边界类型取反
*
* @return 取反后的边界类型
*/
public BoundType negate() {
if (isLowerBound()) {
return isOpen() ? CLOSE_UPPER_BOUND : OPEN_UPPER_BOUND;
}
return isOpen() ? CLOSE_LOWER_BOUND : OPEN_LOWER_BOUND;
}
}

View File

@ -0,0 +1,514 @@
package cn.hutool.core.lang.range;
import cn.hutool.core.lang.Assert;
import java.util.Objects;
import java.util.function.Predicate;
/**
* <p>参考<em>Guava</em><em>Range</em>实现用于描述作为上下界的两个{@link Bound}实例围成的一段区间<br>
* 作为{@link Predicate}使用时可检验指定值是否在区间中即指定值是否同时满足上下界的{@link Bound#test}方法
*
* <p>区间的类型支持通过工厂方法创建下述几种类型的区间</p>
* <table summary="">
* <tr><th>区间 <th>数学定义 <th>工厂方法
* <tr><td>{@code (a, b)} <td>{@code {x | a < x < b}} <td>{@link #open}
* <tr><td>{@code [a, b]} <td>{@code {x | a <= x <= b}}<td>{@link #close}
* <tr><td>{@code (a, b]} <td>{@code {x | a < x <= b}} <td>{@link #openClose}
* <tr><td>{@code [a, b)} <td>{@code {x | a <= x < b}} <td>{@link #closeOpen}
* <tr><td>{@code (a, +)} <td>{@code {x | x > a}} <td>{@link #greaterThan}
* <tr><td>{@code [a, +)} <td>{@code {x | x >= a}} <td>{@link #atLeast}
* <tr><td>{@code (-, b)} <td>{@code {x | x < b}} <td>{@link #lessThan}
* <tr><td>{@code (-, b]} <td>{@code {x | x <= b}} <td>{@link #atMost}
* <tr><td>{@code (-, +)}<td>{@code {x}} <td>{@link #all}
* </table>
*
* <p>空区间</p>
* <p>根据数学定义当区间中无任何实数时认为该区间代表的集合为空集
* 用户可通过{@link #isEmpty}确认当前实例是否为空区间<br>
* 若实例上界<em>a</em>下界为<em>b</em>则当实例满足下述任意条件时认为其为一个空区间
* <ul>
* <li>{@code a > b}</li>
* <li>{@code [a, b)}{@code a == b}</li>
* <li>{@code (a, b)}{@code a == b}</li>
* <li>{@code (a, b]}{@code a == b}</li>
* </ul>
* 当通过工厂方法创建区间时若区间为空则会抛出{@link IllegalArgumentException},
* 但是通过交并操作仍有可能创建出满足上述描述的空区间
* 此时若空区间参与操作可能得到意外的结果
* 因此对通过非工厂方法得到的区间在操作前有必要通过{@link #isEmpty}进行检验
*
* @param <T> 边界值类型
* @author huangchengxing
* @see Bound
* @since 6.0.0
*/
public class BoundedRange<T extends Comparable<? super T>> implements Predicate<T> {
/**
* 双向无界的区间
*/
@SuppressWarnings({"rawtypes", "unchecked"})
private static final BoundedRange ALL = new BoundedRange(Bound.noneLowerBound(), Bound.noneUpperBound());
/**
* 构建一个上下界皆无限大的区间{@code {x | - < x < +}}
*
* @param <T> 比较对象类型
* @return 区间
*/
@SuppressWarnings("unchecked")
public static <T extends Comparable<? super T>> BoundedRange<T> all() {
return ALL;
}
/**
* 构建一个闭区间{@code {x | lowerBound <= x <= upperBound}}
*
* @param lowerBound 下界不能为空
* @param upperBound 上界不能为空
* @param <T> 边界值类型
* @return 区间
* @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出
* @throws NullPointerException 上界或下界为{@code null}时抛出
*/
public static <T extends Comparable<? super T>> BoundedRange<T> close(final T lowerBound, final T upperBound) {
Objects.requireNonNull(lowerBound);
Objects.requireNonNull(upperBound);
return checkEmpty(
new BoundedRange<>(
Bound.atLeast(lowerBound), Bound.atMost(upperBound)
)
);
}
/**
* 构建一个开区间{@code {x | lowerBound < x < upperBound}}
*
* @param lowerBound 下界不能为空
* @param upperBound 上界不能为空
* @param <T> 边界值类型
* @return 区间
* @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出
* @throws NullPointerException 上界或下界为{@code null}时抛出
*/
public static <T extends Comparable<? super T>> BoundedRange<T> open(final T lowerBound, final T upperBound) {
Objects.requireNonNull(lowerBound);
Objects.requireNonNull(upperBound);
return checkEmpty(
new BoundedRange<>(Bound.greaterThan(lowerBound), Bound.lessThan(upperBound))
);
}
/**
* 构建一个左闭右开区间{@code {x | lowerBound <= x < upperBound}}
*
* @param lowerBound 下界不能为空
* @param upperBound 上界不能为空
* @param <T> 边界值类型
* @return 区间
* @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出
* @throws NullPointerException 上界或下界为{@code null}时抛出
*/
public static <T extends Comparable<? super T>> BoundedRange<T> closeOpen(final T lowerBound, final T upperBound) {
Objects.requireNonNull(lowerBound);
Objects.requireNonNull(upperBound);
return checkEmpty(
new BoundedRange<>(
Bound.atLeast(lowerBound),
Bound.lessThan(upperBound)
)
);
}
/**
* 构建一个左闭右开区间{@code {x | lowerBound < x <= upperBound}}
*
* @param lowerBound 下界不能为空
* @param upperBound 上界不能为空
* @param <T> 边界值类型
* @return 区间
* @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出
* @throws NullPointerException 上界或下界为{@code null}时抛出
*/
public static <T extends Comparable<? super T>> BoundedRange<T> openClose(final T lowerBound, final T upperBound) {
Objects.requireNonNull(lowerBound);
Objects.requireNonNull(upperBound);
return checkEmpty(
new BoundedRange<>(
Bound.greaterThan(lowerBound),
Bound.atMost(upperBound)
)
);
}
/**
* {@code {x | lowerBound < x < +}}
*
* @param lowerBound 下界不能为空
* @param <T> 边界值类型
* @return 区间
* @throws NullPointerException 下界为{@code null}时抛出
* @see Bound#toRange()
*/
public static <T extends Comparable<? super T>> BoundedRange<T> greaterThan(final T lowerBound) {
return Bound.greaterThan(lowerBound).toRange();
}
/**
* {@code {x | lowerBound < x < +}}
*
* @param lowerBound 下界不能为空
* @param <T> 边界值类型
* @return 区间
* @throws NullPointerException 下界为{@code null}时抛出
* @see Bound#toRange()
*/
public static <T extends Comparable<? super T>> BoundedRange<T> atLeast(final T lowerBound) {
return Bound.atLeast(lowerBound).toRange();
}
/**
* {@code {x | - < x < upperBound}}
*
* @param upperBound 上界不能为空
* @param <T> 边界值类型
* @return 区间
* @throws NullPointerException 上界为{@code null}时抛出
* @see Bound#toRange()
*/
public static <T extends Comparable<? super T>> BoundedRange<T> lessThan(final T upperBound) {
return Bound.lessThan(upperBound).toRange();
}
/**
* {@code {x | - < x <= max}}
*
* @param upperBound 上界不能为空
* @param <T> 边界值类型
* @return 区间
* @throws NullPointerException 上界为{@code null}时抛出
* @see Bound#toRange()
*/
public static <T extends Comparable<? super T>> BoundedRange<T> atMost(final T upperBound) {
return Bound.atMost(upperBound).toRange();
}
/**
* 下界
*/
private final Bound<T> lowerBound;
/**
* 上界
*/
private final Bound<T> upperBound;
/**
* 构造
*
* @param lowerBound 下界
* @param upperBound 上界
*/
BoundedRange(final Bound<T> lowerBound, final Bound<T> upperBound) {
this.lowerBound = lowerBound;
this.upperBound = upperBound;
}
// endregion
// region ========== 通用方法 ==========
/**
* 获取下界
*
* @return 下界
*/
public Bound<T> getLowerBound() {
return lowerBound;
}
/**
* 获取下界值
*
* @return 下界值
*/
public T getLowerBoundValue() {
return getLowerBound().getValue();
}
/**
* 是否有下界
*
* @return 是否
*/
public boolean hasLowerBound() {
return Objects.nonNull(getLowerBound().getValue());
}
/**
* 获取上界
*
* @return 上界
*/
public Bound<T> getUpperBound() {
return upperBound;
}
/**
* 获取上界值
*
* @return 上界值
*/
public T getUpperBoundValue() {
return getUpperBound().getValue();
}
/**
* 是否有上界
*
* @return 是否
*/
public boolean hasUpperBound() {
return Objects.nonNull(getUpperBound().getValue());
}
/**
* <p>当前区间是否为空 <br>
* 当由下界<em>left</em>与上界<em>right</em>构成的区间
* 符合下述任意条件时认为当前区间为空
* <ul>
* <li>对任何区间{@code left > right}</li>
* <li>对半开半闭区间{@code [left, right)}{@code left == right}</li>
* <li>对开区间{@code (left, right)}{@code left == right}</li>
* <li>对半开半闭区间{@code (left, right]}{@code left == right}</li>
* </ul>
*
* @return 是否
*/
public boolean isEmpty() {
final Bound<T> low = getLowerBound();
final Bound<T> up = getUpperBound();
if (low instanceof NoneLowerBound || up instanceof NoneUpperBound) {
return false;
}
final int compareValue = low.getValue().compareTo(up.getValue());
if (compareValue < 0) {
return false;
}
// 上界小于下界时为空
return compareValue > 0
// 上下界的边界值相等且不为退化区间是为空
|| false == (low.getType().isClose() && up.getType().isClose());
}
/**
* 输出当前区间的字符串格式为{@code "[a, b]"}
*
* @return 字符串
*/
@Override
public String toString() {
return getLowerBound().descBound() + ", " + getUpperBound().descBound();
}
/**
* 比较两个实例是否相等
*
* @param o 另一实例
* @return 是否
*/
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final BoundedRange<?> that = (BoundedRange<?>) o;
return lowerBound.equals(that.lowerBound) && upperBound.equals(that.upperBound);
}
/**
* 获取实例哈希值
*
* @return 哈希值
*/
@Override
public int hashCode() {
return Objects.hash(lowerBound, upperBound);
}
// endregion
// region ========== 判断交并集 ==========
/**
* {@code other}是否是当前区间的子集
*
* @param other 另一个区间
* @return 是否
*/
public boolean isSuperset(final BoundedRange<T> other) {
return getLowerBound().compareTo(other.getLowerBound()) <= 0
&& getUpperBound().compareTo(other.getUpperBound()) >= 0;
}
/**
* {@code other}是否是当前区间的子集
*
* @param other 另一个区间
* @return 是否
*/
public boolean isProperSuperset(final BoundedRange<T> other) {
return getLowerBound().compareTo(other.getLowerBound()) < 0
&& getUpperBound().compareTo(other.getUpperBound()) > 0;
}
/**
* 当前区间是否是{@code other}的子集
*
* @param other 另一个区间
* @return 是否
*/
public boolean isSubset(final BoundedRange<T> other) {
return getLowerBound().compareTo(other.getLowerBound()) >= 0
&& getUpperBound().compareTo(other.getUpperBound()) <= 0;
}
/**
* 当前区间是否是{@code other}的真子集
*
* @param other 另一个区间
* @return 是否
*/
public boolean isProperSubset(final BoundedRange<T> other) {
return getLowerBound().compareTo(other.getLowerBound()) > 0
&& getUpperBound().compareTo(other.getUpperBound()) < 0;
}
/**
* {@code other}是否与当前区间不相交
*
* @param other 另一个区间
* @return 是否
*/
public boolean isDisjoint(final BoundedRange<T> other) {
return BoundedRangeOperation.isDisjoint(this, other);
}
/**
* {@code other}是否与当前区间相交
*
* @param other 另一个区间
* @return 是否
*/
public boolean isIntersected(final BoundedRange<T> other) {
return BoundedRangeOperation.isIntersected(this, other);
}
/**
* 指定值是否在当前区间内
*
* @param value 要检测的值
* @return 是否
*/
@Override
public boolean test(final T value) {
return getLowerBound()
.and(getUpperBound())
.test(value);
}
// endregion
// region ========== 交并集操作 ==========
/**
* {@code other}与当前区间相交则将其与当前区间合并
*
* @param other 另一个区间
* @return 合并后的新区间若两区间不相交则返回当前集合
*/
public BoundedRange<T> unionIfIntersected(final BoundedRange<T> other) {
return BoundedRangeOperation.unionIfIntersected(this, other);
}
/**
* 获得包含当前区间与指定区间的最小的区间
*
* @param other 另一个区间
* @return 包含当前区间与指定区间的最小的区间
*/
public BoundedRange<T> span(final BoundedRange<T> other) {
return BoundedRangeOperation.span(this, other);
}
/**
* {@code other}与当前区间不相连则获得两区间中间的间隔部分
*
* @param other 另一个区间
* @return 代表间隔部分的区间若两区间相交则返回{@code null}
*/
public BoundedRange<T> gap(final BoundedRange<T> other) {
return BoundedRangeOperation.gap(this, other);
}
/**
* {@code other}与当前区间相交则获得该区间与当前区间的交集
*
* @param other 另一个区间
* @return 代表交集的区间若无交集则返回{@code null}
*/
public BoundedRange<T> intersection(final BoundedRange<T> other) {
return BoundedRangeOperation.intersection(this, other);
}
/**
* 截取当前区间中大于{@code min}的部分{@code min}不在该区间中则返回当前区间本身
*
* @param min 最大的左值
* @return 区间
*/
public BoundedRange<T> subGreatThan(final T min) {
return BoundedRangeOperation.subGreatThan(this, min);
}
/**
* 截取当前区间中大于等于{@code min}的部分{@code min}不在该区间中则返回当前区间本身
*
* @param min 最大的左值
* @return 区间
*/
public BoundedRange<T> subAtLeast(final T min) {
return BoundedRangeOperation.subAtLeast(this, min);
}
/**
* 截取当前区间中小于{@code max}的部分{@code max}不在该区间中则返回当前区间本身
*
* @param max 最大的左值
* @return 区间
*/
public BoundedRange<T> subLessThan(final T max) {
return BoundedRangeOperation.subLessThan(this, max);
}
/**
* 截取当前区间中小于等于{@code max}的部分{@code max}不在该区间中则返回当前区间本身
*
* @param max 最大的左值
* @return 区间
*/
public BoundedRange<T> subAtMost(final T max) {
return BoundedRangeOperation.subAtMost(this, max);
}
// endregion
private static <T extends Comparable<? super T>> BoundedRange<T> checkEmpty(final BoundedRange<T> range) {
Assert.isFalse(range.isEmpty(), "{} is a empty range", range);
return range;
}
}

View File

@ -0,0 +1,181 @@
package cn.hutool.core.lang.range;
import cn.hutool.core.comparator.CompareUtil;
import cn.hutool.core.lang.Opt;
import java.util.Objects;
/**
* 边界区间的操作工具如子区间合并区间等
*
* @author huangchengxing
* @since 6.0.0
*/
public class BoundedRangeOperation {
/**
* {@code other}与当前区间相交则将其与当前区间合并
*
* @param <T> 可比较对象类型
* @param boundedRange 区间
* @param other 另一个区间
* @return 合并后的新区间若两区间不相交则返回当前集合
*/
public static <T extends Comparable<? super T>> BoundedRange<T> unionIfIntersected(final BoundedRange<T> boundedRange, final BoundedRange<T> other) {
Objects.requireNonNull(boundedRange);
Objects.requireNonNull(other);
if (isDisjoint(boundedRange, other)) {
return boundedRange;
}
return new BoundedRange<>(
CompareUtil.min(boundedRange.getLowerBound(), other.getLowerBound()),
CompareUtil.max(boundedRange.getUpperBound(), other.getUpperBound())
);
}
/**
* 获得包含当前区间与指定区间的最小的区间
*
* @param <T> 可比较对象类型
* @param boundedRange 区间
* @param other 另一个区间
* @return 包含当前区间与指定区间的最小的区间
*/
public static <T extends Comparable<? super T>> BoundedRange<T> span(final BoundedRange<T> boundedRange, final BoundedRange<T> other) {
Objects.requireNonNull(boundedRange);
Objects.requireNonNull(other);
return new BoundedRange<>(
CompareUtil.min(boundedRange.getLowerBound(), other.getLowerBound()),
CompareUtil.max(boundedRange.getUpperBound(), other.getUpperBound())
);
}
/**
* {@code other}与当前区间不相连则获得两区间中间的间隔部分
*
* @param <T> 可比较对象类型
* @param boundedRange 区间
* @param other 另一个区间
* @return 代表间隔部分的区间若两区间相交则返回{@code null}
*/
public static <T extends Comparable<? super T>> BoundedRange<T> gap(final BoundedRange<T> boundedRange, final BoundedRange<T> other) {
Objects.requireNonNull(boundedRange);
Objects.requireNonNull(other);
if (isIntersected(boundedRange, other)) {
return null;
}
return new BoundedRange<>(
CompareUtil.min(boundedRange.getUpperBound(), other.getUpperBound()).negate(),
CompareUtil.max(boundedRange.getLowerBound(), other.getLowerBound()).negate()
);
}
/**
* {@code other}与当前区间相交则获得该区间与当前区间的交集
*
* @param <T> 可比较对象类型
* @param boundedRange 区间
* @param other 另一个区间
* @return 代表交集的区间若无交集则返回{@code null}
*/
public static <T extends Comparable<? super T>> BoundedRange<T> intersection(final BoundedRange<T> boundedRange, final BoundedRange<T> other) {
Objects.requireNonNull(boundedRange);
Objects.requireNonNull(other);
if (isDisjoint(boundedRange, other)) {
return null;
}
return new BoundedRange<>(
CompareUtil.max(boundedRange.getLowerBound(), other.getLowerBound()),
CompareUtil.min(boundedRange.getUpperBound(), other.getUpperBound())
);
}
/**
* 截取当前区间中大于{@code min}的部分{@code min}不在该区间中则返回当前区间本身
*
* @param <T> 可比较对象类型
* @param boundedRange 区间
* @param min 最大的左值
* @return 区间
*/
public static <T extends Comparable<? super T>> BoundedRange<T> subGreatThan(final BoundedRange<T> boundedRange, final T min) {
return Opt.ofNullable(min)
.filter(boundedRange)
.map(t -> new BoundedRange<>(Bound.greaterThan(t), boundedRange.getUpperBound()))
.orElse(boundedRange);
}
/**
* 截取当前区间中大于等于{@code min}的部分{@code min}不在该区间中则返回当前区间本身
*
* @param <T> 可比较对象类型
* @param boundedRange 区间
* @param min 最大的左值
* @return 区间
*/
public static <T extends Comparable<? super T>> BoundedRange<T> subAtLeast(final BoundedRange<T> boundedRange, final T min) {
return Opt.ofNullable(min)
.filter(boundedRange)
.map(t -> new BoundedRange<>(Bound.atLeast(t), boundedRange.getUpperBound()))
.orElse(boundedRange);
}
/**
* 截取当前区间中小于{@code max}的部分{@code max}不在该区间中则返回当前区间本身
*
* @param <T> 可比较对象类型
* @param boundedRange 区间
* @param max 最大的左值
* @return 区间
*/
public static <T extends Comparable<? super T>> BoundedRange<T> subLessThan(final BoundedRange<T> boundedRange, final T max) {
return Opt.ofNullable(max)
.filter(boundedRange)
.map(t -> new BoundedRange<>(boundedRange.getLowerBound(), Bound.lessThan(max)))
.orElse(boundedRange);
}
/**
* 截取当前区间中小于等于{@code max}的部分{@code max}不在该区间中则返回当前区间本身
*
* @param <T> 可比较对象类型
* @param boundedRange 区间
* @param max 最大的左值
* @return 区间
*/
public static <T extends Comparable<? super T>> BoundedRange<T> subAtMost(final BoundedRange<T> boundedRange, final T max) {
return Opt.ofNullable(max)
.filter(boundedRange)
.map(t -> new BoundedRange<>(boundedRange.getLowerBound(), Bound.atMost(max)))
.orElse(boundedRange);
}
// region ========== 判断交并集 ==========
/**
* {@code boundedRange}是否与{@code other}相交
*
* @param <T> 可比较对象类型
* @param boundedRange 区间
* @param other 另一个区间
* @return 是否相交
*/
public static <T extends Comparable<? super T>> boolean isIntersected(final BoundedRange<T> boundedRange, final BoundedRange<T> other) {
return false == isDisjoint(boundedRange, other);
}
/**
* {@code boundedRange}是否与{@code other}前区间不相交
*
* @param <T> 可比较对象类型
* @param boundedRange 区间
* @param other 另一个区间
* @return 是否
*/
public static <T extends Comparable<? super T>> boolean isDisjoint(final BoundedRange<T> boundedRange, final BoundedRange<T> other) {
Objects.requireNonNull(boundedRange);
Objects.requireNonNull(other);
return boundedRange.getLowerBound().compareTo(other.getUpperBound()) > 0
|| boundedRange.getUpperBound().compareTo(other.getLowerBound()) < 0;
}
// endregion
}

View File

@ -0,0 +1,193 @@
package cn.hutool.core.lang.range;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjUtil;
import java.util.Objects;
/**
* 由一个有限值构成的边界
*
* @param <T> 边界值类型
*/
class FiniteBound<T extends Comparable<? super T>> implements Bound<T> {
/**
* 边界值
*/
private final T value;
/**
* 边界类型
*/
private final BoundType type;
/**
* 构造
*
* @param value 边界值
* @param type 边界类型
*/
FiniteBound(final T value, final BoundType type) {
this.value = value;
this.type = type;
}
/**
* 获取边界值
*
* @return 边界值
*/
@Override
public T getValue() {
return value;
}
/**
* 获取边界类型
*
* @return 边界类型
*/
@Override
public BoundType getType() {
return type;
}
/**
* 检验指定值是否在当前边界表示的范围内
*
* @param t 要检验的值不允许为{@code null}
* @return 是否
*/
@Override
public boolean test(final T t) {
final BoundType bt = this.getType();
final int compareValue = getValue().compareTo(t);
// 与边界值相等
if (compareValue == 0) {
return bt.isClose();
}
// 小于或大于边界值
return compareValue > 0 ? bt.isUpperBound() : bt.isLowerBound();
}
/**
* <p>比较另一边界与当前边界在坐标轴上位置的先后顺序<br>
* 若令当前边界为<em>t1</em>另一边界为<em>t2</em>则有
* <ul>
* <li>-1<em>t1</em><em>t2</em>的左侧</li>
* <li>0<em>t1</em><em>t2</em>的重合</li>
* <li>-1<em>t1</em><em>t2</em>的右侧</li>
* </ul>
*
* @param bound 边界
* @return 位置
*/
@Override
public int compareTo(final Bound<T> bound) {
// 另一边界为无限小的左边界则当前边界必然靠后
if (bound instanceof NoneLowerBound) {
return 1;
}
// 另一边界为无限大的右边界则当前边界必然靠前
if (bound instanceof NoneUpperBound) {
return -1;
}
// 两值不相等直接比较边界值
if (ObjUtil.notEquals(getValue(), bound.getValue())) {
return getValue().compareTo(bound.getValue());
}
// 两边界值相等
return compareIfSameBoundValue(bound);
}
/**
* 获取{@code "[value"}{@code "(value"}格式的字符串
*
* @return 字符串
*/
@Override
public String descBound() {
final BoundType bt = getType();
return bt.isLowerBound() ? bt.getSymbol() + getValue() : getValue() + bt.getSymbol();
}
/**
* 对当前边界取反
*
* @return 取反后的边界
*/
@Override
public Bound<T> negate() {
return new FiniteBound<>(value, getType().negate());
}
/**
* 将当前实例转为一个区间
*
* @return 区间
*/
@Override
public BoundedRange<T> toRange() {
return getType().isLowerBound() ?
new BoundedRange<>(this, Bound.noneUpperBound()) : new BoundedRange<>(Bound.noneLowerBound(), this);
}
/**
* 两实例是否相等
*
* @param o 另一实例
* @return 是否
*/
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final FiniteBound<?> that = (FiniteBound<?>)o;
return value.equals(that.value) && type == that.type;
}
/**
* 获取哈希值
*
* @return 哈希值
*/
@Override
public int hashCode() {
return Objects.hash(value, type);
}
/**
* 获得当前实例对应的{@code {x| x >= xxx}}格式的不等式字符串
*
* @return 字符串
*/
@Override
public String toString() {
return CharSequenceUtil.format(
"{x | x {} {}}", type.getOperator(), value
);
}
/**
* 当两个边界的值不相等时判断它们在坐标轴上位置的先后顺序
*/
private int compareIfSameBoundValue(final Bound<T> bound) {
final BoundType bt1 = this.getType();
final BoundType bt2 = bound.getType();
// 两边界类型相同说明连边界重合
if (bt1 == bt2) {
return 0;
}
// 一为左边界一为右边界则左边界恒在右边界后
if (bt1.isDislocated(bt2)) {
return bt1.isLowerBound() ? 1 : -1;
}
// 都为左边界则封闭边界在前若都为右边界则封闭边界在后
return Integer.compare(bt1.getCode(), bt2.getCode());
}
}

View File

@ -0,0 +1,108 @@
package cn.hutool.core.lang.range;
/**
* 无限小的左边界
*
* @param <T> 边界值类型
* @author huangchengxing
* @since 6.0.0
*/
class NoneLowerBound<T extends Comparable<? super T>> implements Bound<T> {
/**
* 无限小的左边界单例
*/
@SuppressWarnings("rawtypes")
static final NoneLowerBound INSTANCE = new NoneLowerBound();
private NoneLowerBound() {
}
/**
* 获取边界值
*
* @return 边界值
*/
@Override
public T getValue() {
return null;
}
/**
* 获取边界类型
*
* @return 边界类型
*/
@Override
public BoundType getType() {
return BoundType.OPEN_LOWER_BOUND;
}
/**
* 检验指定值是否在当前边界表示的范围内
*
* @param t 要检验的值不允许为{@code null}
* @return 是否
*/
@Override
public boolean test(final T t) {
return true;
}
/**
* <p>比较另一边界与当前边界在坐标轴上位置的先后顺序<br>
* 若令当前边界为<em>t1</em>另一边界为<em>t2</em>则有
* <ul>
* <li>-1<em>t1</em><em>t2</em>的左侧</li>
* <li>0<em>t1</em><em>t2</em>的重合</li>
* <li>-1<em>t1</em><em>t2</em>的右侧</li>
* </ul>
*
* @param bound 边界
* @return 位置
*/
@Override
public int compareTo(final Bound<T> bound) {
return bound instanceof NoneLowerBound ? 0 : -1;
}
/**
* 获取{@code "[value"}{@code "(value"}格式的字符串
*
* @return 字符串
*/
@Override
public String descBound() {
return getType().getSymbol() + INFINITE_MIN;
}
/**
* 对当前边界取反
*
* @return 取反后的边界
*/
@Override
public Bound<T> negate() {
return this;
}
/**
* 将当前实例转为一个区间
*
* @return 区间
*/
@Override
public BoundedRange<T> toRange() {
return BoundedRange.all();
}
/**
* 获得当前实例对应的{@code { x | x >= xxx}}格式的不等式字符串
*
* @return 字符串
*/
@Override
public String toString() {
return "{x | x > -\u221e}";
}
}

View File

@ -0,0 +1,106 @@
package cn.hutool.core.lang.range;
/**
* 无限大的右边界
*
* @param <T> 边界值类型
*/
class NoneUpperBound<T extends Comparable<? super T>> implements Bound<T> {
/**
* 无限大的右边界
*/
@SuppressWarnings("rawtypes")
static final NoneUpperBound INSTANCE = new NoneUpperBound();
private NoneUpperBound() {
}
/**
* 获取边界值
*
* @return 边界值
*/
@Override
public T getValue() {
return null;
}
/**
* 获取边界类型
*
* @return 边界类型
*/
@Override
public BoundType getType() {
return BoundType.OPEN_UPPER_BOUND;
}
/**
* 检验指定值是否在当前边界表示的范围内
*
* @param t 要检验的值不允许为{@code null}
* @return 是否
*/
@Override
public boolean test(final T t) {
return true;
}
/**
* <p>比较另一边界与当前边界在坐标轴上位置的先后顺序<br>
* 若令当前边界为<em>t1</em>另一边界为<em>t2</em>则有
* <ul>
* <li>-1<em>t1</em><em>t2</em>的左侧</li>
* <li>0<em>t1</em><em>t2</em>的重合</li>
* <li>-1<em>t1</em><em>t2</em>的右侧</li>
* </ul>
*
* @param bound 边界
* @return 位置
*/
@Override
public int compareTo(final Bound<T> bound) {
return bound instanceof NoneUpperBound ? 0 : 1;
}
/**
* 获取{@code "[value"}{@code "(value"}格式的字符串
*
* @return 字符串
*/
@Override
public String descBound() {
return INFINITE_MAX + getType().getSymbol();
}
/**
* 获得当前实例对应的{@code { x | x >= xxx}}格式的不等式字符串
*
* @return 字符串
*/
@Override
public String toString() {
return "{x | x < +\u221e}";
}
/**
* 对当前边界取反
*
* @return 取反后的边界
*/
@Override
public Bound<T> negate() {
return this;
}
/**
* 将当前实例转为一个区间
*
* @return 区间
*/
@Override
public BoundedRange<T> toRange() {
return BoundedRange.all();
}
}

View File

@ -1,5 +1,6 @@
package cn.hutool.core.lang;
package cn.hutool.core.lang.range;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.thread.lock.NoLock;
import java.io.Serializable;

View File

@ -0,0 +1,10 @@
/**
* 提供区间和边界封装主要包括
* <ul>
* <li>{@link cn.hutool.core.lang.range.Bound}: 提供边界的抽象表示包括边界范围开闭区间等</li>
* <li>{@link cn.hutool.core.lang.range.Range}: 提供可迭代的区间</li>
* </ul>
*
* @author huangchengxing, looly
*/
package cn.hutool.core.lang.range;

View File

@ -2,8 +2,10 @@ package cn.hutool.core.map;
import cn.hutool.core.text.StrUtil;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* 驼峰Key风格的Map<br>
@ -73,7 +75,8 @@ public class CamelCaseMap<K, V> extends FuncKeyMap<K, V> {
*/
@SuppressWarnings("unchecked")
CamelCaseMap(final MapBuilder<K, V> emptyMapBuilder) {
super(emptyMapBuilder.build(), (key) -> {
// issue#I5VRHW@Gitee 使Function可以被序列化
super(emptyMapBuilder.build(), (Function<Object, K> & Serializable)(key) -> {
if (key instanceof CharSequence) {
key = StrUtil.toCamelCase(key.toString());
}

View File

@ -1,7 +1,9 @@
package cn.hutool.core.map;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* 忽略大小写的Map<br>
@ -73,7 +75,8 @@ public class CaseInsensitiveMap<K, V> extends FuncKeyMap<K, V> {
*/
@SuppressWarnings("unchecked")
CaseInsensitiveMap(final MapBuilder<K, V> emptyMapBuilder) {
super(emptyMapBuilder.build(), (key)->{
// issue#I5VRHW@Gitee 使Function可以被序列化
super(emptyMapBuilder.build(), (Function<Object, K> & Serializable)(key)->{
if (key instanceof CharSequence) {
key = key.toString().toLowerCase();
}

View File

@ -1,21 +1,28 @@
package cn.hutool.core.map;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
/**
* 固定大小的{@link LinkedHashMap} 实现<br>
* 注意此类非线程安全由于{@link #get(Object)}操作会修改链表的顺序结构因此也不可以使用读写锁
*
* @author looly
*
* @param <K> 键类型
* @param <V> 值类型
* @author looly
*/
public class FixedLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
private static final long serialVersionUID = -629171177321416095L;
/** 容量,超过此容量自动删除末尾元素 */
/**
* 容量超过此容量自动删除末尾元素
*/
private int capacity;
/**
* 移除监听
*/
private Consumer<java.util.Map.Entry<K, V>> removeListener;
/**
* 构造
@ -45,10 +52,25 @@ public class FixedLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
this.capacity = capacity;
}
/**
* 设置自定义移除监听
*
* @param removeListener 移除监听
*/
public void setRemoveListener(final Consumer<Map.Entry<K, V>> removeListener) {
this.removeListener = removeListener;
}
@Override
protected boolean removeEldestEntry(final java.util.Map.Entry<K, V> eldest) {
//当链表元素大于容量时移除最老最久未被使用的元素
return size() > this.capacity;
if (size() > this.capacity) {
if (null != removeListener) {
// 自定义监听
removeListener.accept(eldest);
}
return true;
}
return false;
}
}

View File

@ -1,5 +1,6 @@
package cn.hutool.core.math;
import cn.hutool.core.comparator.CompareUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.ArrayUtil;
@ -946,78 +947,6 @@ public class NumberUtil {
return Long.parseLong(binaryStr, 2);
}
/**
* 检查值是否在指定范围内
*
* @param value
* @param minInclude 最小值包含
* @param maxInclude 最大值包含
* @return 经过检查后的值
* @since 5.8.5
*/
public static boolean isIn(final BigDecimal value, final BigDecimal minInclude, final BigDecimal maxInclude) {
Assert.notNull(value);
Assert.notNull(minInclude);
Assert.notNull(maxInclude);
return isGreaterOrEqual(value, minInclude) && isLessOrEqual(value, maxInclude);
}
/**
* 比较大小参数1 &gt; 参数2 返回true
*
* @param bigNum1 数字1
* @param bigNum2 数字2
* @return 是否大于
* @since 3.0.9
*/
public static boolean isGreater(final BigDecimal bigNum1, final BigDecimal bigNum2) {
Assert.notNull(bigNum1);
Assert.notNull(bigNum2);
return bigNum1.compareTo(bigNum2) > 0;
}
/**
* 比较大小参数1 &gt;= 参数2 返回true
*
* @param bigNum1 数字1
* @param bigNum2 数字2
* @return 是否大于等于
* @since 3, 0.9
*/
public static boolean isGreaterOrEqual(final BigDecimal bigNum1, final BigDecimal bigNum2) {
Assert.notNull(bigNum1);
Assert.notNull(bigNum2);
return bigNum1.compareTo(bigNum2) >= 0;
}
/**
* 比较大小参数1 &lt; 参数2 返回true
*
* @param bigNum1 数字1
* @param bigNum2 数字2
* @return 是否小于
* @since 3, 0.9
*/
public static boolean isLess(final BigDecimal bigNum1, final BigDecimal bigNum2) {
Assert.notNull(bigNum1);
Assert.notNull(bigNum2);
return bigNum1.compareTo(bigNum2) < 0;
}
/**
* 比较大小参数1&lt;=参数2 返回true
*
* @param bigNum1 数字1
* @param bigNum2 数字2
* @return 是否小于等于
* @since 3, 0.9
*/
public static boolean isLessOrEqual(final BigDecimal bigNum1, final BigDecimal bigNum2) {
Assert.notNull(bigNum1);
Assert.notNull(bigNum2);
return bigNum1.compareTo(bigNum2) <= 0;
}
/**
* 比较大小值相等 返回true<br>
* 此方法通过调用{@link Double#doubleToLongBits(double)}方法来判断是否相等<br>
@ -1067,17 +996,10 @@ public class NumberUtil {
* @param bigNum1 数字1
* @param bigNum2 数字2
* @return 是否相等
* @see CompareUtil#equals(Comparable, Comparable)
*/
public static boolean equals(final BigDecimal bigNum1, final BigDecimal bigNum2) {
//noinspection NumberEquality
if (bigNum1 == bigNum2) {
// 如果用户传入同一对象省略compareTo以提高性能
return true;
}
if (bigNum1 == null || bigNum2 == null) {
return false;
}
return 0 == bigNum1.compareTo(bigNum2);
return CompareUtil.equals(bigNum1, bigNum2);
}
/**

View File

@ -1,7 +1,7 @@
package cn.hutool.core.net.url;
import cn.hutool.core.lang.builder.Builder;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.builder.Builder;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.CharsetUtil;
@ -280,6 +280,21 @@ public final class UrlBuilder implements Builder<String> {
return port;
}
/**
* 获取端口如果未自定义返回协议默认端口
*
* @return 端口
* @since 5.8.9
*/
public int getPortWithDefault() {
int port = getPort();
if (port <= 0) {
port = toURL().getDefaultPort();
return port;
}
return port;
}
/**
* 设置端口默认-1
*

View File

@ -336,7 +336,7 @@ public class MethodUtil {
public static Method[] getDeclaredMethods(final Class<?> beanClass) throws SecurityException {
Assert.notNull(beanClass);
return DECLARED_METHODS_CACHE.computeIfAbsent(beanClass,
key -> getMethodsDirectly(beanClass, false, Objects.equals(Object.class, beanClass)));
key -> getMethodsDirectly(beanClass, false, Objects.equals(Object.class, beanClass)));
}
/**
@ -781,7 +781,7 @@ public class MethodUtil {
actualArgs[i] = null;
} else if (false == parameterTypes[i].isAssignableFrom(args[i].getClass())) {
//对于类型不同的字段尝试转换转换失败则使用原对象类型
final Object targetValue = Convert.convert(parameterTypes[i], args[i]);
final Object targetValue = Convert.convertQuietly(parameterTypes[i], args[i], args[i]);
if (null != targetValue) {
actualArgs[i] = targetValue;
}

View File

@ -0,0 +1,23 @@
package cn.hutool.core.reflect;
import java.lang.reflect.Type;
/**
* 空类型表示
*
* @author looly
* @since 6.0.0
*/
public class NullType implements Type {
/**
* 单例对象
*/
public static NullType INSTANCE = new NullType();
private NullType(){}
@Override
public String toString() {
return "Type of null";
}
}

View File

@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.collection.UniqueKeySet;
import cn.hutool.core.comparator.CompareUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
@ -139,6 +140,20 @@ public class ArrayUtil extends PrimitiveArrayUtil {
return null == firstNonNull(array);
}
/**
* 是否包含非{@code null}元素<br>
* 如果列表是{@code null}或者空返回{@code false}否则当列表中有非{@code null}字符时返回{@code true}
*
* @param <T> 数组元素类型
* @param array 被检查的数组
* @return 是否包含非{@code null}元素
* @since 5.4.0
*/
@SuppressWarnings("unchecked")
public static <T> boolean hasNonNull(final T... array) {
return null != firstNonNull(array);
}
/**
* 返回数组中第一个非空元素
*
@ -317,15 +332,18 @@ public class ArrayUtil extends PrimitiveArrayUtil {
* 将新元素添加到已有数组中<br>
* 添加新元素会生成一个新的数组不影响原数组
*
* @param <A> 数组类型
* @param <T> 数组元素类型
* @param array 已有数组
* @param newElements 新元素
* @return 新数组
*/
@SuppressWarnings("unchecked")
@SafeVarargs
public static <T> Object append(final Object array, final T... newElements) {
public static <A, T> A append(final A array, final T... newElements) {
if (isEmpty(array)) {
return newElements;
// 可变长参数可能为包装类型如果array是原始类型则此处强转不合适采用万能转换器完成转换
return (A) Convert.convert(array.getClass(), newElements);
}
return insert(array, length(array), newElements);
}
@ -357,13 +375,14 @@ public class ArrayUtil extends PrimitiveArrayUtil {
/**
* 将元素值设置为数组的某个位置当给定的index大于数组长度则追加
*
* @param <A> 数组类型
* @param array 已有数组
* @param index 位置大于长度追加否则替换
* @param value 新值
* @return 新数组或原有数组
* @since 4.1.2
*/
public static Object setOrAppend(final Object array, final int index, final Object value) {
public static <A> A setOrAppend(final A array, final int index, final Object value) {
if (index < length(array)) {
Array.set(array, index, value);
return array;
@ -436,6 +455,7 @@ public class ArrayUtil extends PrimitiveArrayUtil {
* 添加新元素会生成一个新的数组不影响原数组<br>
* 如果插入位置为为负数从原数组从后向前计数若大于原数组长度则空白处用null填充
*
* @param <A> 数组类型
* @param <T> 数组元素类型
* @param array 已有数组
* @param index 插入位置此位置为对应此位置元素之前的空档
@ -444,12 +464,12 @@ public class ArrayUtil extends PrimitiveArrayUtil {
* @since 4.0.8
*/
@SuppressWarnings({"unchecked", "SuspiciousSystemArraycopy"})
public static <T> Object insert(final Object array, int index, final T... newElements) {
public static <A, T> A insert(final A array, int index, final T... newElements) {
if (isEmpty(newElements)) {
return array;
}
if (isEmpty(array)) {
return newElements;
return (A) Convert.convert(array.getClass(), newElements);
}
final int len = length(array);
@ -457,13 +477,13 @@ public class ArrayUtil extends PrimitiveArrayUtil {
index = (index % len) + len;
}
final T[] result = newArray(array.getClass().getComponentType(), Math.max(len, index) + newElements.length);
final Object result = Array.newInstance(array.getClass().getComponentType(), Math.max(len, index) + newElements.length);
System.arraycopy(array, 0, result, 0, Math.min(len, index));
System.arraycopy(newElements, 0, result, index, newElements.length);
if (index < len) {
System.arraycopy(array, index, result, index + newElements.length, len - index);
}
return result;
return (A) result;
}
/**
@ -496,6 +516,7 @@ public class ArrayUtil extends PrimitiveArrayUtil {
* @param newSize 新的数组大小
* @return 调整后的新数组
* @since 4.6.7
* @see System#arraycopy(Object, int, Object, int, int)
*/
public static Object resize(final Object array, final int newSize) {
if (newSize < 0) {
@ -1572,7 +1593,7 @@ public class ArrayUtil extends PrimitiveArrayUtil {
/**
* 是否存都为{@code null}或空对象通过{@link ObjUtil#isEmpty(Object)} 判断元素
*
* @param <T> 元素类型
* @param <T> 元素类型
* @param args 被检查的对象,一个或者多个
* @return 是否都为空
* @since 4.5.18

View File

@ -259,7 +259,11 @@ public class CharUtil implements CharPool {
|| Character.isSpaceChar(c)
|| c == '\ufeff'
|| c == '\u202a'
|| c == '\u0000';
|| c == '\u0000'
// issue#I5UGSQHangul Filler
|| c == '\u3164'
// Braille Pattern Blank
|| c == '\u2800';
}
/**

View File

@ -2612,7 +2612,7 @@ public class PrimitiveArrayUtil {
return array;
}
// ---------------------------------------------------------------------- shuffle
// ---------------------------------------------------------------------- swap
/**
* 交换数组中两个位置的值
@ -2766,17 +2766,7 @@ public class PrimitiveArrayUtil {
return array;
}
/**
* 检查数组是否升序即array[i] &lt;= array[i+1]若传入空数组则返回false
*
* @param array 数组
* @return 数组是否升序
* @author FengBaoheng
* @since 5.5.2
*/
public static boolean isSorted(final byte[] array) {
return isSortedASC(array);
}
// ---------------------------------------------------------------------- asc and desc
/**
* 检查数组是否升序即array[i] &lt;= array[i+1]若传入空数组则返回false
@ -2822,18 +2812,6 @@ public class PrimitiveArrayUtil {
return true;
}
/**
* 检查数组是否升序即array[i] &lt;= array[i+1]若传入空数组则返回false
*
* @param array 数组
* @return 数组是否升序
* @author FengBaoheng
* @since 5.5.2
*/
public static boolean isSorted(final short[] array) {
return isSortedASC(array);
}
/**
* 检查数组是否升序即array[i] &lt;= array[i+1]若传入空数组则返回false
*
@ -2878,18 +2856,6 @@ public class PrimitiveArrayUtil {
return true;
}
/**
* 检查数组是否升序即array[i] &lt;= array[i+1]若传入空数组则返回false
*
* @param array 数组
* @return 数组是否升序
* @author FengBaoheng
* @since 5.5.2
*/
public static boolean isSorted(final char[] array) {
return isSortedASC(array);
}
/**
* 检查数组是否升序即array[i] &lt;= array[i+1]若传入空数组则返回false
*
@ -2934,18 +2900,6 @@ public class PrimitiveArrayUtil {
return true;
}
/**
* 检查数组是否升序即array[i] &lt;= array[i+1]若传入空数组则返回false
*
* @param array 数组
* @return 数组是否升序
* @author FengBaoheng
* @since 5.5.2
*/
public static boolean isSorted(final int[] array) {
return isSortedASC(array);
}
/**
* 检查数组是否升序即array[i] &lt;= array[i+1]若传入空数组则返回false
*
@ -2990,18 +2944,6 @@ public class PrimitiveArrayUtil {
return true;
}
/**
* 检查数组是否升序即array[i] &lt;= array[i+1]若传入空数组则返回false
*
* @param array 数组
* @return 数组是否升序
* @author FengBaoheng
* @since 5.5.2
*/
public static boolean isSorted(final long[] array) {
return isSortedASC(array);
}
/**
* 检查数组是否升序即array[i] &lt;= array[i+1]若传入空数组则返回false
*
@ -3046,18 +2988,6 @@ public class PrimitiveArrayUtil {
return true;
}
/**
* 检查数组是否升序即array[i] &lt;= array[i+1]若传入空数组则返回false
*
* @param array 数组
* @return 数组是否升序
* @author FengBaoheng
* @since 5.5.2
*/
public static boolean isSorted(final double[] array) {
return isSortedASC(array);
}
/**
* 检查数组是否升序即array[i] &lt;= array[i+1]若传入空数组则返回false
*
@ -3102,18 +3032,6 @@ public class PrimitiveArrayUtil {
return true;
}
/**
* 检查数组是否升序即array[i] &lt;= array[i+1]若传入空数组则返回false
*
* @param array 数组
* @return 数组是否升序
* @author FengBaoheng
* @since 5.5.2
*/
public static boolean isSorted(final float[] array) {
return isSortedASC(array);
}
/**
* 检查数组是否升序即array[i] &lt;= array[i+1]若传入空数组则返回false
*

View File

@ -575,6 +575,21 @@ public class BeanUtilTest {
Assert.assertNull(BeanUtil.copyProperties(null, Food.class));
}
@Test
public void copyPropertiesMapToMapIgnoreNullTest() {
// 测试MapToMap
final Map<String, Object> p1 = new HashMap<>();
p1.put("isSlow", true);
p1.put("name", "测试");
p1.put("subName", null);
final Map<String, Object> map = MapUtil.newHashMap();
BeanUtil.copyProperties(p1, map, CopyOptions.of().setIgnoreNullValue(true));
Assert.assertTrue((Boolean) map.get("isSlow"));
Assert.assertEquals("测试", map.get("name"));
Assert.assertFalse(map.containsKey("subName"));
}
@Test
public void copyBeanPropertiesFilterTest() {
final Food info = new Food();

View File

@ -0,0 +1,55 @@
package cn.hutool.core.bean;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.lang.Console;
import lombok.Data;
import org.junit.Ignore;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class Issue2649Test {
@Test
@Ignore
public void toListTest() {
final List<View1> view1List = new ArrayList<>();
for (int i = 0; i < 200; i++) {
final View1 view1 = new View1();
view1.setA(String.valueOf(i));
view1.setB(String.valueOf(i));
for (int j = 0; j < 2; j++) {
final View2 view2 = new View2();
view1.setA(String.valueOf(i));
view1.setB(String.valueOf(i));
view1.getViewList().add(view2);
}
view1List.add(view1);
}
final StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < 50; i++) {
@SuppressWarnings("unused")
final List<View2> view2s = BeanUtil.copyToList(view1List, View2.class);
}
stopWatch.stop();
Console.log(stopWatch.getTotalTimeSeconds());
}
@Data
static class View1{
private String a;
private String b;
private List<View2> viewList = new ArrayList<>();
}
@Data
static class View2{
private String a;
private String b;
private List<View2> viewList = new ArrayList<>();
}
}

View File

@ -1,6 +1,7 @@
package cn.hutool.core.cache;
import cn.hutool.core.cache.impl.LRUCache;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.RandomUtil;
import org.junit.Assert;
@ -8,6 +9,7 @@ import org.junit.Ignore;
import org.junit.Test;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
/**
* <a href="https://github.com/dromara/hutool/issues/1895">https://github.com/dromara/hutool/issues/1895</a><br>
@ -64,4 +66,23 @@ public class LRUCacheTest {
}
Assert.assertEquals("null123456789", sb2.toString());
}
@Test
public void issue2647Test(){
final AtomicInteger removeCount = new AtomicInteger();
final LRUCache<String, Integer> cache = CacheUtil.newLRUCache(3,1);
cache.setListener((key, value) -> {
// 共移除7次
removeCount.incrementAndGet();
//Console.log("Start remove k-v, key:{}, value:{}", key, value);
});
for (int i = 0; i < 10; i++) {
cache.put(StrUtil.format("key-{}", i), i);
}
Assert.assertEquals(7, removeCount.get());
Assert.assertEquals(3, cache.size());
}
}

View File

@ -12,4 +12,10 @@ public class FileNameUtilTest {
name = FileNameUtil.cleanInvalid("\r1\r\n2\n");
Assert.assertEquals("12", name);
}
@Test
public void mainNameTest() {
final String s = FileNameUtil.mainName("abc.tar.gz");
Assert.assertEquals("abc", s);
}
}

View File

@ -0,0 +1,212 @@
package cn.hutool.core.lang.range;
import org.junit.Assert;
import org.junit.Test;
/**
* test for {@link Bound}
*/
@SuppressWarnings("EqualsWithItself")
public class BoundTest {
@Test
public void testEquals() {
final Bound<Integer> bound = new FiniteBound<>(1, BoundType.OPEN_UPPER_BOUND);
Assert.assertEquals(bound, bound);
Assert.assertEquals(bound, new FiniteBound<>(1, BoundType.OPEN_UPPER_BOUND));
Assert.assertNotEquals(bound, new FiniteBound<>(2, BoundType.OPEN_UPPER_BOUND));
Assert.assertNotEquals(bound, new FiniteBound<>(1, BoundType.OPEN_LOWER_BOUND));
Assert.assertNotEquals(bound, null);
}
@Test
public void testHashCode() {
final int hashCode = new FiniteBound<>(1, BoundType.OPEN_UPPER_BOUND).hashCode();
Assert.assertEquals(hashCode, new FiniteBound<>(1, BoundType.OPEN_UPPER_BOUND).hashCode());
Assert.assertNotEquals(hashCode, new FiniteBound<>(2, BoundType.OPEN_UPPER_BOUND).hashCode());
Assert.assertNotEquals(hashCode, new FiniteBound<>(1, BoundType.OPEN_LOWER_BOUND).hashCode());
}
@Test
public void testNoneLowerBound() {
final Bound<Integer> bound = Bound.noneLowerBound();
// negate
Assert.assertEquals(bound, bound.negate());
// test
Assert.assertTrue(bound.test(Integer.MAX_VALUE));
// getType
Assert.assertEquals(BoundType.OPEN_LOWER_BOUND, bound.getType());
// getValue
Assert.assertNull(bound.getValue());
// toString
Assert.assertEquals("(" + "-\u221e", bound.descBound());
// compareTo
Assert.assertEquals(0, bound.compareTo(bound));
Assert.assertEquals(-1, bound.compareTo(Bound.atMost(1)));
Assert.assertEquals(BoundedRange.all(), bound.toRange());
Assert.assertEquals("{x | x > -\u221e}", bound.toString());
}
@Test
public void testNoneUpperBound() {
final Bound<Integer> bound = Bound.noneUpperBound();
// negate
Assert.assertEquals(bound, bound.negate());
// test
Assert.assertTrue(bound.test(Integer.MAX_VALUE));
// getType
Assert.assertEquals(BoundType.OPEN_UPPER_BOUND, bound.getType());
// getValue
Assert.assertNull(bound.getValue());
// toString
Assert.assertEquals("+\u221e" + ")", bound.descBound());
// compareTo
Assert.assertEquals(0, bound.compareTo(bound));
Assert.assertEquals(1, bound.compareTo(Bound.atMost(1)));
Assert.assertEquals(BoundedRange.all(), bound.toRange());
Assert.assertEquals("{x | x < +\u221e}", bound.toString());
}
@Test
public void testGreatThan() {
// { x | x > 0}
Bound<Integer> bound = Bound.greaterThan(0);
// test
Assert.assertTrue(bound.test(1));
Assert.assertFalse(bound.test(0));
Assert.assertFalse(bound.test(-1));
// getType
Assert.assertEquals(BoundType.OPEN_LOWER_BOUND, bound.getType());
// getValue
Assert.assertEquals((Integer)0, bound.getValue());
// toString
Assert.assertEquals("(0", bound.descBound());
Assert.assertEquals("{x | x > 0}", bound.toString());
// compareTo
Assert.assertEquals(0, bound.compareTo(bound));
Assert.assertEquals(-1, bound.compareTo(Bound.noneUpperBound()));
Assert.assertEquals(1, bound.compareTo(Bound.atLeast(-1)));
Assert.assertEquals(-1, bound.compareTo(Bound.atLeast(2)));
Assert.assertEquals(1, bound.compareTo(Bound.lessThan(0)));
Assert.assertEquals(1, bound.compareTo(Bound.atMost(0)));
Assert.assertEquals(-1, bound.compareTo(Bound.atMost(2)));
Assert.assertEquals(1, bound.compareTo(Bound.noneLowerBound()));
// { x | x >= 0}
bound = bound.negate();
Assert.assertEquals((Integer)0, bound.getValue());
Assert.assertEquals(BoundType.CLOSE_UPPER_BOUND, bound.getType());
Assert.assertNotNull(bound.toRange());
}
@Test
public void testAtLeast() {
// { x | x >= 0}
Bound<Integer> bound = Bound.atLeast(0);
// test
Assert.assertTrue(bound.test(1));
Assert.assertTrue(bound.test(0));
Assert.assertFalse(bound.test(-1));
// getType
Assert.assertEquals(BoundType.CLOSE_LOWER_BOUND, bound.getType());
// getValue
Assert.assertEquals((Integer)0, bound.getValue());
// toString
Assert.assertEquals("[0", bound.descBound());
Assert.assertEquals("{x | x >= 0}", bound.toString());
// compareTo
Assert.assertEquals(0, bound.compareTo(bound));
Assert.assertEquals(-1, bound.compareTo(Bound.noneUpperBound()));
Assert.assertEquals(1, bound.compareTo(Bound.greaterThan(-1)));
Assert.assertEquals(-1, bound.compareTo(Bound.greaterThan(0)));
Assert.assertEquals(1, bound.compareTo(Bound.lessThan(0)));
Assert.assertEquals(1, bound.compareTo(Bound.atMost(0)));
Assert.assertEquals(-1, bound.compareTo(Bound.atMost(2)));
Assert.assertEquals(1, bound.compareTo(Bound.noneLowerBound()));
// { x | x < 0}
bound = bound.negate();
Assert.assertEquals((Integer)0, bound.getValue());
Assert.assertEquals(BoundType.OPEN_UPPER_BOUND, bound.getType());
Assert.assertNotNull(bound.toRange());
}
@Test
public void testLessThan() {
// { x | x < 0}
Bound<Integer> bound = Bound.lessThan(0);
// test
Assert.assertFalse(bound.test(1));
Assert.assertFalse(bound.test(0));
Assert.assertTrue(bound.test(-1));
// getType
Assert.assertEquals(BoundType.OPEN_UPPER_BOUND, bound.getType());
// getValue
Assert.assertEquals((Integer)0, bound.getValue());
// toString
Assert.assertEquals("0)", bound.descBound());
Assert.assertEquals("{x | x < 0}", bound.toString());
// compareTo
Assert.assertEquals(0, bound.compareTo(bound));
Assert.assertEquals(-1, bound.compareTo(Bound.noneUpperBound()));
Assert.assertEquals(1, bound.compareTo(Bound.greaterThan(-1)));
Assert.assertEquals(-1, bound.compareTo(Bound.greaterThan(0)));
Assert.assertEquals(1, bound.compareTo(Bound.lessThan(-1)));
Assert.assertEquals(-1, bound.compareTo(Bound.atMost(0)));
Assert.assertEquals(1, bound.compareTo(Bound.atMost(-1)));
Assert.assertEquals(1, bound.compareTo(Bound.noneLowerBound()));
// { x | x >= 0}
bound = bound.negate();
Assert.assertEquals((Integer)0, bound.getValue());
Assert.assertEquals(BoundType.CLOSE_LOWER_BOUND, bound.getType());
Assert.assertNotNull(bound.toRange());
}
@Test
public void testAtMost() {
// { x | x <= 0}
Bound<Integer> bound = Bound.atMost(0);
// test
Assert.assertFalse(bound.test(1));
Assert.assertTrue(bound.test(0));
Assert.assertTrue(bound.test(-1));
// getType
Assert.assertEquals(BoundType.CLOSE_UPPER_BOUND, bound.getType());
// getValue
Assert.assertEquals((Integer)0, bound.getValue());
// toString
Assert.assertEquals("0]", bound.descBound());
Assert.assertEquals("{x | x <= 0}", bound.toString());
// compareTo
Assert.assertEquals(0, bound.compareTo(bound));
Assert.assertEquals(-1, bound.compareTo(Bound.noneUpperBound()));
Assert.assertEquals(1, bound.compareTo(Bound.greaterThan(-1)));
Assert.assertEquals(-1, bound.compareTo(Bound.greaterThan(0)));
Assert.assertEquals(1, bound.compareTo(Bound.atMost(-1)));
Assert.assertEquals(1, bound.compareTo(Bound.lessThan(0)));
Assert.assertEquals(1, bound.compareTo(Bound.lessThan(-1)));
Assert.assertEquals(1, bound.compareTo(Bound.noneLowerBound()));
// { x | x > 0}
bound = bound.negate();
Assert.assertEquals((Integer)0, bound.getValue());
Assert.assertEquals(BoundType.OPEN_LOWER_BOUND, bound.getType());
Assert.assertNotNull(bound.toRange());
}
}

View File

@ -0,0 +1,83 @@
package cn.hutool.core.lang.range;
import org.junit.Assert;
import org.junit.Test;
/**
* test for {@link BoundType}
*/
public class BoundTypeTest {
@Test
public void testIsDislocated() {
Assert.assertTrue(BoundType.CLOSE_LOWER_BOUND.isDislocated(BoundType.CLOSE_UPPER_BOUND));
Assert.assertTrue(BoundType.CLOSE_LOWER_BOUND.isDislocated(BoundType.OPEN_UPPER_BOUND));
Assert.assertFalse(BoundType.CLOSE_LOWER_BOUND.isDislocated(BoundType.CLOSE_LOWER_BOUND));
Assert.assertFalse(BoundType.CLOSE_LOWER_BOUND.isDislocated(BoundType.OPEN_LOWER_BOUND));
}
@Test
public void testIsLowerBound() {
Assert.assertFalse(BoundType.CLOSE_UPPER_BOUND.isLowerBound());
Assert.assertFalse(BoundType.OPEN_UPPER_BOUND.isLowerBound());
Assert.assertTrue(BoundType.CLOSE_LOWER_BOUND.isLowerBound());
Assert.assertTrue(BoundType.OPEN_LOWER_BOUND.isLowerBound());
}
@Test
public void testIsUpperBound() {
Assert.assertTrue(BoundType.CLOSE_UPPER_BOUND.isUpperBound());
Assert.assertTrue(BoundType.OPEN_UPPER_BOUND.isUpperBound());
Assert.assertFalse(BoundType.CLOSE_LOWER_BOUND.isUpperBound());
Assert.assertFalse(BoundType.OPEN_LOWER_BOUND.isUpperBound());
}
@Test
public void testIsOpen() {
Assert.assertFalse(BoundType.CLOSE_UPPER_BOUND.isOpen());
Assert.assertTrue(BoundType.OPEN_UPPER_BOUND.isOpen());
Assert.assertFalse(BoundType.CLOSE_LOWER_BOUND.isOpen());
Assert.assertTrue(BoundType.OPEN_LOWER_BOUND.isOpen());
}
@Test
public void testIsClose() {
Assert.assertTrue(BoundType.CLOSE_UPPER_BOUND.isClose());
Assert.assertFalse(BoundType.OPEN_UPPER_BOUND.isClose());
Assert.assertTrue(BoundType.CLOSE_LOWER_BOUND.isClose());
Assert.assertFalse(BoundType.OPEN_LOWER_BOUND.isClose());
}
@Test
public void testNegate() {
Assert.assertEquals(BoundType.CLOSE_UPPER_BOUND, BoundType.OPEN_LOWER_BOUND.negate());
Assert.assertEquals(BoundType.OPEN_UPPER_BOUND, BoundType.CLOSE_LOWER_BOUND.negate());
Assert.assertEquals(BoundType.OPEN_LOWER_BOUND, BoundType.CLOSE_UPPER_BOUND.negate());
Assert.assertEquals(BoundType.CLOSE_LOWER_BOUND, BoundType.OPEN_UPPER_BOUND.negate());
}
@Test
public void testGetSymbol() {
Assert.assertEquals("]", BoundType.CLOSE_UPPER_BOUND.getSymbol());
Assert.assertEquals(")", BoundType.OPEN_UPPER_BOUND.getSymbol());
Assert.assertEquals("[", BoundType.CLOSE_LOWER_BOUND.getSymbol());
Assert.assertEquals("(", BoundType.OPEN_LOWER_BOUND.getSymbol());
}
@Test
public void testGetCode() {
Assert.assertEquals(2, BoundType.CLOSE_UPPER_BOUND.getCode());
Assert.assertEquals(1, BoundType.OPEN_UPPER_BOUND.getCode());
Assert.assertEquals(-1, BoundType.OPEN_LOWER_BOUND.getCode());
Assert.assertEquals(-2, BoundType.CLOSE_LOWER_BOUND.getCode());
}
@Test
public void testGetOperator() {
Assert.assertEquals("<=", BoundType.CLOSE_UPPER_BOUND.getOperator());
Assert.assertEquals("<", BoundType.OPEN_UPPER_BOUND.getOperator());
Assert.assertEquals(">", BoundType.OPEN_LOWER_BOUND.getOperator());
Assert.assertEquals(">=", BoundType.CLOSE_LOWER_BOUND.getOperator());
}
}

View File

@ -0,0 +1,283 @@
package cn.hutool.core.lang.range;
import org.junit.Assert;
import org.junit.Test;
/**
* test for {@link BoundedRange}
*/
public class BoundedRangeTest {
@Test
public void testEquals() {
final BoundedRange<Integer> range = new BoundedRange<>(
Bound.greaterThan(0), Bound.lessThan(10)
);
Assert.assertEquals(range, range);
Assert.assertNotEquals(range, null);
Assert.assertEquals(range, new BoundedRange<>(
Bound.greaterThan(0), Bound.lessThan(10)
));
Assert.assertNotEquals(range, new BoundedRange<>(
Bound.greaterThan(1), Bound.lessThan(10)
));
}
@Test
public void testHashCode() {
final int hasCode = new BoundedRange<>(
Bound.greaterThan(0), Bound.lessThan(10)
).hashCode();
Assert.assertEquals(hasCode, new BoundedRange<>(
Bound.greaterThan(0), Bound.lessThan(10)
).hashCode());
Assert.assertNotEquals(hasCode, new BoundedRange<>(
Bound.greaterThan(1), Bound.lessThan(10)
).hashCode());
}
@Test
public void testAll() {
final BoundedRange<Integer> range = BoundedRange.all();
Assert.assertEquals("(-∞, +∞)", range.toString());
// getBound
Assert.assertFalse(range.hasLowerBound());
Assert.assertFalse(range.hasUpperBound());
Assert.assertNull(range.getLowerBoundValue());
Assert.assertNull(range.getUpperBoundValue());
// test
Assert.assertFalse(range.isEmpty());
Assert.assertTrue(range.test(Integer.MAX_VALUE));
Assert.assertTrue(range.test(Integer.MIN_VALUE));
// isXXX
Assert.assertFalse(range.isDisjoint(BoundedRange.open(0, 5)));
Assert.assertEquals(range, range);
Assert.assertNotEquals(range, BoundedRange.open(0, 5));
Assert.assertTrue(range.isIntersected(BoundedRange.open(0, 5)));
Assert.assertTrue(range.isIntersected(range));
Assert.assertFalse(range.isSubset(BoundedRange.open(0, 5)));
Assert.assertTrue(range.isSubset(range));
Assert.assertFalse(range.isProperSubset(BoundedRange.open(0, 5)));
Assert.assertFalse(range.isProperSubset(range));
Assert.assertTrue(range.isSuperset(BoundedRange.open(0, 5)));
Assert.assertTrue(range.isSuperset(range));
Assert.assertTrue(range.isProperSuperset(BoundedRange.open(0, 5)));
Assert.assertFalse(range.isProperSuperset(range));
// operate
Assert.assertEquals(range, range.unionIfIntersected(BoundedRange.open(0, 5)));
Assert.assertEquals(range, range.span(BoundedRange.open(0, 5)));
Assert.assertNull(range.gap(BoundedRange.open(0, 5)));
Assert.assertEquals(range, range.intersection(range));
Assert.assertEquals(BoundedRange.open(0, 5), range.intersection(BoundedRange.open(0, 5)));
// sub
Assert.assertEquals("(0, +∞)", range.subGreatThan(0).toString());
Assert.assertEquals("[0, +∞)", range.subAtLeast(0).toString());
Assert.assertEquals("(-∞, 0)", range.subLessThan(0).toString());
Assert.assertEquals("(-∞, 0]", range.subAtMost(0).toString());
}
@Test
public void testOpen() {
final BoundedRange<Integer> range = BoundedRange.open(0, 5);
Assert.assertEquals("(0, 5)", range.toString());
// getBound
Assert.assertTrue(range.hasLowerBound());
Assert.assertTrue(range.hasUpperBound());
Assert.assertEquals((Integer)0, range.getLowerBoundValue());
Assert.assertEquals((Integer)5, range.getUpperBoundValue());
// test
Assert.assertFalse(range.isEmpty());
Assert.assertFalse(range.test(6));
Assert.assertFalse(range.test(5));
Assert.assertTrue(range.test(3));
Assert.assertFalse(range.test(0));
Assert.assertFalse(range.test(-1));
// isXXX
Assert.assertEquals(range, range);
Assert.assertTrue(range.isDisjoint(BoundedRange.open(-5, 0))); // (-5, 0)
Assert.assertTrue(range.isDisjoint(BoundedRange.close(-5, 0))); // [-5, 0]
Assert.assertTrue(range.isIntersected(BoundedRange.close(-5, 1))); // [-5, 1]
Assert.assertTrue(range.isSubset(BoundedRange.close(0, 5))); // [0, 5]
Assert.assertTrue(range.isProperSubset(BoundedRange.close(0, 5))); // [0, 5]
Assert.assertFalse(range.isSuperset(BoundedRange.close(0, 5))); // [0, 5]
Assert.assertFalse(range.isProperSuperset(BoundedRange.close(0, 5))); // [0, 5]
// operate
Assert.assertEquals("(0, 10]", range.unionIfIntersected(BoundedRange.close(4, 10)).toString());
Assert.assertEquals("(0, 10)", range.span(BoundedRange.open(9, 10)).toString());
Assert.assertEquals("(-2, 0]", range.gap(BoundedRange.close(-10, -2)).toString());
Assert.assertEquals("(3, 5)", range.intersection(BoundedRange.open(3, 10)).toString());
// sub
Assert.assertEquals("(3, 5)", range.subGreatThan(3).toString());
Assert.assertEquals("[3, 5)", range.subAtLeast(3).toString());
Assert.assertEquals("(0, 3)", range.subLessThan(3).toString());
Assert.assertEquals("(0, 3]", range.subAtMost(3).toString());
}
@Test
public void testClose() {
final BoundedRange<Integer> range = BoundedRange.close(0, 5);
Assert.assertEquals("[0, 5]", range.toString());
// getBound
Assert.assertTrue(range.hasLowerBound());
Assert.assertTrue(range.hasUpperBound());
Assert.assertEquals((Integer)0, range.getLowerBoundValue());
Assert.assertEquals((Integer)5, range.getUpperBoundValue());
// test
Assert.assertFalse(range.isEmpty());
Assert.assertFalse(range.test(6));
Assert.assertTrue(range.test(5));
Assert.assertTrue(range.test(0));
Assert.assertFalse(range.test(-1));
// isXXX
Assert.assertEquals(range, range);
Assert.assertTrue(range.isDisjoint(BoundedRange.open(-5, 0))); // (-5, 0)
Assert.assertTrue(range.isDisjoint(BoundedRange.close(-5, 0))); // [-5, 0]
Assert.assertTrue(range.isIntersected(BoundedRange.open(-5, 1))); // [-5, 1]
Assert.assertFalse(range.isSubset(BoundedRange.open(0, 5))); // (0, 5)
Assert.assertFalse(range.isProperSubset(BoundedRange.open(0, 5))); // (0, 5)
Assert.assertTrue(range.isSuperset(BoundedRange.open(0, 5))); // (0, 5)
Assert.assertTrue(range.isProperSuperset(BoundedRange.open(0, 5))); // (0, 5)
// operate
Assert.assertEquals("[0, 5]", range.unionIfIntersected(BoundedRange.open(5, 10)).toString());
Assert.assertEquals("[0, 10]", range.unionIfIntersected(BoundedRange.close(4, 10)).toString());
Assert.assertEquals("[0, 10)", range.span(BoundedRange.open(9, 10)).toString());
Assert.assertEquals("(-2, 0)", range.gap(BoundedRange.close(-10, -2)).toString());
Assert.assertEquals("(3, 5]", range.intersection(BoundedRange.open(3, 10)).toString());
Assert.assertNull(range.intersection(BoundedRange.open(5, 10)));
// sub
Assert.assertEquals("(3, 5]", range.subGreatThan(3).toString());
Assert.assertEquals("[3, 5]", range.subAtLeast(3).toString());
Assert.assertEquals("[0, 3)", range.subLessThan(3).toString());
Assert.assertEquals("[0, 3]", range.subAtMost(3).toString());
}
@Test
public void testOpenClose() {
final BoundedRange<Integer> range = BoundedRange.openClose(0, 5);
Assert.assertEquals("(0, 5]", range.toString());
// getBound
Assert.assertTrue(range.hasLowerBound());
Assert.assertTrue(range.hasUpperBound());
Assert.assertEquals((Integer)0, range.getLowerBoundValue());
Assert.assertEquals((Integer)5, range.getUpperBoundValue());
// test
Assert.assertFalse(range.isEmpty());
Assert.assertFalse(range.test(6));
Assert.assertTrue(range.test(5));
Assert.assertFalse(range.test(0));
Assert.assertFalse(range.test(-1));
}
@Test
public void testCloseOpen() {
final BoundedRange<Integer> range = BoundedRange.closeOpen(0, 5);
Assert.assertEquals("[0, 5)", range.toString());
// getBound
Assert.assertTrue(range.hasLowerBound());
Assert.assertTrue(range.hasUpperBound());
Assert.assertEquals((Integer)0, range.getLowerBoundValue());
Assert.assertEquals((Integer)5, range.getUpperBoundValue());
// test
Assert.assertFalse(range.isEmpty());
Assert.assertFalse(range.test(6));
Assert.assertFalse(range.test(5));
Assert.assertTrue(range.test(0));
Assert.assertFalse(range.test(-1));
}
@Test
public void testGreatThan() {
final BoundedRange<Integer> range = BoundedRange.greaterThan(0);
Assert.assertEquals("(0, +∞)", range.toString());
// getBound
Assert.assertTrue(range.hasLowerBound());
Assert.assertFalse(range.hasUpperBound());
Assert.assertEquals((Integer)0, range.getLowerBoundValue());
Assert.assertNull(range.getUpperBoundValue());
// test
Assert.assertFalse(range.isEmpty());
Assert.assertTrue(range.test(6));
Assert.assertTrue(range.test(5));
Assert.assertFalse(range.test(0));
Assert.assertFalse(range.test(-1));
}
@Test
public void testAtLeast() {
final BoundedRange<Integer> range = BoundedRange.atLeast(0);
Assert.assertEquals("[0, +∞)", range.toString());
// getBound
Assert.assertTrue(range.hasLowerBound());
Assert.assertFalse(range.hasUpperBound());
Assert.assertEquals((Integer)0, range.getLowerBoundValue());
Assert.assertNull(range.getUpperBoundValue());
// test
Assert.assertFalse(range.isEmpty());
Assert.assertTrue(range.test(6));
Assert.assertTrue(range.test(5));
Assert.assertTrue(range.test(0));
Assert.assertFalse(range.test(-1));
}
@Test
public void testLessThan() {
final BoundedRange<Integer> range = BoundedRange.lessThan(5);
Assert.assertEquals("(-∞, 5)", range.toString());
// getBound
Assert.assertTrue(range.hasUpperBound());
Assert.assertFalse(range.hasLowerBound());
Assert.assertEquals((Integer)5, range.getUpperBoundValue());
Assert.assertNull(range.getLowerBoundValue());
// test
Assert.assertFalse(range.isEmpty());
Assert.assertFalse(range.test(6));
Assert.assertFalse(range.test(5));
Assert.assertTrue(range.test(0));
Assert.assertTrue(range.test(-1));
}
@Test
public void testAtMost() {
final BoundedRange<Integer> range = BoundedRange.atMost(5);
Assert.assertEquals("(-∞, 5]", range.toString());
// getBound
Assert.assertTrue(range.hasUpperBound());
Assert.assertFalse(range.hasLowerBound());
Assert.assertEquals((Integer)5, range.getUpperBoundValue());
Assert.assertNull(range.getLowerBoundValue());
// test
Assert.assertFalse(range.isEmpty());
Assert.assertFalse(range.test(6));
Assert.assertTrue(range.test(5));
Assert.assertTrue(range.test(0));
Assert.assertTrue(range.test(-1));
}
}

View File

@ -1,4 +1,4 @@
package cn.hutool.core.lang;
package cn.hutool.core.lang.range;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateRange;

View File

@ -1,5 +1,6 @@
package cn.hutool.core.map;
import cn.hutool.core.io.SerializeUtil;
import org.junit.Assert;
import org.junit.Test;
@ -20,4 +21,16 @@ public class CamelCaseMapTest {
Assert.assertEquals("OK", map.get("customKey"));
Assert.assertEquals("OK", map.get("custom_key"));
}
@Test
public void serializableKeyFuncTest() {
final CamelCaseMap<String, String> map = new CamelCaseMap<>();
map.put("serializable_key", "OK");
final CamelCaseMap<String, String> deSerializableMap = SerializeUtil.deserialize(SerializeUtil.serialize(map));
Assert.assertEquals("OK", deSerializableMap.get("serializable_key"));
Assert.assertEquals("OK", deSerializableMap.get("serializableKey"));
deSerializableMap.put("serializable_func", "OK");
Assert.assertEquals("OK", deSerializableMap.get("serializable_func"));
Assert.assertEquals("OK", deSerializableMap.get("serializableFunc"));
}
}

View File

@ -16,8 +16,11 @@ public class UrlBuilderTest {
@Test
public void buildTest() {
final String buildUrl = UrlBuilder.of().setHost("www.hutool.cn").build();
final UrlBuilder builder = UrlBuilder.of();
final String buildUrl = builder.setHost("www.hutool.cn").build();
Assert.assertEquals("http://www.hutool.cn/", buildUrl);
Assert.assertEquals(buildUrl, 80, builder.getPortWithDefault());
}
@Test
@ -26,9 +29,11 @@ public class UrlBuilderTest {
String buildUrl = UrlBuilder.of().setScheme("http").setHost("192.168.1.1").setPort(8080).setWithEndTag(false).build();
Assert.assertEquals("http://192.168.1.1:8080", buildUrl);
buildUrl = UrlBuilder.of().setScheme("http").setHost("192.168.1.1").setPort(8080).addQuery("url", "http://192.168.1.1/test/1")
final UrlBuilder urlBuilder = UrlBuilder.of();
buildUrl = urlBuilder.setScheme("http").setHost("192.168.1.1").setPort(8080).addQuery("url", "http://192.168.1.1/test/1")
.setWithEndTag(false).build();
Assert.assertEquals("http://192.168.1.1:8080?url=http://192.168.1.1/test/1", buildUrl);
Assert.assertEquals(buildUrl, 8080, urlBuilder.getPortWithDefault());
}
@Test
@ -459,7 +464,7 @@ public class UrlBuilderTest {
}
@Test
public void getAuthorityTest(){
public void getAuthorityTest() {
final UrlBuilder builder = UrlBuilder.ofHttp("127.0.0.1:8080")
.addQuery("param[0].field", "编码");
@ -467,7 +472,7 @@ public class UrlBuilderTest {
}
@Test
public void addPathTest(){
public void addPathTest() {
//https://gitee.com/dromara/hutool/issues/I5O4ML
UrlBuilder.of().addPath("");
UrlBuilder.of().addPath("/");

View File

@ -181,7 +181,6 @@ public class AbstractEnhancedWrappedStreamTest {
final List<Integer> list = asList(1, 2, 3);
final Map<Boolean, List<Integer>> map = new HashMap<Boolean, List<Integer>>() {
private static final long serialVersionUID = 1L;
{
put(Boolean.TRUE, singletonList(2));
put(Boolean.FALSE, asList(1, 3));
@ -625,7 +624,6 @@ public class AbstractEnhancedWrappedStreamTest {
public void testToEntries() {
final Map<Integer, Integer> expect = new HashMap<Integer, Integer>() {
private static final long serialVersionUID = 1L;
{
put(1, 1);
put(2, 2);

View File

@ -63,6 +63,9 @@ public class ArrayUtilTest {
public void newArrayTest() {
final String[] newArray = ArrayUtil.newArray(String.class, 3);
Assert.assertEquals(3, newArray.length);
final Object[] newArray2 = ArrayUtil.newArray(3);
Assert.assertEquals(3, newArray2.length);
}
@Test
@ -508,10 +511,20 @@ public class ArrayUtilTest {
}
@Test
public void setOrAppendTest(){
String[] arr = new String[0];
String[] newArr = ArrayUtil.setOrAppend(arr, 0, "Good");// ClassCastException
public void setOrAppendTest() {
final String[] arr = new String[0];
final String[] newArr = ArrayUtil.setOrAppend(arr, 0, "Good");// ClassCastException
Assert.assertArrayEquals(new String[]{"Good"}, newArr);
// 非空数组替换第一个元素
int[] arr2 = new int[]{1};
int[] o = ArrayUtil.setOrAppend(arr2, 0, 2);
Assert.assertArrayEquals(new int[]{2}, o);
// 空数组追加
arr2 = new int[0];
o = ArrayUtil.setOrAppend(arr2, 0, 2);
Assert.assertArrayEquals(new int[]{2}, o);
}
@Test
@ -522,4 +535,46 @@ public class ArrayUtilTest {
final String[] c = {"d", "e"};
Assert.assertTrue(ArrayUtil.containsAll(c, resultO[0], resultO[1]));
}
@Test
public void hasNonNullTest() {
String[] a = {null, "e"};
Assert.assertTrue(ArrayUtil.hasNonNull(a));
a = new String[]{null, null};
Assert.assertFalse(ArrayUtil.hasNonNull(a));
a = new String[]{"", null};
Assert.assertTrue(ArrayUtil.hasNonNull(a));
a = new String[]{null};
Assert.assertFalse(ArrayUtil.hasNonNull(a));
a = new String[]{};
Assert.assertFalse(ArrayUtil.hasNonNull(a));
a = null;
Assert.assertFalse(ArrayUtil.hasNonNull(a));
}
@Test
public void isAllNullTest() {
String[] a = {null, "e"};
Assert.assertFalse(ArrayUtil.isAllNull(a));
a = new String[]{null, null};
Assert.assertTrue(ArrayUtil.isAllNull(a));
a = new String[]{"", null};
Assert.assertFalse(ArrayUtil.isAllNull(a));
a = new String[]{null};
Assert.assertTrue(ArrayUtil.isAllNull(a));
a = new String[]{};
Assert.assertTrue(ArrayUtil.isAllNull(a));
a = null;
Assert.assertTrue(ArrayUtil.isAllNull(a));
}
}

View File

@ -55,4 +55,13 @@ public class CharUtilTest {
Assert.assertEquals('⑫', CharUtil.toCloseByNumber(12));
Assert.assertEquals('⑳', CharUtil.toCloseByNumber(20));
}
@Test
public void issueI5UGSQTest(){
char c = '\u3164';
Assert.assertTrue(CharUtil.isBlankChar(c));
c = '\u2800';
Assert.assertTrue(CharUtil.isBlankChar(c));
}
}

View File

@ -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.7.2</spring-boot.version>
<spring-boot.version>2.7.4</spring-boot.version>
<cglib.version>3.3.0</cglib.version>
</properties>
@ -430,7 +430,7 @@
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>5.3.1</version>
<version>5.3.2</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
@ -465,7 +465,7 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.22</version>
<version>5.3.23</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
@ -476,6 +476,13 @@
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>QLExpress</artifactId>
<version>3.3.0</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<!-- 表达式引擎可选依赖 end -->
<dependency>
<groupId>org.apache.commons</groupId>

View File

@ -0,0 +1,38 @@
package cn.hutool.extra.expression.engine.qlexpress;
import cn.hutool.extra.expression.ExpressionEngine;
import cn.hutool.extra.expression.ExpressionException;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import java.util.Map;
/**
* QLExpress引擎封装<br>
* https://github.com/alibaba/QLExpress
*
* @author looly
* @since 5.8.9
*/
public class QLExpressEngine implements ExpressionEngine {
private final ExpressRunner engine;
/**
* 构造
*/
public QLExpressEngine() {
engine = new ExpressRunner();
}
@Override
public Object eval(final String expression, final Map<String, Object> context) {
final DefaultContext<String, Object> defaultContext = new DefaultContext<>();
defaultContext.putAll(context);
try {
return engine.execute(expression, defaultContext, null, true, false);
} catch (final Exception e) {
throw new ExpressionException(e);
}
}
}

View File

@ -0,0 +1,7 @@
/**
* QLExpress引擎封装<br>
* https://github.com/alibaba/QLExpress
*
* @author looly
*/
package cn.hutool.extra.expression.engine.qlexpress;

View File

@ -3,4 +3,5 @@ cn.hutool.extra.expression.engine.jexl.JexlEngine
cn.hutool.extra.expression.engine.mvel.MvelEngine
cn.hutool.extra.expression.engine.jfireel.JfireELEngine
cn.hutool.extra.expression.engine.spel.SpELEngine
cn.hutool.extra.expression.engine.rhino.RhinoEngine
cn.hutool.extra.expression.engine.rhino.RhinoEngine
cn.hutool.extra.expression.engine.qlexpress.QLExpressEngine

View File

@ -4,6 +4,7 @@ import cn.hutool.core.map.Dict;
import cn.hutool.extra.expression.engine.jexl.JexlEngine;
import cn.hutool.extra.expression.engine.jfireel.JfireELEngine;
import cn.hutool.extra.expression.engine.mvel.MvelEngine;
import cn.hutool.extra.expression.engine.qlexpress.QLExpressEngine;
import cn.hutool.extra.expression.engine.rhino.RhinoEngine;
import cn.hutool.extra.expression.engine.spel.SpELEngine;
import org.junit.Assert;
@ -95,4 +96,16 @@ public class ExpressionUtilTest {
Assert.assertEquals(-143.8, (double)eval, 0);
}
@Test
public void qlExpressTest(){
final ExpressionEngine engine = new QLExpressEngine();
final Dict dict = Dict.of()
.set("a", 100.3)
.set("b", 45)
.set("c", -199.100);
final Object eval = engine.eval("a-(b-c)", dict);
Assert.assertEquals(-143.8, (double)eval, 0);
}
}

View File

@ -193,7 +193,7 @@ public class DownloadTest {
}
@Test
//@Ignore
@Ignore
public void downloadTeamViewerTest() throws IOException {
// 此URL有3次重定向, 需要请求4次
final String url = "https://download.teamviewer.com/download/TeamViewer_Setup_x64.exe";

View File

@ -1,12 +1,13 @@
package cn.hutool.http;
import cn.hutool.core.lang.Console;
import org.junit.Ignore;
import org.junit.Test;
public class IssueI5TPSYTest {
@Test
//@Ignore
@Ignore
public void redirectTest() {
final String url = "https://bsxt.gdzwfw.gov.cn/UnifiedReporting/auth/newIndex";
final HttpResponse res = HttpUtil.createGet(url).setFollowRedirects(true)

View File

@ -9,25 +9,26 @@ import cn.hutool.core.map.CaseInsensitiveLinkedMap;
import cn.hutool.core.map.CaseInsensitiveTreeMap;
import cn.hutool.core.math.NumberUtil;
import cn.hutool.core.reflect.ClassUtil;
import cn.hutool.core.reflect.ConstructorUtil;
import cn.hutool.core.reflect.TypeUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.json.serialize.GlobalSerializeMapping;
import cn.hutool.json.serialize.JSONDeserializer;
import cn.hutool.json.serialize.JSONString;
import cn.hutool.json.writer.GlobalValueWriterMapping;
import cn.hutool.json.writer.JSONValueWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.time.temporal.TemporalAccessor;
import java.util.Calendar;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.*;
import java.util.function.Predicate;
/**
@ -56,9 +57,11 @@ public final class InternalJSONUtil {
* @return 包装后的值null表示此值需被忽略
*/
static Object wrap(final Object object, final JSONConfig jsonConfig) {
if (object == null) {
return null;
// null和自定义对象原样存储
if (null == object || null != InternalJSONUtil.getValueWriter(object)) {
return object;
}
if (object instanceof JSON //
|| object instanceof JSONString //
|| object instanceof CharSequence //
@ -369,7 +372,44 @@ public final class InternalJSONUtil {
return rawHashMap;
}
/**
* 根据值类型获取{@link JSONValueWriter}首先判断对象是否实现了{@link JSONValueWriter}接口<br>
* 如果未实现从{@link GlobalValueWriterMapping}中查找全局的writer否则返回null
*
* @param value
* @param <T> 值类型
* @return {@link JSONValueWriter}
*/
@SuppressWarnings("unchecked")
public static <T> JSONValueWriter<T> getValueWriter(final T value) {
if (value instanceof JSONValueWriter) {
return (JSONValueWriter<T>) value;
}
// 全局自定义序列化支持null的自定义写出
return (JSONValueWriter<T>) GlobalValueWriterMapping.get(null == value ? null : value.getClass());
}
/**
* 根据目标类型获取对应的{@link JSONDeserializer}首先判断是否实现了{@link JSONDeserializer}接口<br>
* 如果未实现从{@link GlobalSerializeMapping}中查找全局的{@link JSONDeserializer}否则返回null
*
* @param targetType 目标类型
* @param <T> 目标类型
* @return {@link JSONDeserializer}
*/
@SuppressWarnings("unchecked")
public static <T> JSONDeserializer<T> getDeserializer(final Type targetType) {
final Class<T> rawType = (Class<T>) TypeUtil.getClass(targetType);
if (null != rawType && JSONDeserializer.class.isAssignableFrom(rawType)) {
return (JSONDeserializer<T>) ConstructorUtil.newInstanceIfPossible(rawType);
}
// 全局自定义反序列化优先级低于实现JSONDeserializer接口
return (JSONDeserializer<T>) GlobalSerializeMapping.getDeserializer(targetType);
}
// --------------------------------------------------------------------------------------------- Private method start
/**
* 对所有双引号做转义处理使用双反斜杠做转义<br>
* 为了能在HTML中较好的显示会将&lt;/转义为&lt;\/<br>

View File

@ -1,19 +1,11 @@
package cn.hutool.json;
import cn.hutool.core.comparator.CompareUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.convert.Converter;
import cn.hutool.core.convert.impl.DateConverter;
import cn.hutool.core.convert.impl.TemporalAccessorConverter;
import cn.hutool.core.reflect.TypeUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.json.convert.JSONConverter;
import cn.hutool.json.serialize.JSONString;
import java.io.Serializable;
import java.time.temporal.TemporalAccessor;
import java.util.Comparator;
import java.util.Date;
/**
* JSON配置项
@ -59,35 +51,7 @@ public class JSONConfig implements Serializable {
/**
* 自定义的类型转换器用于在getXXX操作中自动转换类型
*/
private Converter converter = (type, value)->{
if(null == value){
return null;
}
if(value instanceof JSONString){
// 被JSONString包装的对象获取其原始类型
value = ((JSONString) value).getRaw();
}
final Class<?> rawType = TypeUtil.getClass(type);
if(null == rawType){
return value;
}
if(JSON.class.isAssignableFrom(rawType)){
return JSONConverter.INSTANCE.toJSON(value);
}
if(Date.class.isAssignableFrom(rawType) || TemporalAccessor.class.isAssignableFrom(rawType)){
// 日期转换支持自定义日期格式
final String format = getDateFormat();
if (StrUtil.isNotBlank(format)) {
if (Date.class.isAssignableFrom(rawType)) {
return new DateConverter(format).convert(type, value);
} else {
return new TemporalAccessorConverter(format).convert(type, value);
}
}
}
return Convert.convertWithCheck(type, value, null, isIgnoreError());
};
private Converter converter = JSONConverter.of(this);
/**
* 创建默认的配置项

View File

@ -2,6 +2,7 @@ package cn.hutool.json;
import cn.hutool.core.lang.mutable.Mutable;
import cn.hutool.core.lang.mutable.MutableEntry;
import cn.hutool.core.util.CharUtil;
import java.util.function.Predicate;
@ -80,7 +81,7 @@ public class JSONParser {
switch (tokener.nextClean()) {
case ';':
case ',':
case CharUtil.COMMA:
if (tokener.nextClean() == '}') {
// issue#2380
// 尾后逗号Trailing CommasJSON中虽然不支持但是ECMAScript 2017支持此处做兼容
@ -111,7 +112,7 @@ public class JSONParser {
if (x.nextClean() != ']') {
x.back();
for (; ; ) {
if (x.nextClean() == ',') {
if (x.nextClean() == CharUtil.COMMA) {
x.back();
jsonArray.addRaw(null, predicate);
} else {
@ -119,7 +120,7 @@ public class JSONParser {
jsonArray.addRaw(x.nextValue(), predicate);
}
switch (x.nextClean()) {
case ',':
case CharUtil.COMMA:
if (x.nextClean() == ']') {
return;
}

View File

@ -71,6 +71,7 @@ public class JSONTokener {
*
* @param inputStream InputStream
* @param config JSON配置
* @throws JSONException JSON异常包装IO异常
*/
public JSONTokener(final InputStream inputStream, final JSONConfig config) throws JSONException {
this(IoUtil.getUtf8Reader(inputStream), config);
@ -89,6 +90,8 @@ public class JSONTokener {
/**
* 将标记回退到第一个字符重新开始解析新的JSON
*
* @throws JSONException JSON异常包装IO异常
*/
public void back() throws JSONException {
if (this.usePrevious || this.index <= 0) {
@ -111,6 +114,7 @@ public class JSONTokener {
* 源字符串是否有更多的字符
*
* @return 如果未达到结尾返回true否则false
* @throws JSONException JSON异常包装IO异常
*/
public boolean more() throws JSONException {
this.next();
@ -272,11 +276,11 @@ public class JSONTokener {
}
/**
* Get the text up but not including the specified character or the end of line, whichever comes first. <br>
* 获得从当前位置直到分隔符不包括分隔符或行尾的的所有字符
*
* @param delimiter 分隔符
* @return 字符串
* @throws JSONException JSON异常包装IO异常
*/
public String nextTo(final char delimiter) throws JSONException {
final StringBuilder sb = new StringBuilder();
@ -297,6 +301,7 @@ public class JSONTokener {
*
* @param delimiters A set of delimiter characters.
* @return A string, trimmed.
* @throws JSONException JSON异常包装IO异常
*/
public String nextTo(final String delimiters) throws JSONException {
char c;
@ -314,9 +319,9 @@ public class JSONTokener {
}
/**
* 获得下一个值值类型可以是Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the JSONObject.NULL
* 获得下一个值值类型可以是Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
*
* @return Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the JSONObject.NULL
* @return Boolean, Double, Integer, JSONArray, JSONObject, Long, or String
* @throws JSONException 语法错误
*/
public Object nextValue() throws JSONException {
@ -360,6 +365,7 @@ public class JSONTokener {
*
* @param to 需要定位的字符
* @return 定位的字符如果字符未找到返回0
* @throws JSONException IO异常
*/
public char skipTo(final char to) throws JSONException {
char c;
@ -378,8 +384,8 @@ public class JSONTokener {
return c;
}
} while (c != to);
} catch (final IOException exception) {
throw new JSONException(exception);
} catch (final IOException e) {
throw new JSONException(e);
}
this.back();
return c;
@ -396,43 +402,6 @@ public class JSONTokener {
return new JSONException(message + this);
}
/**
* 转为 {@link JSONArray}
*
* @return {@link JSONArray}
*/
public JSONArray toJSONArray() {
final JSONArray jsonArray = new JSONArray(this.config);
if (this.nextClean() != '[') {
throw this.syntaxError("A JSONArray text must start with '['");
}
if (this.nextClean() != ']') {
this.back();
while (true) {
if (this.nextClean() == ',') {
this.back();
jsonArray.add(null);
} else {
this.back();
jsonArray.add(this.nextValue());
}
switch (this.nextClean()) {
case ',':
if (this.nextClean() == ']') {
return jsonArray;
}
this.back();
break;
case ']':
return jsonArray;
default:
throw this.syntaxError("Expected a ',' or ']'");
}
}
}
return jsonArray;
}
/**
* Make a printable string of this JSONTokener.
*

View File

@ -10,12 +10,11 @@ import cn.hutool.json.serialize.GlobalSerializeMapping;
import cn.hutool.json.serialize.JSONArraySerializer;
import cn.hutool.json.serialize.JSONDeserializer;
import cn.hutool.json.serialize.JSONObjectSerializer;
import cn.hutool.json.writer.JSONValueWriter;
import cn.hutool.json.writer.JSONWriter;
import cn.hutool.json.xml.JSONXMLUtil;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.io.*;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.List;
@ -296,7 +295,18 @@ public class JSONUtil {
* @return JSON字符串
* @since 5.7.12
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static String toJsonStr(final Object obj, final JSONConfig jsonConfig) {
// 自定义规则优先级高于全局规则
final JSONValueWriter valueWriter = InternalJSONUtil.getValueWriter(obj);
if(null != valueWriter){
final StringWriter stringWriter = new StringWriter();
final JSONWriter jsonWriter = JSONWriter.of(stringWriter, 0, 0, null);
// 用户对象自定义实现了JSONValueWriter接口理解为需要自定义输出
valueWriter.write(jsonWriter, obj);
return stringWriter.toString();
}
if (null == obj) {
return null;
}

View File

@ -2,39 +2,33 @@ package cn.hutool.json.convert;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.BeanCopier;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.convert.ConvertException;
import cn.hutool.core.convert.Converter;
import cn.hutool.core.convert.RegisterConverter;
import cn.hutool.core.convert.impl.ArrayConverter;
import cn.hutool.core.convert.impl.CollectionConverter;
import cn.hutool.core.convert.impl.MapConverter;
import cn.hutool.core.convert.impl.*;
import cn.hutool.core.map.MapWrapper;
import cn.hutool.core.reflect.ConstructorUtil;
import cn.hutool.core.reflect.TypeReference;
import cn.hutool.core.reflect.TypeUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.json.InternalJSONUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONException;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.json.serialize.GlobalSerializeMapping;
import cn.hutool.json.*;
import cn.hutool.json.serialize.JSONDeserializer;
import cn.hutool.json.serialize.JSONString;
import java.lang.reflect.Type;
import java.time.temporal.TemporalAccessor;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
/**
* JSON转换器实现Object对象转换为{@link JSON}支持的对象
* <ul>
* <li>String: 转换为相应的对象</li>
* <li>ArrayIterableIterator转换为JSONArray</li>
* <li>Bean对象转为JSONObject</li>
* <li>任意支持的对象转换为JSON</li>
* <li>JSOn转换为指定对象Bean</li>
* </ul>
*
* @author looly
@ -73,27 +67,40 @@ public class JSONConverter implements Converter {
}
@Override
public Object convert(Type targetType, final Object obj) throws ConvertException {
if (null == obj) {
public Object convert(Type targetType, Object value) throws ConvertException {
if (null == value) {
return null;
}
// 对象转JSON
if (targetType instanceof JSON) {
return toJSON(obj);
if (value instanceof JSONString) {
// 被JSONString包装的对象获取其原始类型
value = ((JSONString) value).getRaw();
}
// JSON转对象
if (obj instanceof JSON) {
if (value instanceof JSON) {
if (targetType instanceof TypeReference) {
// 还原原始类型
targetType = ((TypeReference<?>) targetType).getType();
}
return toBean(targetType, (JSON) obj);
return toBean(targetType, (JSON) value);
}
// 无法转换
throw new JSONException("Can not convert from {}: [{}] to [{}]",
obj.getClass().getName(), obj, targetType.getTypeName());
// 对象转JSON
final Class<?> targetClass = TypeUtil.getClass(targetType);
if(null != targetClass){
if (JSON.class.isAssignableFrom(targetClass)) {
return toJSON(value);
}
// 自定义日期格式
if(Date.class.isAssignableFrom(targetClass) || TemporalAccessor.class.isAssignableFrom(targetClass)){
final Object date = toDateWithFormat(targetClass, value);
if(null != date){
return date;
}
}
}
return Convert.convertWithCheck(targetType, value, null, config.isIgnoreError());
}
/**
@ -127,22 +134,30 @@ public class JSONConverter implements Converter {
return json;
}
// ----------------------------------------------------------- Private method start
/**
* JSON转Bean
*
* @param <T> 目标类型
* @param targetType 目标类型
* @param json JSON
* @return bean
*/
@SuppressWarnings("unchecked")
private <T> T toBean(final Type targetType, final JSON json) {
final Class<T> rawType = (Class<T>) TypeUtil.getClass(targetType);
if(null != rawType && JSONDeserializer.class.isAssignableFrom(rawType)){
return (T) JSONDeserializerConverter.INSTANCE.convert(targetType, json);
}
// 全局自定义反序列化优先级低于实现JSONDeserializer接口
final JSONDeserializer<?> deserializer = GlobalSerializeMapping.getDeserializer(targetType);
// 自定义对象反序列化
final JSONDeserializer<Object> deserializer = InternalJSONUtil.getDeserializer(targetType);
if (null != deserializer) {
return (T) deserializer.deserialize(json);
}
// 其他转换不支持非Class的泛型类型
final Class<T> rawType = (Class<T>) TypeUtil.getClass(targetType);
if (null == rawType) {
throw new JSONException("Can not get class from type: {}", targetType);
// 当目标类型不确定时返回原JSON
return (T) json;
//throw new JSONException("Can not get class from type: {}", targetType);
}
// 特殊类型转换包括CollectionMap强转Array等
final T result = toSpecial(targetType, rawType, json);
@ -164,7 +179,7 @@ public class JSONConverter implements Converter {
}
// 跳过异常时返回null
if(json.getConfig().isIgnoreError()){
if (json.getConfig().isIgnoreError()) {
return null;
}
@ -173,8 +188,6 @@ public class JSONConverter implements Converter {
json.getClass().getName(), json, targetType.getTypeName());
}
// ----------------------------------------------------------- Private method start
/**
* 特殊类型转换<br>
* 包括
@ -220,5 +233,18 @@ public class JSONConverter implements Converter {
// 表示非需要特殊转换的对象
return null;
}
private Object toDateWithFormat(final Class<?> targetClass, final Object value){
// 日期转换支持自定义日期格式
final String format = config.getDateFormat();
if (StrUtil.isNotBlank(format)) {
if (Date.class.isAssignableFrom(targetClass)) {
return new DateConverter(format).convert(targetClass, value);
} else {
return new TemporalAccessorConverter(format).convert(targetClass, value);
}
}
return null;
}
// ----------------------------------------------------------- Private method end
}

View File

@ -1,36 +0,0 @@
package cn.hutool.json.convert;
import cn.hutool.core.convert.AbstractConverter;
import cn.hutool.core.convert.ConvertException;
import cn.hutool.core.reflect.ConstructorUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.serialize.JSONDeserializer;
/**
* 实现了{@link JSONDeserializer}接口的Bean对象转换器用于将指定JSON转换为JSONDeserializer子对象
*
* @author looly
* @since 6.0.0
*/
public class JSONDeserializerConverter extends AbstractConverter {
private static final long serialVersionUID = 1L;
/**
* 单例
*/
public static final JSONDeserializerConverter INSTANCE = new JSONDeserializerConverter();
@Override
protected Object convertInternal(final Class<?> targetClass, final Object value) {
// 自定义反序列化
if (value instanceof JSON) {
final JSONDeserializer<?> target = (JSONDeserializer<?>) ConstructorUtil.newInstanceIfPossible(targetClass);
if (null == target) {
throw new ConvertException("Can not instance target: [{}]", targetClass);
}
return target.deserialize((JSON) value);
}
throw new ConvertException("JSONDeserializer bean must be convert from JSON!");
}
}

View File

@ -1,6 +1,8 @@
package cn.hutool.json.serialize;
import cn.hutool.core.map.SafeConcurrentHashMap;
import cn.hutool.core.reflect.NullType;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.json.JSON;
import java.lang.reflect.Type;
@ -59,19 +61,6 @@ public class GlobalSerializeMapping {
putInternal(type, serializer);
}
/**
* 加入自定义的序列化器
*
* @param type 对象类型
* @param serializer 序列化器实现
*/
synchronized private static void putInternal(final Type type, final JSONSerializer<? extends JSON, ?> serializer) {
if (null == serializerMap) {
serializerMap = new ConcurrentHashMap<>();
}
serializerMap.put(type, serializer);
}
/**
* 加入自定义的反序列化器
*
@ -82,7 +71,7 @@ public class GlobalSerializeMapping {
if (null == deserializerMap) {
deserializerMap = new ConcurrentHashMap<>();
}
deserializerMap.put(type, deserializer);
deserializerMap.put(ObjUtil.defaultIfNull(type, NullType.INSTANCE), deserializer);
}
/**
@ -95,7 +84,7 @@ public class GlobalSerializeMapping {
if (null == serializerMap) {
return null;
}
return serializerMap.get(type);
return serializerMap.get(ObjUtil.defaultIfNull(type, NullType.INSTANCE));
}
/**
@ -108,6 +97,19 @@ public class GlobalSerializeMapping {
if (null == deserializerMap) {
return null;
}
return deserializerMap.get(type);
return deserializerMap.get(ObjUtil.defaultIfNull(type, NullType.INSTANCE));
}
/**
* 加入自定义的序列化器
*
* @param type 对象类型
* @param serializer 序列化器实现
*/
synchronized private static void putInternal(final Type type, final JSONSerializer<? extends JSON, ?> serializer) {
if (null == serializerMap) {
serializerMap = new ConcurrentHashMap<>();
}
serializerMap.put(ObjUtil.defaultIfNull(type, NullType.INSTANCE), serializer);
}
}

View File

@ -0,0 +1,4 @@
/**
* JSON序列化和反序列化提供对象和JSON之间的转换
*/
package cn.hutool.json.serialize;

View File

@ -0,0 +1,44 @@
package cn.hutool.json.writer;
import cn.hutool.core.map.SafeConcurrentHashMap;
import cn.hutool.core.reflect.NullType;
import cn.hutool.core.util.ObjUtil;
import java.lang.reflect.Type;
import java.util.Map;
/**
* 全局自定义对象写出<br>
* 用户通过此全局定义可针对某些特殊对象
*
* @author looly
* @since 6.0.0
*/
public class GlobalValueWriterMapping {
private static final Map<Type, JSONValueWriter<?>> writerMap;
static {
writerMap = new SafeConcurrentHashMap<>();
}
/**
* 加入自定义的对象值写出规则
*
* @param type 对象类型
* @param writer 自定义对象写出实现
*/
public static void put(final Type type, final JSONValueWriter<?> writer) {
writerMap.put(ObjUtil.defaultIfNull(type, NullType.INSTANCE), writer);
}
/**
* 获取自定义对象值写出规则
*
* @param type 对象类型
* @return 自定义的 {@link JSONValueWriter}
*/
public static JSONValueWriter<?> get(final Type type) {
return writerMap.get(ObjUtil.defaultIfNull(type, NullType.INSTANCE));
}
}

View File

@ -4,6 +4,7 @@ import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.lang.mutable.MutableEntry;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.json.InternalJSONUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONConfig;
@ -76,7 +77,7 @@ public class JSONWriter extends Writer {
this.writer = writer;
this.indentFactor = indentFactor;
this.indent = indent;
this.config = config;
this.config = ObjUtil.defaultIfNull(config, JSONConfig.of());
}
/**
@ -298,12 +299,21 @@ public class JSONWriter extends Writer {
* @param predicate 过滤修改器
* @return this
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private JSONWriter writeObjValue(final Object value, final Predicate<MutableEntry<Object, Object>> predicate) {
final int indent = indentFactor + this.indent;
// 自定义规则
final JSONValueWriter valueWriter = InternalJSONUtil.getValueWriter(value);
if(null != valueWriter){
valueWriter.write(this, value);
return this;
}
if (value == null) {
//noinspection resource
writeRaw(StrUtil.NULL);
} else if (value instanceof JSON) {
}else if (value instanceof JSON) {
((JSON) value).write(writer, indentFactor, indent, predicate);
} else if (value instanceof Number) {
NumberValueWriter.INSTANCE.write(this, (Number) value);

View File

@ -0,0 +1,67 @@
package cn.hutool.json.writer;
import cn.hutool.core.convert.Converter;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONUtil;
import lombok.Data;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class GlobalValueWriterMappingTest {
@Before
public void init(){
GlobalValueWriterMapping.put(CustomSubBean.class, (JSONValueWriter<CustomSubBean>) (writer, value) -> {
writer.writeRaw(String.valueOf(value.getId()));
});
}
@Test
public void customWriteTest(){
final CustomSubBean customBean = new CustomSubBean();
customBean.setId(12);
customBean.setName("aaa");
final String s = JSONUtil.toJsonStr(customBean);
Assert.assertEquals("12", s);
}
@Test
public void customWriteSubTest(){
final CustomSubBean customSubBean = new CustomSubBean();
customSubBean.setId(12);
customSubBean.setName("aaa");
final CustomBean customBean = new CustomBean();
customBean.setId(1);
customBean.setSub(customSubBean);
final String s = JSONUtil.toJsonStr(customBean);
Assert.assertEquals("{\"id\":1,\"sub\":12}", s);
// 自定义转换
final JSONConfig jsonConfig = JSONConfig.of();
final Converter converter = jsonConfig.getConverter();
jsonConfig.setConverter((targetType, value) -> {
if(targetType == CustomSubBean.class){
final CustomSubBean subBean = new CustomSubBean();
subBean.setId((Integer) value);
return subBean;
}
return converter.convert(targetType, value);
});
final CustomBean customBean1 = JSONUtil.parseObj(s, jsonConfig).toBean(CustomBean.class);
Assert.assertEquals(12, customBean1.getSub().getId());
}
@Data
static class CustomSubBean {
private int id;
private String name;
}
@Data
static class CustomBean{
private int id;
private CustomSubBean sub;
}
}

View File

@ -466,10 +466,7 @@ public class CellUtil {
* @since 5.4.5
*/
private static Cell getCellIfMergedRegion(final Sheet sheet, final int x, final int y) {
final int sheetMergeCount = sheet.getNumMergedRegions();
CellRangeAddress ca;
for (int i = 0; i < sheetMergeCount; i++) {
ca = sheet.getMergedRegion(i);
for (final CellRangeAddress ca : sheet.getMergedRegions()) {
if (ca.isInRange(y, x)) {
return SheetUtil.getCell(sheet, ca.getFirstRow(), ca.getFirstColumn());
}

View File

@ -2,7 +2,7 @@ package cn.hutool.swing.img;
import cn.hutool.core.io.FileTypeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.net.URLUtil;
import cn.hutool.core.net.url.URLUtil;
import org.junit.Ignore;
import org.junit.Test;