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

This commit is contained in:
zhangrenhua 2022-07-21 15:22:35 +08:00
commit 0da4339f51
16 changed files with 587 additions and 112 deletions

View File

@ -3,7 +3,7 @@
-------------------------------------------------------------------------------------------------------------
# 5.8.5.M1 (2022-07-17)
# 5.8.5.M1 (2022-07-20)
### ❌不兼容特性
* 【core 】 合成注解相关功能重构,增加@Link及其子注解pr#702@Gitee
@ -25,6 +25,7 @@
* 【core 】 ReUtil增加getAllGroups重载pr#2455@Github
* 【core 】 PageUtil#totalPage增加totalCount为long类型的重载方法pr#2442@Github
* 【crypto 】 PemUtil.readPemPrivateKey支持pkcs#1格式增加OpensslKeyUtilpr#2456@Github
* 【core 】 添加了通用的注解扫描器 `GenericAnnotationScanner`,并在 `AnnotationScanner` 接口中统一提供了提前配置好的扫描器静态实例pr#715@Github
*
### 🐞Bug修复
* 【core 】 修复CollUtil里面关于可变参数传null造成的crash问题pr#2428@Github
@ -32,6 +33,9 @@
* 【core 】 修复当时间戳为Integer时时间转换问题pr#2449@Github
* 【core 】 修复bmp文件判断问题issue#I5H93G@Gitee
* 【core 】 修复CombinationAnnotationElement造成递归循环issue#I5FQGW@Gitee
* 【core 】 修复Dict缺少putIfAbsent、computeIfAbsent问题issue#I5FQGW@Gitee
* 【core 】 修复Console.log应该把异常信息输出位置错误问题pr#716@Gitee
* 【core 】 修复UrlBuilder无法配置末尾追加“/”问题issue#2459@Github
-------------------------------------------------------------------------------------------------------------

View File

