mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
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:
commit
b47b531b4c
@ -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考虑添加缓存支持,增加最大和最小范围判断,减少遍历
|
||||
|
||||
### ❌不兼容特性
|
||||
|
||||
|
@ -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);
|
||||
// 非覆盖模式下,如果目标值存在,则跳过
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -126,7 +126,7 @@ public class DateUtil extends CalendarUtil {
|
||||
|
||||
/**
|
||||
* Long类型时间转为{@link DateTime}<br>
|
||||
* 只支持毫秒级别时间戳,如果需要秒级别时间戳,请自行×1000
|
||||
* 只支持毫秒级别时间戳,如果需要秒级别时间戳,请自行×1000L
|
||||
*
|
||||
* @param date Long类型Date(Unix时间戳)
|
||||
* @return 时间对象
|
||||
|
@ -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--;
|
||||
}
|
||||
|
188
hutool-core/src/main/java/cn/hutool/core/lang/range/Bound.java
Normal file
188
hutool-core/src/main/java/cn/hutool/core/lang/range/Bound.java
Normal 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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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}";
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
@ -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;
|
@ -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());
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 > 参数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 >= 参数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 < 参数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<=参数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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -259,7 +259,11 @@ public class CharUtil implements CharPool {
|
||||
|| Character.isSpaceChar(c)
|
||||
|| c == '\ufeff'
|
||||
|| c == '\u202a'
|
||||
|| c == '\u0000';
|
||||
|| c == '\u0000'
|
||||
// issue#I5UGSQ,Hangul Filler
|
||||
|| c == '\u3164'
|
||||
// Braille Pattern Blank
|
||||
|| c == '\u2800';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2612,7 +2612,7 @@ public class PrimitiveArrayUtil {
|
||||
return array;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------- shuffle
|
||||
// ---------------------------------------------------------------------- swap
|
||||
|
||||
/**
|
||||
* 交换数组中两个位置的值
|
||||
@ -2766,17 +2766,7 @@ public class PrimitiveArrayUtil {
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数组是否升序,即array[i] <= 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] <= array[i+1],若传入空数组,则返回false
|
||||
@ -2822,18 +2812,6 @@ public class PrimitiveArrayUtil {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数组是否升序,即array[i] <= 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] <= array[i+1],若传入空数组,则返回false
|
||||
*
|
||||
@ -2878,18 +2856,6 @@ public class PrimitiveArrayUtil {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数组是否升序,即array[i] <= 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] <= array[i+1],若传入空数组,则返回false
|
||||
*
|
||||
@ -2934,18 +2900,6 @@ public class PrimitiveArrayUtil {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数组是否升序,即array[i] <= 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] <= array[i+1],若传入空数组,则返回false
|
||||
*
|
||||
@ -2990,18 +2944,6 @@ public class PrimitiveArrayUtil {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数组是否升序,即array[i] <= 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] <= array[i+1],若传入空数组,则返回false
|
||||
*
|
||||
@ -3046,18 +2988,6 @@ public class PrimitiveArrayUtil {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数组是否升序,即array[i] <= 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] <= array[i+1],若传入空数组,则返回false
|
||||
*
|
||||
@ -3102,18 +3032,6 @@ public class PrimitiveArrayUtil {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数组是否升序,即array[i] <= 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] <= array[i+1],若传入空数组,则返回false
|
||||
*
|
||||
|
@ -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();
|
||||
|
55
hutool-core/src/test/java/cn/hutool/core/bean/Issue2649Test.java
Executable file
55
hutool-core/src/test/java/cn/hutool/core/bean/Issue2649Test.java
Executable 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<>();
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
@ -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("/");
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* QLExpress引擎封装<br>
|
||||
* 见:https://github.com/alibaba/QLExpress
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
package cn.hutool.extra.expression.engine.qlexpress;
|
@ -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
|
||||
|
@ -0,0 +1 @@
|
||||
cn.hutool.extra.spring.SpringUtil
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -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)
|
||||
|
@ -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中较好的显示,会将</转义为<\/<br>
|
||||
|
@ -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);
|
||||
|
||||
/**
|
||||
* 创建默认的配置项
|
||||
|
@ -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 Commas),JSON中虽然不支持,但是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;
|
||||
}
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>Array、Iterable、Iterator:转换为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);
|
||||
}
|
||||
// 特殊类型转换,包括Collection、Map、强转、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
|
||||
}
|
||||
|
@ -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!");
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* JSON序列化和反序列化,提供对象和JSON之间的转换
|
||||
*/
|
||||
package cn.hutool.json.serialize;
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user