@ -1,6 +1,9 @@
package cn.hutool.core.annotation;
import cn.hutool.core.annotation.scanner.*;
import cn.hutool.core.annotation.scanner.AnnotationScanner;
import cn.hutool.core.annotation.scanner.MetaAnnotationScanner;
import cn.hutool.core.annotation.scanner.MethodAnnotationScanner;
import cn.hutool.core.annotation.scanner.TypeAnnotationScanner;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.lang.Opt;
@ -334,7 +337,7 @@ public class AnnotationUtil {
* @see MetaAnnotationScanner
*/
public static List<Annotation> scanMetaAnnotation(Class<? extends Annotation> annotationType) {
return new MetaAnnotationScanner().getIfSupport(annotationType);
return AnnotationScanner.DIRECTLY_AND_META_ANNOTATION.getAnnotationsIfSupport(annotationType);
}
/**
@ -363,7 +366,7 @@ public class AnnotationUtil {
* @see TypeAnnotationScanner
*/
public static List<Annotation> scanClass(Class<?> targetClass) {
return new TypeAnnotationScanner().getIfSupport(targetClass);
return AnnotationScanner.TYPE_HIERARCHY.getAnnotationsIfSupport(targetClass);
}
/**
@ -391,7 +394,7 @@ public class AnnotationUtil {
* @see MethodAnnotationScanner
*/
public static List<Annotation> scanMethod(Method method) {
return new MethodAnnotationScanner(true).getIfSupport(method);
return AnnotationScanner.TYPE_HIERARCHY.getAnnotationsIfSupport(method);
}
/**
@ -478,14 +481,12 @@ public class AnnotationUtil {
if (ObjectUtil.isNotNull(target)) {
return target;
}
AnnotationScanner[] scanners = new AnnotationScanner[]{
new MetaAnnotationScanner(), new TypeAnnotationScanner(), new MethodAnnotationScanner(), new FieldAnnotationScanner()
};
return AnnotationScanner.scanByAnySupported(annotatedEle, scanners).stream()
.map(annotation -> getSynthesizedAnnotation(annotationType, annotation))
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
return AnnotationScanner.DIRECTLY
.getAnnotationsIfSupport(annotatedEle).stream()
.map(annotation -> getSynthesizedAnnotation(annotationType, annotation))
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}
/**
@ -511,10 +512,8 @@ public class AnnotationUtil {
* @see SynthesizedAggregateAnnotation
*/
public static <T extends Annotation> List<T> getAllSynthesizedAnnotations(AnnotatedElement annotatedEle, Class<T> annotationType) {
AnnotationScanner[] scanners = new AnnotationScanner[]{
new MetaAnnotationScanner(), new TypeAnnotationScanner(), new MethodAnnotationScanner(), new FieldAnnotationScanner()
};
return AnnotationScanner.scanByAnySupported(annotatedEle, scanners).stream()
return AnnotationScanner.DIRECTLY
.getAnnotationsIfSupport(annotatedEle).stream()
.map(annotation -> getSynthesizedAnnotation(annotationType, annotation))
.filter(Objects::nonNull)
.collect(Collectors.toList());
@ -527,7 +526,7 @@ public class AnnotationUtil {
* @return 聚合注解
*/
public static SynthesizedAggregateAnnotation aggregatingFromAnnotation(Annotation... annotations) {
return new GenericSynthesizedAggregateAnnotation(Arrays.asList(annotations), EmptyAnnotationScanner.INSTANCE);
return new GenericSynthesizedAggregateAnnotation(Arrays.asList(annotations), AnnotationScanner.NOTHING);
}
/**
@ -537,7 +536,7 @@ public class AnnotationUtil {
* @return 聚合注解
*/
public static SynthesizedAggregateAnnotation aggregatingFromAnnotationWithMeta(Annotation... annotations) {
return new GenericSynthesizedAggregateAnnotation(Arrays.asList(annotations), new MetaAnnotationScanner());
return new GenericSynthesizedAggregateAnnotation(Arrays.asList(annotations), AnnotationScanner.DIRECTLY_AND_META_ANNOTATION);
}
/**

View File

@ -60,9 +60,9 @@ public abstract class AbstractTypeAnnotationScanner<T extends AbstractTypeAnnota
* 构造一个类注解扫描器
*
* @param includeSuperClass 是否允许扫描父类
* @param includeInterfaces 是否允许扫描父接口
* @param filter 过滤器
* @param excludeTypes 不包含的类型
* @param includeInterfaces 是否允许扫描父接口
* @param filter 过滤器
* @param excludeTypes 不包含的类型
*/
@SuppressWarnings("unchecked")
protected AbstractTypeAnnotationScanner(boolean includeSuperClass, boolean includeInterfaces, Predicate<Class<?>> filter, Set<Class<?>> excludeTypes) {
@ -186,7 +186,7 @@ public abstract class AbstractTypeAnnotationScanner<T extends AbstractTypeAnnota
// 处理层级索引和注解
final Annotation[] targetAnnotations = getAnnotationsFromTargetClass(annotatedEle, index, targetClass);
for (final Annotation annotation : targetAnnotations) {
if (AnnotationUtil.isNotJdkMateAnnotation(annotation.annotationType()) || filter.test(annotation)) {
if (AnnotationUtil.isNotJdkMateAnnotation(annotation.annotationType()) && filter.test(annotation)) {
consumer.accept(index, annotation);
}
}

View File

@ -5,6 +5,7 @@ import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.reflect.AnnotatedElement;
import java.util.ArrayList;
import java.util.Collection;
@ -16,16 +17,120 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 注解扫描器用于从支持的可注解元素上获取所需注解
* <p>注解扫描器用于从支持的可注解元素上获取所需注解
*
* <p>默认提供了以下扫描方式
* <ul>
* <li>{@link #NOTHING}什么都不做什么注解都不扫描</li>
* <li>{@link #DIRECTLY}扫描元素本身直接声明的注解包括父类带有{@link Inherited}被传递到元素上的注解</li>
* <li>
* {@link #DIRECTLY_AND_META_ANNOTATION}扫描元素本身直接声明的注解包括父类带有{@link Inherited}被传递到元素上的注解
* 以及这些注解的元注解
* </li>
* <li>{@link #SUPERCLASS}扫描元素本身以及父类的层级结构中声明的注解</li>
* <li>{@link #SUPERCLASS_AND_META_ANNOTATION}扫描元素本身以及父类的层级结构中声明的注解以及这些注解的元注解</li>
* <li>{@link #INTERFACE}扫描元素本身以及父接口的层级结构中声明的注解</li>
* <li>{@link #INTERFACE_AND_META_ANNOTATION}扫描元素本身以及父接口的层级结构中声明的注解以及这些注解的元注解</li>
* <li>{@link #TYPE_HIERARCHY}扫描元素本身以及父类父接口的层级结构中声明的注解</li>
* <li>{@link #TYPE_HIERARCHY_AND_META_ANNOTATION}扫描元素本身以及父接口父接口的层级结构中声明的注解以及这些注解的元注解</li>
* </ul>
*
* @author huangchengxing
* @see TypeAnnotationScanner
* @see MethodAnnotationScanner
* @see FieldAnnotationScanner
* @see MetaAnnotationScanner
* @see ElementAnnotationScanner
* @see GenericAnnotationScanner
*/
public interface AnnotationScanner {
// ============================ 预置的扫描器实例 ============================
/**
* 不扫描任何注解
*/
AnnotationScanner NOTHING = new EmptyAnnotationScanner();
/**
* 扫描元素本身直接声明的注解包括父类带有{@link Inherited}被传递到元素上的注解的扫描器
*/
AnnotationScanner DIRECTLY = new GenericAnnotationScanner(false, false, false);
/**
* 扫描元素本身直接声明的注解包括父类带有{@link Inherited}被传递到元素上的注解以及这些注解的元注解的扫描器
*/
AnnotationScanner DIRECTLY_AND_META_ANNOTATION = new GenericAnnotationScanner(true, false, false);
/**
* 扫描元素本身以及父类的层级结构中声明的注解的扫描器
*/
AnnotationScanner SUPERCLASS = new GenericAnnotationScanner(false, true, false);
/**
* 扫描元素本身以及父类的层级结构中声明的注解以及这些注解的元注解的扫描器
*/
AnnotationScanner SUPERCLASS_AND_META_ANNOTATION = new GenericAnnotationScanner(true, true, false);
/**
* 扫描元素本身以及父接口的层级结构中声明的注解的扫描器
*/
AnnotationScanner INTERFACE = new GenericAnnotationScanner(false, false, true);
/**
* 扫描元素本身以及父接口的层级结构中声明的注解以及这些注解的元注解的扫描器
*/
AnnotationScanner INTERFACE_AND_META_ANNOTATION = new GenericAnnotationScanner(true, false, true);
/**
* 扫描元素本身以及父类父接口的层级结构中声明的注解的扫描器
*/
AnnotationScanner TYPE_HIERARCHY = new GenericAnnotationScanner(false, true, true);
/**
* 扫描元素本身以及父接口父接口的层级结构中声明的注解以及这些注解的元注解的扫描器
*/
AnnotationScanner TYPE_HIERARCHY_AND_META_ANNOTATION = new GenericAnnotationScanner(true, true, true);
// ============================ 静态方法 ============================
/**
* 给定一组扫描器使用第一个支持处理该类型元素的扫描器获取元素上可能存在的注解
*
* @param annotatedEle {@link AnnotatedElement}可以是ClassMethodFieldConstructorReflectPermission
* @param scanners 注解扫描器
* @return 注解
*/
static List<Annotation> scanByAnySupported(AnnotatedElement annotatedEle, AnnotationScanner... scanners) {
if (ObjectUtil.isNull(annotatedEle) && ArrayUtil.isNotEmpty(scanners)) {
return Collections.emptyList();
}
return Stream.of(scanners)
.filter(scanner -> scanner.support(annotatedEle))
.findFirst()
.map(scanner -> scanner.getAnnotations(annotatedEle))
.orElseGet(Collections::emptyList);
}
/**
* 根据指定的扫描器扫描元素上可能存在的注解
*
* @param annotatedEle {@link AnnotatedElement}可以是ClassMethodFieldConstructorReflectPermission
* @param scanners 注解扫描器
* @return 注解
*/
static List<Annotation> scanByAllSupported(AnnotatedElement annotatedEle, AnnotationScanner... scanners) {
if (ObjectUtil.isNull(annotatedEle) && ArrayUtil.isNotEmpty(scanners)) {
return Collections.emptyList();
}
return Stream.of(scanners)
.map(scanner -> scanner.getAnnotationsIfSupport(annotatedEle))
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
// ============================ 抽象方法 ============================
/**
* 判断是否支持扫描该注解元素
*
@ -56,7 +161,7 @@ public interface AnnotationScanner {
* @param annotatedEle {@link AnnotatedElement}可以是ClassMethodFieldConstructorReflectPermission
* @return 注解
*/
default List<Annotation> getIfSupport(AnnotatedElement annotatedEle) {
default List<Annotation> getAnnotationsIfSupport(AnnotatedElement annotatedEle) {
return support(annotatedEle) ? getAnnotations(annotatedEle) : Collections.emptyList();
}
@ -90,39 +195,4 @@ public interface AnnotationScanner {
}
}
/**
* 给定一组扫描器使用第一个支持处理该类型元素的扫描器获取元素上可能存在的注解
*
* @param annotatedEle {@link AnnotatedElement}可以是ClassMethodFieldConstructorReflectPermission
* @param scanners 注解扫描器
* @return 注解
*/
static List<Annotation> scanByAnySupported(AnnotatedElement annotatedEle, AnnotationScanner... scanners) {
if (ObjectUtil.isNull(annotatedEle) && ArrayUtil.isNotEmpty(scanners)) {
return Collections.emptyList();
}
return Stream.of(scanners)
.filter(scanner -> scanner.support(annotatedEle))
.findFirst()
.map(scanner -> scanner.getAnnotations(annotatedEle))
.orElseGet(Collections::emptyList);
}
/**
* 根据指定的扫描器扫描元素上可能存在的注解
*
* @param annotatedEle {@link AnnotatedElement}可以是ClassMethodFieldConstructorReflectPermission
* @param scanners 注解扫描器
* @return 注解
*/
static List<Annotation> scanByAllScanner(AnnotatedElement annotatedEle, AnnotationScanner... scanners) {
if (ObjectUtil.isNull(annotatedEle) && ArrayUtil.isNotEmpty(scanners)) {
return Collections.emptyList();
}
return Stream.of(scanners)
.map(scanner -> scanner.getIfSupport(annotatedEle))
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,44 @@
package cn.hutool.core.annotation.scanner;
import cn.hutool.core.util.ObjectUtil;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* 扫描{@link AnnotatedElement}上的注解不支持处理层级对象
*
* @author huangchengxing
*/
public class ElementAnnotationScanner implements AnnotationScanner {
/**
* 判断是否支持扫描该注解元素仅当注解元素不为空时返回{@code true}
*
* @param annotatedEle {@link AnnotatedElement}可以是ClassMethodFieldConstructorReflectPermission
* @return 是否支持扫描该注解元素
*/
@Override
public boolean support(AnnotatedElement annotatedEle) {
return ObjectUtil.isNotNull(annotatedEle);
}
/**
* 扫描{@link AnnotatedElement}上直接声明的注解调用前需要确保调用{@link #support(AnnotatedElement)}返回为true
*
* @param consumer 对获取到的注解和注解对应的层级索引的处理
* @param annotatedEle {@link AnnotatedElement}可以是ClassMethodFieldConstructorReflectPermission
* @param filter 注解过滤器无法通过过滤器的注解不会被处理该参数允许为空
*/
@Override
public void scan(BiConsumer<Integer, Annotation> consumer, AnnotatedElement annotatedEle, Predicate<Annotation> filter) {
filter = ObjectUtil.defaultIfNull(filter, t -> true);
Stream.of(annotatedEle.getAnnotations())
.filter(filter)
.forEach(annotation -> consumer.accept(0, annotation));
}
}

View File

@ -14,8 +14,6 @@ import java.util.function.Predicate;
*/
public class EmptyAnnotationScanner implements AnnotationScanner {
public static final EmptyAnnotationScanner INSTANCE = new EmptyAnnotationScanner();
@Override
public boolean support(AnnotatedElement annotatedEle) {
return true;

View File

@ -0,0 +1,149 @@
package cn.hutool.core.annotation.scanner;
import cn.hutool.core.map.multi.ListValueMap;
import cn.hutool.core.util.ObjectUtil;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
/**
* <p>通用注解扫描器支持按不同的层级结构扫描{@link AnnotatedElement}上的注解
*
* <p>{@link AnnotatedElement}类型不同时层级结构指向的对象将有所区别
* <ul>
* <li>
* 当元素为{@link Method}此处层级结构指声明方法的类的层级结构
* 扫描器将从层级结构中寻找与该方法签名相同的方法并对其进行扫描
* </li>
* <li>
* 当元素为{@link Class}此处层级结构即指类本身与其父类父接口共同构成的层级结构
* 扫描器将扫描层级结构中类接口声明的注解
* </li>
* <li>当元素不为{@link Method}{@link Class}则其层级结构仅有其本身一层</li>
* </ul>
* 此外扫描器支持在获取到层级结构中的注解对象后再对注解对象的元注解进行扫描
*
* @author huangchengxing
* @see TypeAnnotationScanner
* @see MethodAnnotationScanner
* @see MetaAnnotationScanner
* @see ElementAnnotationScanner
*/
public class GenericAnnotationScanner implements AnnotationScanner {
/**
* 类型扫描器
*/
private final AnnotationScanner typeScanner;
/**
* 方法扫描器
*/
private final AnnotationScanner methodScanner;
/**
* 元注解扫描器
*/
private final AnnotationScanner metaScanner;
/**
* 普通元素扫描器
*/
private final AnnotationScanner elementScanner;
/**
* 通用注解扫描器支持扫描所有类型的{@link AnnotatedElement}
*
* @param annotatedEle {@link AnnotatedElement}可以是ClassMethodFieldConstructorReflectPermission
* @return 是否支持扫描该注解元素
*/
@Override
public boolean support(AnnotatedElement annotatedEle) {
return true;
}
/**
* 构造一个通用注解扫描器
*
* @param enableScanMetaAnnotation 是否扫描注解上的元注解
* @param enableScanSupperClass 是否扫描父类
* @param enableScanSupperInterface 是否扫描父接口
*/
public GenericAnnotationScanner(
boolean enableScanMetaAnnotation,
boolean enableScanSupperClass,
boolean enableScanSupperInterface) {
this.metaScanner = enableScanMetaAnnotation ? new MetaAnnotationScanner() : new EmptyAnnotationScanner();
this.typeScanner = new TypeAnnotationScanner(
enableScanSupperClass, enableScanSupperInterface, a -> true, Collections.emptySet()
);
this.methodScanner = new MethodAnnotationScanner(
enableScanSupperClass, enableScanSupperInterface, a -> true, Collections.emptySet()
);
this.elementScanner = new ElementAnnotationScanner();
}
/**
* 扫描注解元素的层级结构若存在然后对获取到的注解和注解对应的层级索引进行处理
*
* @param consumer 对获取到的注解和注解对应的层级索引的处理
* @param annotatedEle {@link AnnotatedElement}可以是ClassMethodFieldConstructorReflectPermission
* @param filter 注解过滤器无法通过过滤器的注解不会被处理该参数允许为空
*/
@Override
public void scan(BiConsumer<Integer, Annotation> consumer, AnnotatedElement annotatedEle, Predicate<Annotation> filter) {
filter = ObjectUtil.defaultIfNull(filter, t -> true);
if (ObjectUtil.isNull(annotatedEle)) {
return;
}
// 注解元素是类
if (annotatedEle instanceof Class) {
scanElements(typeScanner, consumer, annotatedEle, filter);
}
// 注解元素是方法
else if (annotatedEle instanceof Method) {
scanElements(methodScanner, consumer, annotatedEle, filter);
}
// 注解元素是其他类型
else {
scanElements(elementScanner, consumer, annotatedEle, filter);
}
}
/**
* 扫描注解类的层级结构若存在然后对获取到的注解和注解对应的层级索引进行处理
*
* @param scanner 使用的扫描器
* @param consumer 对获取到的注解和注解对应的层级索引的处理
* @param annotatedEle {@link AnnotatedElement}可以是ClassMethodFieldConstructorReflectPermission
* @param filter 注解过滤器无法通过过滤器的注解不会被处理该参数允许为空
*/
private void scanElements(
AnnotationScanner scanner,
BiConsumer<Integer, Annotation> consumer,
AnnotatedElement annotatedEle,
Predicate<Annotation> filter) {
// 扫描类上注解
final ListValueMap<Integer, Annotation> classAnnotations = new ListValueMap<>(new LinkedHashMap<>());
scanner.scan((index, annotation) -> {
if (filter.test(annotation)) {
classAnnotations.putValue(index, annotation);
}
}, annotatedEle, filter);
// 扫描元注解
classAnnotations.forEach((index, annotations) ->
annotations.forEach(annotation -> {
consumer.accept(index, annotation);
metaScanner.scan(consumer, annotation.annotationType(), filter);
})
);
}
}

View File

@ -20,14 +20,10 @@ import java.util.stream.Stream;
public class MethodAnnotationScanner extends AbstractTypeAnnotationScanner<MethodAnnotationScanner> implements AnnotationScanner {
/**
* 构造一个方法注解扫描器
*
* @param scanSameSignatureMethod 是否扫描类层级结构中具有相同方法签名的方法
* @param filter 过滤器
* @param excludeTypes 不包含的类型
* 构造一个类注解扫描器仅扫描该方法上直接声明的注解
*/
public MethodAnnotationScanner(boolean scanSameSignatureMethod, Predicate<Class<?>> filter, Set<Class<?>> excludeTypes) {
super(scanSameSignatureMethod, scanSameSignatureMethod, filter, excludeTypes);
public MethodAnnotationScanner() {
this(false);
}
/**
@ -40,10 +36,26 @@ public class MethodAnnotationScanner extends AbstractTypeAnnotationScanner<Metho
}
/**
* 构造一个类注解扫描器仅扫描该方法上直接声明的注解
* 构造一个方法注解扫描器
*
* @param scanSameSignatureMethod 是否扫描类层级结构中具有相同方法签名的方法
* @param filter 过滤器
* @param excludeTypes 不包含的类型
*/
public MethodAnnotationScanner() {
this(false);
public MethodAnnotationScanner(boolean scanSameSignatureMethod, Predicate<Class<?>> filter, Set<Class<?>> excludeTypes) {
super(scanSameSignatureMethod, scanSameSignatureMethod, filter, excludeTypes);
}
/**
* 构造一个方法注解扫描器
*
* @param includeSuperClass 是否允许扫描父类中具有相同方法签名的方法
* @param includeInterfaces 是否允许扫描父接口中具有相同方法签名的方法
* @param filter 过滤器
* @param excludeTypes 不包含的类型
*/
public MethodAnnotationScanner(boolean includeSuperClass, boolean includeInterfaces, Predicate<Class<?>> filter, Set<Class<?>> excludeTypes) {
super(includeSuperClass, includeInterfaces, filter, excludeTypes);
}
/**

View File

@ -86,7 +86,7 @@ public class Console {
out.println(StrUtil.format(template, values));
if (null != t) {
//noinspection CallToPrintStackTrace
t.printStackTrace();
t.printStackTrace(out);
out.flush();
}
}

View File

@ -20,6 +20,7 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* 字典对象扩充了HashMap中的方法
@ -633,6 +634,17 @@ public class Dict extends LinkedHashMap<String, Object> implements BasicTypeGett
public Object merge(final String key, final Object value, final BiFunction<? super Object, ? super Object, ?> remappingFunction) {
return super.merge(customKey(key), value, remappingFunction);
}
@Override
public Object putIfAbsent(String key, Object value) {
return super.putIfAbsent(customKey(key), value);
}
@Override
public Object computeIfAbsent(String key, Function<? super String, ?> mappingFunction) {
return super.computeIfAbsent(customKey(key), mappingFunction);
}
//---------------------------------------------------------------------------- Override default methods end
/**

View File

@ -2,6 +2,7 @@ package cn.hutool.core.map;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
/**
@ -98,6 +99,16 @@ public abstract class TransMap<K, V> extends MapWrapper<K, V> {
public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
return super.merge(customKey(key), customValue(value), (v1, v2) -> remappingFunction.apply(customValue(v1), customValue(v2)));
}
@Override
public V putIfAbsent(K key, V value) {
return super.putIfAbsent(customKey(key), customValue(value));
}
@Override
public V computeIfAbsent(final K key, final Function<? super K, ? extends V> mappingFunction) {
return super.computeIfAbsent(customKey(key), mappingFunction);
}
//---------------------------------------------------------------------------- Override default methods end
/**

View File

@ -119,7 +119,7 @@ public final class UrlBuilder implements Builder<String> {
/**
* 使用URL字符串构建UrlBuilder默认使用UTF-8编码
*
* @param url URL字符串
* @param url URL字符串
* @return UrlBuilder
*/
public static UrlBuilder of(String url) {
@ -318,6 +318,22 @@ public final class UrlBuilder implements Builder<String> {
return null == this.path ? StrUtil.SLASH : this.path.build(charset, this.needEncodePercent);
}
/**
* 是否path的末尾加 /
*
* @param withEngTag 是否path的末尾加 /
* @return this
* @since 5.8.5
*/
public UrlBuilder setWithEndTag(boolean withEngTag) {
if (null == this.path) {
this.path = new UrlPath();
}
this.path.setWithEndTag(withEngTag);
return this;
}
/**
* 设置路径例如/aa/bb/cc将覆盖之前所有的path相关设置
*
@ -501,7 +517,7 @@ public final class UrlBuilder implements Builder<String> {
final StringBuilder fileBuilder = new StringBuilder();
// path
fileBuilder.append(StrUtil.blankToDefault(getPathStr(), StrUtil.SLASH));
fileBuilder.append(getPathStr());
// query
final String query = getQueryStr();

View File

@ -142,12 +142,13 @@ public class UrlPath {
*/
public String build(Charset charset, boolean encodePercent) {
if (CollUtil.isEmpty(this.segments)) {
return StrUtil.EMPTY;
// 没有节点的path取决于是否末尾追加/如果不追加返回空串否则返回/
return withEngTag ? StrUtil.SLASH : StrUtil.EMPTY;
}
final char[] safeChars = encodePercent ? null : new char[]{'%'};
final StringBuilder builder = new StringBuilder();
for (String segment : segments) {
for (final String segment : segments) {
if(builder.length() == 0){
// 根据https://www.ietf.org/rfc/rfc3986.html#section-3.3定义
// path的第一部分不允许有":"其余部分允许
@ -157,12 +158,15 @@ public class UrlPath {
builder.append(CharUtil.SLASH).append(RFC3986.SEGMENT.encode(segment, charset, safeChars));
}
}
if (StrUtil.isEmpty(builder)) {
// 空白追加是保证以/开头
builder.append(CharUtil.SLASH);
}else if (withEngTag && false == StrUtil.endWith(builder, CharUtil.SLASH)) {
// 尾部没有/则追加否则不追加
builder.append(CharUtil.SLASH);
if(withEngTag){
if (StrUtil.isEmpty(builder)) {
// 空白追加是保证以/开头
builder.append(CharUtil.SLASH);
}else if (false == StrUtil.endWith(builder, CharUtil.SLASH)) {
// 尾部没有/则追加否则不追加
builder.append(CharUtil.SLASH);
}
}
return builder.toString();

View File

@ -0,0 +1,60 @@
package cn.hutool.core.annotation.scanner;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import org.junit.Assert;
import org.junit.Test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ElementAnnotationScannerTest {
@Test
public void supportTest() {
final ElementAnnotationScanner scanner = new ElementAnnotationScanner();
Assert.assertTrue(scanner.support(ReflectUtil.getField(FieldAnnotationScannerTest.Example.class, "id")));
Assert.assertTrue(scanner.support(ReflectUtil.getMethod(FieldAnnotationScannerTest.Example.class, "getId")));
Assert.assertFalse(scanner.support(null));
Assert.assertTrue(scanner.support(FieldAnnotationScannerTest.Example.class));
}
@Test
public void getAnnotationsTest() {
final ElementAnnotationScanner scanner = new ElementAnnotationScanner();
final Field field = ReflectUtil.getField(FieldAnnotationScannerTest.Example.class, "id");
Assert.assertNotNull(field);
Assert.assertTrue(scanner.support(field));
List<Annotation> annotations = scanner.getAnnotations(field);
Assert.assertEquals(1, annotations.size());
Assert.assertEquals(AnnotationForScannerTest.class, CollUtil.getFirst(annotations).annotationType());
}
@Test
public void scanTest() {
final ElementAnnotationScanner scanner = new ElementAnnotationScanner();
final Field field = ReflectUtil.getField(FieldAnnotationScannerTest.Example.class, "id");
final Map<Integer, List<Annotation>> map = new HashMap<>();
scanner.scan(
(index, annotation) -> map.computeIfAbsent(index, i -> new ArrayList<>()).add(annotation),
field, null
);
Assert.assertEquals(1, map.size());
Assert.assertEquals(1, map.get(0).size());
Assert.assertEquals(AnnotationForScannerTest.class, map.get(0).get(0).annotationType());
}
public static class Example {
@AnnotationForScannerTest
private Integer id;
public Integer getId() {
return id;
}
}
}

View File

@ -0,0 +1,85 @@
package cn.hutool.core.annotation.scanner;
import org.junit.Assert;
import org.junit.Test;
import java.lang.annotation.*;
import java.util.List;
public class GenericAnnotationScannerTest {
@Test
public void scanDirectlyTest() {
final GenericAnnotationScanner scanner = new GenericAnnotationScanner(false, false, false);
final List<Annotation> annotations = scanner.getAnnotations(ClassForTest.class);
Assert.assertEquals(1, annotations.size());
}
@Test
public void scanDirectlyAndMetaAnnotationTest() {
final GenericAnnotationScanner scanner = new GenericAnnotationScanner(true, false, false);
final List<Annotation> annotations = scanner.getAnnotations(ClassForTest.class);
Assert.assertEquals(2, annotations.size());
}
@Test
public void scanSuperclassTest() {
final GenericAnnotationScanner scanner = new GenericAnnotationScanner(false, true, false);
final List<Annotation> annotations = scanner.getAnnotations(ClassForTest.class);
Assert.assertEquals(2, annotations.size());
}
@Test
public void scanSuperclassAndMetaAnnotationTest() {
final GenericAnnotationScanner scanner = new GenericAnnotationScanner(true, true, false);
final List<Annotation> annotations = scanner.getAnnotations(ClassForTest.class);
Assert.assertEquals(4, annotations.size());
}
@Test
public void scanInterfaceTest() {
final GenericAnnotationScanner scanner = new GenericAnnotationScanner(false, false, true);
final List<Annotation> annotations = scanner.getAnnotations(ClassForTest.class);
Assert.assertEquals(2, annotations.size());
}
@Test
public void scanInterfaceAndMetaAnnotationTest() {
final GenericAnnotationScanner scanner = new GenericAnnotationScanner(true, false, true);
final List<Annotation> annotations = scanner.getAnnotations(ClassForTest.class);
Assert.assertEquals(4, annotations.size());
}
@Test
public void scanTypeHierarchyTest() {
final GenericAnnotationScanner scanner = new GenericAnnotationScanner(false, true, true);
final List<Annotation> annotations = scanner.getAnnotations(ClassForTest.class);
Assert.assertEquals(3, annotations.size());
}
@Test
public void scanTypeHierarchyAndMetaAnnotationTest() {
final GenericAnnotationScanner scanner = new GenericAnnotationScanner(true, true, true);
final List<Annotation> annotations = scanner.getAnnotations(ClassForTest.class);
Assert.assertEquals(6, annotations.size());
}
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MetaAnnotationForTest { }
@MetaAnnotationForTest
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationForTest { }
@AnnotationForTest
static class ClassForTest extends SupperForTest implements InterfaceForTest { }
@AnnotationForTest
static class SupperForTest { }
@AnnotationForTest
interface InterfaceForTest { }
}

View File

@ -16,20 +16,31 @@ public class UrlBuilderTest {
@Test
public void buildTest() {
String buildUrl = UrlBuilder.create().setHost("www.hutool.cn").build();
final String buildUrl = UrlBuilder.create().setHost("www.hutool.cn").build();
Assert.assertEquals("http://www.hutool.cn/", buildUrl);
}
@Test
public void buildWithoutSlashTest(){
// https://github.com/dromara/hutool/issues/2459
String buildUrl = UrlBuilder.create().setScheme("http").setHost("192.168.1.1").setPort(8080).setWithEndTag(false).build();
Assert.assertEquals("http://192.168.1.1:8080", buildUrl);
buildUrl = UrlBuilder.create().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);
}
@Test
public void buildTest2() {
// path中的+不做处理
String buildUrl = UrlBuilder.ofHttp("http://www.hutool.cn/+8618888888888", CharsetUtil.CHARSET_UTF_8).build();
final String buildUrl = UrlBuilder.ofHttp("http://www.hutool.cn/+8618888888888", CharsetUtil.CHARSET_UTF_8).build();
Assert.assertEquals("http://www.hutool.cn/+8618888888888", buildUrl);
}
@Test
public void testHost() {
String buildUrl = UrlBuilder.create()
final String buildUrl = UrlBuilder.create()
.setScheme("https")
.setHost("www.hutool.cn").build();
Assert.assertEquals("https://www.hutool.cn/", buildUrl);
@ -37,7 +48,7 @@ public class UrlBuilderTest {
@Test
public void testHostPort() {
String buildUrl = UrlBuilder.create()
final String buildUrl = UrlBuilder.create()
.setScheme("https")
.setHost("www.hutool.cn")
.setPort(8080)
@ -87,7 +98,7 @@ public class UrlBuilderTest {
@Test
public void testFragment() {
String buildUrl = new UrlBuilder()
final String buildUrl = new UrlBuilder()
.setScheme("https")
.setHost("www.hutool.cn")
.setFragment("abc").build();
@ -96,7 +107,7 @@ public class UrlBuilderTest {
@Test
public void testChineseFragment() {
String buildUrl = new UrlBuilder()
final String buildUrl = new UrlBuilder()
.setScheme("https")
.setHost("www.hutool.cn")
.setFragment("测试").build();
@ -105,7 +116,7 @@ public class UrlBuilderTest {
@Test
public void testChineseFragmentWithPath() {
String buildUrl = new UrlBuilder()
final String buildUrl = new UrlBuilder()
.setScheme("https")
.setHost("www.hutool.cn")
.addPath("/s")
@ -115,7 +126,7 @@ public class UrlBuilderTest {
@Test
public void testChineseFragmentWithPathAndQuery() {
String buildUrl = new UrlBuilder()
final String buildUrl = new UrlBuilder()
.setScheme("https")
.setHost("www.hutool.cn")
.addPath("/s")
@ -194,7 +205,7 @@ public class UrlBuilderTest {
@Test
public void weixinUrlTest(){
String urlStr = "https://mp.weixin.qq.com/s?" +
final String urlStr = "https://mp.weixin.qq.com/s?" +
"__biz=MzI5NjkyNTIxMg==" +
"&amp;mid=100000465" +
"&amp;idx=1" +
@ -240,14 +251,14 @@ public class UrlBuilderTest {
@Test
public void toURITest() throws URISyntaxException {
String webUrl = "http://exmple.com/patha/pathb?a=123"; // 报错数据
final String webUrl = "http://exmple.com/patha/pathb?a=123"; // 报错数据
final UrlBuilder urlBuilder = UrlBuilder.of(webUrl, StandardCharsets.UTF_8);
Assert.assertEquals(new URI(webUrl), urlBuilder.toURI());
}
@Test
public void testEncodeInQuery() {
String webUrl = "http://exmple.com/patha/pathb?a=123&b=4?6&c=789"; // b=4?6 参数中有未编码的
final String webUrl = "http://exmple.com/patha/pathb?a=123&b=4?6&c=789"; // b=4?6 参数中有未编码的
final UrlBuilder urlBuilder = UrlBuilder.of(webUrl, StandardCharsets.UTF_8);
Assert.assertEquals("a=123&b=4?6&c=789", urlBuilder.getQueryStr());
}
@ -271,11 +282,11 @@ public class UrlBuilderTest {
@Test
public void gimg2Test(){
String url = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1114%2F0H320120Z3%2F200H3120Z3-6-1200.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1621996490&t=8c384c2823ea453da15a1b9cd5183eea";
final String url = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1114%2F0H320120Z3%2F200H3120Z3-6-1200.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1621996490&t=8c384c2823ea453da15a1b9cd5183eea";
final UrlBuilder urlBuilder = UrlBuilder.of(url);
// PATH除了第一个path外:是允许的
String url2 = "https://gimg2.baidu.com/image_search/src=http:%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1114%2F0H320120Z3%2F200H3120Z3-6-1200.jpg&refer=http:%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1621996490&t=8c384c2823ea453da15a1b9cd5183eea";
final String url2 = "https://gimg2.baidu.com/image_search/src=http:%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1114%2F0H320120Z3%2F200H3120Z3-6-1200.jpg&refer=http:%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1621996490&t=8c384c2823ea453da15a1b9cd5183eea";
Assert.assertEquals(url2, urlBuilder.toString());
}
@ -283,7 +294,7 @@ public class UrlBuilderTest {
public void fragmentEncodeTest(){
// https://gitee.com/dromara/hutool/issues/I49KAL
// https://stackoverflow.com/questions/26088849/url-fragment-allowed-characters
String url = "https://hutool.cn/docs/#/?id=简介";
final String url = "https://hutool.cn/docs/#/?id=简介";
UrlBuilder urlBuilder = UrlBuilder.ofHttp(url);
Assert.assertEquals("https://hutool.cn/docs/#/?id=%E7%AE%80%E4%BB%8B", urlBuilder.toString());
@ -296,14 +307,14 @@ public class UrlBuilderTest {
// https://github.com/dromara/hutool/issues/1904
// 在query中"/"是不可转义字符
// https://www.rfc-editor.org/rfc/rfc3986.html#section-3.4
String url = "https://invoice.maycur.com/2b27a802-8423-4d41-86f5-63a6b259f61e.xlsx?download/2b27a802-8423-4d41-86f5-63a6b259f61e.xlsx&e=1630491088";
final String url = "https://invoice.maycur.com/2b27a802-8423-4d41-86f5-63a6b259f61e.xlsx?download/2b27a802-8423-4d41-86f5-63a6b259f61e.xlsx&e=1630491088";
final UrlBuilder urlBuilder = UrlBuilder.ofHttp(url);
Assert.assertEquals(url, urlBuilder.toString());
}
@Test
public void addPathEncodeTest(){
String url = UrlBuilder.create()
final String url = UrlBuilder.create()
.setScheme("https")
.setHost("domain.cn")
.addPath("api")
@ -317,7 +328,7 @@ public class UrlBuilderTest {
@Test
public void addPathEncodeTest2(){
// https://github.com/dromara/hutool/issues/1912
String url = UrlBuilder.create()
final String url = UrlBuilder.create()
.setScheme("https")
.setHost("domain.cn")
.addPath("/api/xxx/bbb")
@ -328,14 +339,14 @@ public class UrlBuilderTest {
@Test
public void percent2BTest(){
String url = "http://xxx.cn/a?Signature=3R013Bj9Uq4YeISzAs2iC%2BTVCL8%3D";
final String url = "http://xxx.cn/a?Signature=3R013Bj9Uq4YeISzAs2iC%2BTVCL8%3D";
final UrlBuilder of = UrlBuilder.ofHttpWithoutEncode(url);
Assert.assertEquals(url, of.toString());
}
@Test
public void paramTest(){
String url = "http://ci.xiaohongshu.com/spectrum/c136c98aa2047babe25b994a26ffa7b492bd8058?imageMogr2/thumbnail/x800/format/jpg";
final String url = "http://ci.xiaohongshu.com/spectrum/c136c98aa2047babe25b994a26ffa7b492bd8058?imageMogr2/thumbnail/x800/format/jpg";
final UrlBuilder builder = UrlBuilder.ofHttp(url);
Assert.assertEquals(url, builder.toString());
}
@ -343,7 +354,7 @@ public class UrlBuilderTest {
@Test
public void fragmentTest(){
// https://gitee.com/dromara/hutool/issues/I49KAL#note_8060874
String url = "https://www.hutool.cn/#/a/b?timestamp=1640391380204";
final String url = "https://www.hutool.cn/#/a/b?timestamp=1640391380204";
final UrlBuilder builder = UrlBuilder.ofHttp(url);
Assert.assertEquals(url, builder.toString());
@ -352,7 +363,7 @@ public class UrlBuilderTest {
@Test
public void fragmentAppendParamTest(){
// https://gitee.com/dromara/hutool/issues/I49KAL#note_8060874
String url = "https://www.hutool.cn/#/a/b";
final String url = "https://www.hutool.cn/#/a/b";
final UrlBuilder builder = UrlBuilder.ofHttp(url);
builder.setFragment(builder.getFragment() + "?timestamp=1640391380204");
Assert.assertEquals("https://www.hutool.cn/#/a/b?timestamp=1640391380204", builder.toString());
@ -360,7 +371,7 @@ public class UrlBuilderTest {
@Test
public void paramWithPlusTest(){
String url = "http://127.0.0.1/?" +
final String url = "http://127.0.0.1/?" +
"Expires=1642734164&" +
"security-token=CAIS+AF1q6Ft5B2yfSjIr5fYEeju1b1ggpPee2KGpjlgQtdfl43urjz2IHtKdXRvBu8Xs" +
"/4wnmxX7f4YlqB6T55OSAmcNZEoPwKpT4zmMeT7oMWQweEurv" +
@ -376,7 +387,7 @@ public class UrlBuilderTest {
@Test
public void issueI4Z2ETTest(){
// =是url参数值中的合法字符但是某些URL强制编码了
String url = "http://dsl-fd.dslbuy.com/fssc/1647947565522.pdf?" +
final String url = "http://dsl-fd.dslbuy.com/fssc/1647947565522.pdf?" +
"Expires=1647949365" +
"&OSSAccessKeyId=STS.NTZ9hvqPSLG8ENknz2YaByLKj" +
"&Signature=oYUu26JufAyPY4PdzaOp1x4sr4Q%3D";
@ -387,22 +398,22 @@ public class UrlBuilderTest {
@Test
public void issue2215Test(){
String url = "https://hutool.cn/v1/104303371/messages:send";
final String url = "https://hutool.cn/v1/104303371/messages:send";
final String build = UrlBuilder.of(url).build();
Assert.assertEquals(url, build);
}
@Test
public void issuesI4Z2ETTest(){
String url = "http://hutool.cn/2022/03/09/123.zip?Expires=1648704684&OSSAccessKeyId=LTAI4FncgaVtwZGBnYHHi8ox&Signature=%2BK%2B%3D";
final String url = "http://hutool.cn/2022/03/09/123.zip?Expires=1648704684&OSSAccessKeyId=LTAI4FncgaVtwZGBnYHHi8ox&Signature=%2BK%2B%3D";
final String build = UrlBuilder.of(url, null).build();
Assert.assertEquals(url, build);
}
@Test
public void issueI50NHQTest(){
String url = "http://127.0.0.1/devicerecord/list";
HashMap<String, Object> params = new LinkedHashMap<>();
final String url = "http://127.0.0.1/devicerecord/list";
final HashMap<String, Object> params = new LinkedHashMap<>();
params.put("start", "2022-03-31 00:00:00");
params.put("end", "2022-03-31 23:59:59");
params.put("page", 1);
@ -422,7 +433,7 @@ public class UrlBuilderTest {
public void issue2243Test(){
// https://github.com/dromara/hutool/issues/2243
// 如果用户已经做了%编码不应该重复编码
String url = "https://hutool.cn/v1.0?privateNum=%2B8616512884988";
final String url = "https://hutool.cn/v1.0?privateNum=%2B8616512884988";
final String s = UrlBuilder.of(url, null).setCharset(CharsetUtil.CHARSET_UTF_8).toString();
Assert.assertEquals(url, s);
}
@ -430,7 +441,7 @@ public class UrlBuilderTest {
@Test
public void issueI51T0VTest(){
// &amp;自动转换为&
String url = "https://hutool.cn/a.mp3?Expires=1652423884&amp;key=JMv2rKNc7Pz&amp;sign=12zva00BpVqgZcX1wcb%2BrmN7H3E%3D";
final String url = "https://hutool.cn/a.mp3?Expires=1652423884&amp;key=JMv2rKNc7Pz&amp;sign=12zva00BpVqgZcX1wcb%2BrmN7H3E%3D";
final UrlBuilder of = UrlBuilder.of(url, null);
Assert.assertEquals(url.replace("&amp;", "&"), of.toString());
}