diff --git a/CHANGELOG.md b/CHANGELOG.md index 63b7aa224..68281999a 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,10 @@ ------------------------------------------------------------------------------------------------------------- -# 5.8.5.M1 (2022-07-11) +# 5.8.5.M1 (2022-07-17) + +### ❌不兼容特性 +* 【core 】 合成注解相关功能重构,增加@Link及其子注解(pr#702@Gitee) ### 🐣新特性 * 【core 】 NumberUtil新增isIn方法(pr#669@Gitee) @@ -14,10 +17,21 @@ * 【core 】 新增CollectorUtil.reduceListMap()(pr#676@Gitee) * 【core 】 CollStreamUtil为空返回空的集合变为可编辑(pr#681@Gitee) * 【core 】 增加StrUtil.containsAll(pr#2437@Github) +* 【core 】 ForestMap添加getNodeValue方法(pr#699@Gitee) +* 【http 】 优化HttpUtil.isHttp判断,避免NPE(pr#698@Gitee) +* 【core 】 修复Dict#containsKey方法没区分大小写问题(pr#697@Gitee) +* 【core 】 增加比较两个LocalDateTime是否为同一天(pr#693@Gitee) +* 【core 】 增加TemporalAccessorUtil.isIn、LocalDateTimeUtil.isIn(issue#I5HBL0@Gitee) +* 【core 】 ReUtil增加getAllGroups重载(pr#2455@Github) +* 【core 】 PageUtil#totalPage增加totalCount为long类型的重载方法(pr#2442@Github) +* 【crypto 】 PemUtil.readPemPrivateKey支持pkcs#1格式,增加OpensslKeyUtil(pr#2456@Github) * ### 🐞Bug修复 * 【core 】 修复CollUtil里面关于可变参数传null造成的crash问题(pr#2428@Github) * 【socket 】 修复异常socket没有关闭问题(pr#690@Gitee) +* 【core 】 修复当时间戳为Integer时时间转换问题(pr#2449@Github) +* 【core 】 修复bmp文件判断问题(issue#I5H93G@Gitee) +* 【core 】 修复CombinationAnnotationElement造成递归循环(issue#I5FQGW@Gitee) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AbstractAnnotationSynthesizer.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AbstractAnnotationSynthesizer.java new file mode 100644 index 000000000..1b04ebdcf --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AbstractAnnotationSynthesizer.java @@ -0,0 +1,168 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.annotation.scanner.AnnotationScanner; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * {@link AnnotationSynthesizer}的基本实现 + * + * @author huangchengxing + */ +public abstract class AbstractAnnotationSynthesizer implements AnnotationSynthesizer { + + /** + * 合成注解来源最初来源 + */ + protected final T source; + + /** + * 包含根注解以及其元注解在内的全部注解实例 + */ + protected final Map, SynthesizedAnnotation> synthesizedAnnotationMap; + + /** + * 已经合成过的注解对象 + */ + private final Map, Annotation> synthesizedProxyAnnotations; + + /** + * 合成注解选择器 + */ + protected final SynthesizedAnnotationSelector annotationSelector; + + /** + * 合成注解属性处理器 + */ + protected final Collection postProcessors; + + /** + * 注解扫描器 + */ + protected final AnnotationScanner annotationScanner; + + /** + * 构造一个注解合成器 + * + * @param source 当前查找的注解对象 + * @param annotationSelector 合成注解选择器 + * @param annotationPostProcessors 注解后置处理器 + * @param annotationScanner 注解扫描器,该扫描器需要支持扫描注解类 + */ + protected AbstractAnnotationSynthesizer( + T source, + SynthesizedAnnotationSelector annotationSelector, + Collection annotationPostProcessors, + AnnotationScanner annotationScanner) { + Assert.notNull(source, "source must not null"); + Assert.notNull(annotationSelector, "annotationSelector must not null"); + Assert.notNull(annotationPostProcessors, "annotationPostProcessors must not null"); + Assert.notNull(annotationPostProcessors, "annotationScanner must not null"); + + this.source = source; + this.annotationSelector = annotationSelector; + this.annotationScanner = annotationScanner; + this.postProcessors = CollUtil.unmodifiable( + CollUtil.sort(annotationPostProcessors, Comparator.comparing(SynthesizedAnnotationPostProcessor::order)) + ); + this.synthesizedProxyAnnotations = new LinkedHashMap<>(); + this.synthesizedAnnotationMap = MapUtil.unmodifiable(loadAnnotations()); + annotationPostProcessors.forEach(processor -> + synthesizedAnnotationMap.values().forEach(synthesized -> processor.process(synthesized, this)) + ); + } + + /** + * 加载合成注解的必要属性 + * + * @return 合成注解 + */ + protected abstract Map, SynthesizedAnnotation> loadAnnotations(); + + /** + * 根据指定的注解类型和对应注解对象,合成最终所需的合成注解 + * + * @param annotationType 注解类型 + * @param annotation 合成注解对象 + * @param 注解类型 + * @return 最终所需的合成注解 + */ + protected abstract A synthesize(Class annotationType, SynthesizedAnnotation annotation); + + /** + * 获取合成注解来源最初来源 + * + * @return 合成注解来源最初来源 + */ + @Override + public T getSource() { + return source; + } + + /** + * 合成注解选择器 + * + * @return 注解选择器 + */ + @Override + public SynthesizedAnnotationSelector getAnnotationSelector() { + return annotationSelector; + } + + /** + * 获取合成注解后置处理器 + * + * @return 合成注解后置处理器 + */ + @Override + public Collection getAnnotationPostProcessors() { + return postProcessors; + } + + /** + * 获取已合成的注解 + * + * @param annotationType 注解类型 + * @return 已合成的注解 + */ + @Override + public SynthesizedAnnotation getSynthesizedAnnotation(Class annotationType) { + return synthesizedAnnotationMap.get(annotationType); + } + + /** + * 获取全部的合成注解 + * + * @return 合成注解 + */ + @Override + public Map, SynthesizedAnnotation> getAllSynthesizedAnnotation() { + return synthesizedAnnotationMap; + } + + /** + * 获取合成注解 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 类型 + */ + @SuppressWarnings("unchecked") + @Override + public A synthesize(Class annotationType) { + return (A)synthesizedProxyAnnotations.computeIfAbsent(annotationType, type -> { + final SynthesizedAnnotation synthesizedAnnotation = synthesizedAnnotationMap.get(annotationType); + return ObjectUtil.isNull(synthesizedAnnotation) ? + null : synthesize(annotationType, synthesizedAnnotation); + }); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AbstractLinkAnnotationPostProcessor.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AbstractLinkAnnotationPostProcessor.java new file mode 100644 index 000000000..0f97dbc26 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AbstractLinkAnnotationPostProcessor.java @@ -0,0 +1,163 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; + +import java.lang.annotation.Annotation; +import java.util.HashMap; +import java.util.Map; + +/** + * {@link SynthesizedAnnotationPostProcessor}的基本实现, + * 用于处理注解中带有{@link Link}注解的属性。 + * + * @author huangchengxing + * @see MirrorLinkAnnotationPostProcessor + * @see AliasLinkAnnotationPostProcessor + */ +public abstract class AbstractLinkAnnotationPostProcessor implements SynthesizedAnnotationPostProcessor { + + /** + * 若一个注解属性上存在{@link Link}注解,注解的{@link Link#type()}返回值在{@link #processTypes()}中存在, + * 且此{@link Link}指定的注解对象在当前的{@link SynthesizedAggregateAnnotation}中存在, + * 则从聚合器中获取类型对应的合成注解对象,与该对象中的指定属性,然后将全部关联数据交给 + * {@link #processLinkedAttribute}处理。 + * + * @param synthesizedAnnotation 合成的注解 + * @param synthesizer 合成注解聚合器 + */ + @Override + public void process(SynthesizedAnnotation synthesizedAnnotation, AnnotationSynthesizer synthesizer) { + final Map attributeMap = new HashMap<>(synthesizedAnnotation.getAttributes()); + attributeMap.forEach((originalAttributeName, originalAttribute) -> { + // 获取注解 + final Link link = getLinkAnnotation(originalAttribute, processTypes()); + if (ObjectUtil.isNull(link)) { + return; + } + // 获取注解属性 + final SynthesizedAnnotation linkedAnnotation = getLinkedAnnotation(link, synthesizer, synthesizedAnnotation.annotationType()); + if (ObjectUtil.isNull(linkedAnnotation)) { + return; + } + final AnnotationAttribute linkedAttribute = linkedAnnotation.getAttributes().get(link.attribute()); + // 处理 + processLinkedAttribute( + synthesizer, link, + synthesizedAnnotation, synthesizedAnnotation.getAttributes().get(originalAttributeName), + linkedAnnotation, linkedAttribute + ); + }); + } + + // =========================== 抽象方法 =========================== + + /** + * 当属性上存在{@link Link}注解时,仅当{@link Link#type()}在本方法返回值内存在时才进行处理 + * + * @return 支持处理的{@link RelationType}类型 + */ + protected abstract RelationType[] processTypes(); + + /** + * 对关联的合成注解对象及其关联属性的处理 + * + * @param synthesizer 注解合成器 + * @param annotation {@code originalAttribute}上的{@link Link}注解对象 + * @param originalAnnotation 当前正在处理的{@link SynthesizedAnnotation}对象 + * @param originalAttribute {@code originalAnnotation}上的待处理的属性 + * @param linkedAnnotation {@link Link}指向的关联注解对象 + * @param linkedAttribute {@link Link}指向的{@code originalAnnotation}中的关联属性,该参数可能为空 + */ + protected abstract void processLinkedAttribute( + AnnotationSynthesizer synthesizer, Link annotation, + SynthesizedAnnotation originalAnnotation, AnnotationAttribute originalAttribute, + SynthesizedAnnotation linkedAnnotation, AnnotationAttribute linkedAttribute + ); + + // =========================== @Link注解的处理 =========================== + + /** + * 从注解属性上获取指定类型的{@link Link}注解 + * + * @param attribute 注解属性 + * @param relationTypes 类型 + * @return 注解 + */ + protected Link getLinkAnnotation(AnnotationAttribute attribute, RelationType... relationTypes) { + return Opt.ofNullable(attribute) + .map(t -> AnnotationUtil.getSynthesizedAnnotation(attribute.getAttribute(), Link.class)) + .filter(a -> ArrayUtil.contains(relationTypes, a.type())) + .get(); + } + + /** + * 从合成注解中获取{@link Link#type()}指定的注解对象 + * + * @param annotation {@link Link}注解 + * @param synthesizer 注解合成器 + * @param defaultType 默认类型 + * @return {@link SynthesizedAnnotation} + */ + protected SynthesizedAnnotation getLinkedAnnotation(Link annotation, AnnotationSynthesizer synthesizer, Class defaultType) { + final Class targetAnnotationType = getLinkedAnnotationType(annotation, defaultType); + return synthesizer.getSynthesizedAnnotation(targetAnnotationType); + } + + /** + * 若{@link Link#annotation()}获取的类型{@code Annotation#getClass()},则返回{@code defaultType}, + * 否则返回{@link Link#annotation()}指定的类型 + * + * @param annotation {@link Link}注解 + * @param defaultType 默认注解类型 + * @return 注解类型 + */ + protected Class getLinkedAnnotationType(Link annotation, Class defaultType) { + return ObjectUtil.equals(annotation.annotation(), Annotation.class) ? + defaultType : annotation.annotation(); + } + + // =========================== 注解属性的校验 =========================== + + /** + * 校验两个注解属性的返回值类型是否一致 + * + * @param original 原属性 + * @param alias 别名属性 + */ + protected void checkAttributeType(AnnotationAttribute original, AnnotationAttribute alias) { + Assert.equals( + original.getAttributeType(), alias.getAttributeType(), + "return type of the linked attribute [{}] is inconsistent with the original [{}]", + original.getAttribute(), alias.getAttribute() + ); + } + + /** + * 检查{@link Link}指向的注解属性是否就是本身 + * + * @param original {@link Link}注解的属性 + * @param linked {@link Link}指向的注解属性 + */ + protected void checkLinkedSelf(AnnotationAttribute original, AnnotationAttribute linked) { + boolean linkSelf = (original == linked) || ObjectUtil.equals(original.getAttribute(), linked.getAttribute()); + Assert.isFalse(linkSelf, "cannot link self [{}]", original.getAttribute()); + } + + /** + * 检查{@link Link}指向的注解属性是否存在 + * + * @param original {@link Link}注解的属性 + * @param linkedAttribute {@link Link}指向的注解属性 + * @param annotation {@link Link}注解 + */ + protected void checkLinkedAttributeNotNull(AnnotationAttribute original, AnnotationAttribute linkedAttribute, Link annotation) { + Assert.notNull(linkedAttribute, "cannot find linked attribute [{}] of original [{}] in [{}]", + original.getAttribute(), annotation.attribute(), + getLinkedAnnotationType(annotation, original.getAnnotationType()) + ); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AbstractWrappedAnnotationAttribute.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AbstractWrappedAnnotationAttribute.java new file mode 100644 index 000000000..2dc13d476 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AbstractWrappedAnnotationAttribute.java @@ -0,0 +1,71 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * {@link WrappedAnnotationAttribute}的基本实现 + * + * @author huangchengxing + * @see ForceAliasedAnnotationAttribute + * @see AliasedAnnotationAttribute + * @see MirroredAnnotationAttribute + */ +public abstract class AbstractWrappedAnnotationAttribute implements WrappedAnnotationAttribute { + + protected final AnnotationAttribute original; + protected final AnnotationAttribute linked; + + protected AbstractWrappedAnnotationAttribute(AnnotationAttribute original, AnnotationAttribute linked) { + Assert.notNull(original, "target must not null"); + Assert.notNull(linked, "linked must not null"); + this.original = original; + this.linked = linked; + } + + @Override + public AnnotationAttribute getOriginal() { + return original; + } + + @Override + public AnnotationAttribute getLinked() { + return linked; + } + + @Override + public AnnotationAttribute getNonWrappedOriginal() { + AnnotationAttribute curr = null; + AnnotationAttribute next = original; + while (next != null) { + curr = next; + next = next.isWrapped() ? ((WrappedAnnotationAttribute)curr).getOriginal() : null; + } + return curr; + } + + @Override + public Collection getAllLinkedNonWrappedAttributes() { + List leafAttributes = new ArrayList<>(); + collectLeafAttribute(this, leafAttributes); + return leafAttributes; + } + + private void collectLeafAttribute(AnnotationAttribute curr, List leafAttributes) { + if (ObjectUtil.isNull(curr)) { + return; + } + if (!curr.isWrapped()) { + leafAttributes.add(curr); + return; + } + WrappedAnnotationAttribute wrappedAttribute = (WrappedAnnotationAttribute)curr; + collectLeafAttribute(wrappedAttribute.getOriginal(), leafAttributes); + collectLeafAttribute(wrappedAttribute.getLinked(), leafAttributes); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AggregateAnnotation.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AggregateAnnotation.java new file mode 100644 index 000000000..000c99175 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AggregateAnnotation.java @@ -0,0 +1,27 @@ +package cn.hutool.core.annotation; + +import java.lang.annotation.Annotation; + +/** + * 表示一组被聚合在一起的注解对象 + * + * @author huangchengxing + */ +public interface AggregateAnnotation extends Annotation { + + /** + * 在聚合中是否存在的指定类型注解对象 + * + * @param annotationType 注解类型 + * @return 是否 + */ + boolean isAnnotationPresent(Class annotationType); + + /** + * 获取聚合中的全部注解对象 + * + * @return 注解对象 + */ + Annotation[] getAnnotations(); + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AliasAnnotationPostProcessor.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AliasAnnotationPostProcessor.java new file mode 100644 index 000000000..ac5481a27 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AliasAnnotationPostProcessor.java @@ -0,0 +1,66 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.map.ForestMap; +import cn.hutool.core.map.LinkedForestMap; +import cn.hutool.core.map.TreeEntry; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ObjectUtil; + +import java.util.Map; + +/** + *

用于处理注解对象中带有{@link Alias}注解的属性。
+ * 当该处理器执行完毕后,{@link Alias}注解指向的目标注解的属性将会被包装并替换为 + * {@link ForceAliasedAnnotationAttribute}。 + * + * @author huangchengxing + * @see Alias + * @see ForceAliasedAnnotationAttribute + */ +public class AliasAnnotationPostProcessor implements SynthesizedAnnotationPostProcessor { + + @Override + public int order() { + return Integer.MIN_VALUE; + } + + @Override + public void process(SynthesizedAnnotation synthesizedAnnotation, AnnotationSynthesizer synthesizer) { + final Map attributeMap = synthesizedAnnotation.getAttributes(); + + // 记录别名与属性的关系 + final ForestMap attributeAliasMappings = new LinkedForestMap<>(false); + attributeMap.forEach((attributeName, attribute) -> { + final String alias = Opt.ofNullable(attribute.getAnnotation(Alias.class)) + .map(Alias::value) + .orElse(null); + if (ObjectUtil.isNull(alias)) { + return; + } + final AnnotationAttribute aliasAttribute = attributeMap.get(alias); + Assert.notNull(aliasAttribute, "no method for alias: [{}]", alias); + attributeAliasMappings.putLinkedNodes(alias, aliasAttribute, attributeName, attribute); + }); + + // 处理别名 + attributeMap.forEach((attributeName, attribute) -> { + final AnnotationAttribute resolvedAttribute = Opt.ofNullable(attributeName) + .map(attributeAliasMappings::getRootNode) + .map(TreeEntry::getValue) + .orElse(attribute); + Assert.isTrue( + ObjectUtil.isNull(resolvedAttribute) + || ClassUtil.isAssignable(attribute.getAttributeType(), resolvedAttribute.getAttributeType()), + "return type of the root alias method [{}] is inconsistent with the original [{}]", + resolvedAttribute.getClass(), attribute.getAttributeType() + ); + if (attribute != resolvedAttribute) { + attributeMap.put(attributeName, new ForceAliasedAnnotationAttribute(attribute, resolvedAttribute)); + } + }); + synthesizedAnnotation.setAttributes(attributeMap); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AliasFor.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AliasFor.java new file mode 100644 index 000000000..bf163238b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AliasFor.java @@ -0,0 +1,39 @@ +package cn.hutool.core.annotation; + +import java.lang.annotation.*; + +/** + *

{@link Link}的子注解。表示“原始属性”将作为“关联属性”的别名。 + *

    + *
  • 当“原始属性”为默认值时,获取“关联属性”将返回“关联属性”本身的值;
  • + *
  • 当“原始属性”不为默认值时,获取“关联属性”将返回“原始属性”的值;
  • + *
+ * 注意,该注解与{@link Link}、{@link ForceAliasFor}或{@link MirrorFor}一起使用时,将只有被声明在最上面的注解会生效 + * + * @author huangchengxing + * @see Link + * @see RelationType#ALIAS_FOR + */ +@Link(type = RelationType.ALIAS_FOR) +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +public @interface AliasFor { + + /** + * 产生关联的注解类型,当不指定时,默认指注释的属性所在的类 + * + * @return 注解类型 + */ + @Link(annotation = Link.class, attribute = "annotation", type = RelationType.FORCE_ALIAS_FOR) + Class annotation() default Annotation.class; + + /** + * {@link #annotation()}指定注解中关联的属性 + * + * @return 关联属性 + */ + @Link(annotation = Link.class, attribute = "attribute", type = RelationType.FORCE_ALIAS_FOR) + String attribute() default ""; + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AliasLinkAnnotationPostProcessor.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AliasLinkAnnotationPostProcessor.java new file mode 100644 index 000000000..0cf5b2255 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AliasLinkAnnotationPostProcessor.java @@ -0,0 +1,126 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.ObjectUtil; + +import java.util.function.BinaryOperator; + +/** + *

用于处理注解对象中带有{@link Link}注解,且{@link Link#type()}为 + * {@link RelationType#ALIAS_FOR}或{@link RelationType#FORCE_ALIAS_FOR}的属性。
+ * 当该处理器执行完毕后,{@link Link}注解指向的目标注解的属性将会被包装并替换为 + * {@link AliasedAnnotationAttribute}或{@link ForceAliasedAnnotationAttribute}。 + * + * @author huangchengxing + * @see RelationType#ALIAS_FOR + * @see AliasedAnnotationAttribute + * @see RelationType#FORCE_ALIAS_FOR + * @see ForceAliasedAnnotationAttribute + */ +public class AliasLinkAnnotationPostProcessor extends AbstractLinkAnnotationPostProcessor { + + private static final RelationType[] PROCESSED_RELATION_TYPES = new RelationType[]{ RelationType.ALIAS_FOR, RelationType.FORCE_ALIAS_FOR }; + + @Override + public int order() { + return Integer.MIN_VALUE + 2; + } + + /** + * 该处理器只处理{@link Link#type()}类型为{@link RelationType#ALIAS_FOR}和{@link RelationType#FORCE_ALIAS_FOR}的注解属性 + * + * @return 含有{@link RelationType#ALIAS_FOR}和{@link RelationType#FORCE_ALIAS_FOR}的数组 + */ + @Override + protected RelationType[] processTypes() { + return PROCESSED_RELATION_TYPES; + } + + /** + * 获取{@link Link}指向的目标注解属性,并根据{@link Link#type()}的类型是 + * {@link RelationType#ALIAS_FOR}或{@link RelationType#FORCE_ALIAS_FOR} + * 将目标注解属性包装为{@link AliasedAnnotationAttribute}或{@link ForceAliasedAnnotationAttribute}, + * 然后用包装后注解属性在对应的合成注解中替换原始的目标注解属性 + * + * @param synthesizer 注解合成器 + * @param annotation {@code originalAttribute}上的{@link Link}注解对象 + * @param originalAnnotation 当前正在处理的{@link SynthesizedAnnotation}对象 + * @param originalAttribute {@code originalAnnotation}上的待处理的属性 + * @param linkedAnnotation {@link Link}指向的关联注解对象 + * @param linkedAttribute {@link Link}指向的{@code originalAnnotation}中的关联属性,该参数可能为空 + */ + @Override + protected void processLinkedAttribute( + AnnotationSynthesizer synthesizer, Link annotation, + SynthesizedAnnotation originalAnnotation, AnnotationAttribute originalAttribute, + SynthesizedAnnotation linkedAnnotation, AnnotationAttribute linkedAttribute) { + // 校验别名关系 + checkAliasRelation(annotation, originalAttribute, linkedAttribute); + // 处理aliasFor类型的关系 + if (RelationType.ALIAS_FOR.equals(annotation.type())) { + wrappingLinkedAttribute(synthesizer, originalAttribute, linkedAttribute, AliasedAnnotationAttribute::new); + return; + } + // 处理forceAliasFor类型的关系 + wrappingLinkedAttribute(synthesizer, originalAttribute, linkedAttribute, ForceAliasedAnnotationAttribute::new); + } + + /** + * 对指定注解属性进行包装,若该属性已被包装过,则递归以其为根节点的树结构,对树上全部的叶子节点进行包装 + */ + private void wrappingLinkedAttribute( + AnnotationSynthesizer synthesizer, AnnotationAttribute originalAttribute, AnnotationAttribute aliasAttribute, BinaryOperator wrapping) { + // 不是包装属性 + if (!aliasAttribute.isWrapped()) { + processAttribute(synthesizer, originalAttribute, aliasAttribute, wrapping); + return; + } + // 是包装属性 + final AbstractWrappedAnnotationAttribute wrapper = (AbstractWrappedAnnotationAttribute)aliasAttribute; + wrapper.getAllLinkedNonWrappedAttributes().forEach( + t -> processAttribute(synthesizer, originalAttribute, t, wrapping) + ); + } + + /** + * 获取指定注解属性,然后将其再进行一层包装 + */ + private void processAttribute( + AnnotationSynthesizer synthesizer, AnnotationAttribute originalAttribute, + AnnotationAttribute target, BinaryOperator wrapping) { + Opt.ofNullable(target.getAnnotationType()) + .map(synthesizer::getSynthesizedAnnotation) + .ifPresent(t -> t.replaceAttribute(target.getAttributeName(), old -> wrapping.apply(old, originalAttribute))); + } + + /** + * 基本校验 + */ + private void checkAliasRelation(Link annotation, AnnotationAttribute originalAttribute, AnnotationAttribute linkedAttribute) { + checkLinkedAttributeNotNull(originalAttribute, linkedAttribute, annotation); + checkAttributeType(originalAttribute, linkedAttribute); + checkCircularDependency(originalAttribute, linkedAttribute); + } + + /** + * 检查两个属性是否互为别名 + */ + private void checkCircularDependency(AnnotationAttribute original, AnnotationAttribute alias) { + checkLinkedSelf(original, alias); + Link annotation = getLinkAnnotation(alias, RelationType.ALIAS_FOR, RelationType.FORCE_ALIAS_FOR); + if (ObjectUtil.isNull(annotation)) { + return; + } + final Class aliasAnnotationType = getLinkedAnnotationType(annotation, alias.getAnnotationType()); + if (ObjectUtil.notEqual(aliasAnnotationType, original.getAnnotationType())) { + return; + } + Assert.notEquals( + annotation.attribute(), original.getAttributeName(), + "circular reference between the alias attribute [{}] and the original attribute [{}]", + alias.getAttribute(), original.getAttribute() + ); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AliasedAnnotationAttribute.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AliasedAnnotationAttribute.java new file mode 100644 index 000000000..1c78d8175 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AliasedAnnotationAttribute.java @@ -0,0 +1,36 @@ +package cn.hutool.core.annotation; + +/** + *

表示一个具有别名的属性。 + * 当别名属性值为默认值时,优先返回原属性的值,当别名属性不为默认值时,优先返回别名属性的值 + * + * @author huangchengxing + * @see AliasLinkAnnotationPostProcessor + * @see RelationType#ALIAS_FOR + */ +public class AliasedAnnotationAttribute extends AbstractWrappedAnnotationAttribute { + + protected AliasedAnnotationAttribute(AnnotationAttribute origin, AnnotationAttribute linked) { + super(origin, linked); + } + + /** + * 若{@link #linked}为默认值,则返回{@link #original}的值,否则返回{@link #linked}的值 + * + * @return 属性值 + */ + @Override + public Object getValue() { + return linked.isValueEquivalentToDefaultValue() ? super.getValue() : linked.getValue(); + } + + /** + * 当{@link #original}与{@link #linked}都为默认值时返回{@code true} + * + * @return 是否 + */ + @Override + public boolean isValueEquivalentToDefaultValue() { + return linked.isValueEquivalentToDefaultValue() && original.isValueEquivalentToDefaultValue(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationAttribute.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationAttribute.java new file mode 100644 index 000000000..f3d976d40 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationAttribute.java @@ -0,0 +1,104 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.util.ReflectUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +/** + *

表示注解的某个属性,等同于绑定的调用对象的{@link Method}方法。
+ * 在{@link SynthesizedAggregateAnnotation}的解析以及取值过程中, + * 可以通过设置{@link SynthesizedAnnotation}的注解属性, + * 从而使得可以从一个注解对象中属性获取另一个注解对象的属性值 + * + *

一般情况下,注解属性的处理会发生在{@link SynthesizedAnnotationPostProcessor}调用时 + * + * @author huangchengxing + * @see SynthesizedAnnotationPostProcessor + * @see WrappedAnnotationAttribute + * @see CacheableAnnotationAttribute + * @see AbstractWrappedAnnotationAttribute + * @see ForceAliasedAnnotationAttribute + * @see AliasedAnnotationAttribute + * @see MirroredAnnotationAttribute + */ +public interface AnnotationAttribute { + + /** + * 获取注解对象 + * + * @return 注解对象 + */ + Annotation getAnnotation(); + + /** + * 获取注解属性对应的方法 + * + * @return 注解属性对应的方法 + */ + Method getAttribute(); + + /** + * 获取声明属性的注解类 + * + * @return 声明注解的注解类 + */ + default Class getAnnotationType() { + return getAttribute().getDeclaringClass(); + } + + /** + * 获取属性名称 + * + * @return 属性名称 + */ + default String getAttributeName() { + return getAttribute().getName(); + } + + /** + * 获取注解属性 + * + * @return 注解属性 + */ + default Object getValue() { + return ReflectUtil.invoke(getAnnotation(), getAttribute()); + } + + /** + * 该注解属性的值是否等于默认值 + * + * @return 该注解属性的值是否等于默认值 + */ + boolean isValueEquivalentToDefaultValue(); + + /** + * 获取属性类型 + * + * @return 属性类型 + */ + default Class getAttributeType() { + return getAttribute().getReturnType(); + } + + /** + * 获取属性上的注解 + * + * @param 注解类型 + * @param annotationType 注解类型 + * @return 注解对象 + */ + default T getAnnotation(Class annotationType) { + return getAttribute().getAnnotation(annotationType); + } + + /** + * 当前注解属性是否已经被{@link WrappedAnnotationAttribute}包装 + * + * @return boolean + */ + default boolean isWrapped() { + return false; + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationAttributeValueProvider.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationAttributeValueProvider.java new file mode 100644 index 000000000..b127d75c4 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationAttributeValueProvider.java @@ -0,0 +1,18 @@ +package cn.hutool.core.annotation; + +/** + * 表示一个可以从当前接口的实现类中,获得特定的属性值 + */ +@FunctionalInterface +public interface AnnotationAttributeValueProvider { + + /** + * 获取注解属性值 + * + * @param attributeName 属性名称 + * @param attributeType 属性类型 + * @return 注解属性值 + */ + Object getAttributeValue(String attributeName, Class attributeType); + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationSynthesizer.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationSynthesizer.java new file mode 100644 index 000000000..298a33dbc --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationSynthesizer.java @@ -0,0 +1,77 @@ +package cn.hutool.core.annotation; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Map; + +/** + *

注解合成器,用于处理一组给定的与{@link #getSource()}具有直接或间接联系的注解对象, + * 并返回与原始注解对象具有不同属性的“合成”注解。 + * + *

合成注解一般被用于处理类层级结果中具有直接或间接关联的注解对象, + * 当实例被创建时,会获取到这些注解对象,并使用{@link SynthesizedAnnotationSelector}对类型相同的注解进行过滤, + * 并最终得到类型不重复的有效注解对象。这些有效注解将被包装为{@link SynthesizedAnnotation}, + * 然后最终用于“合成”一个{@link SynthesizedAggregateAnnotation}。
+ * {@link SynthesizedAnnotationSelector}是合成注解生命周期中的第一个钩子, + * 自定义选择器以拦截原始注解被扫描的过程。 + * + *

当合成注解完成对待合成注解的扫描,并完成了必要属性的加载后, + * 将会按顺序依次调用{@link SynthesizedAnnotationPostProcessor}, + * 注解后置处理器允许用于对完成注解的待合成注解进行二次调整, + * 该钩子一般用于根据{@link Link}注解对属性进行调整。
+ * {@link SynthesizedAnnotationPostProcessor}是合成注解生命周期中的第二个钩子, + * 自定义后置处理器以拦截原始在转为待合成注解后的初始化过程。 + * + *

使用{@link #synthesize(Class)}用于获取“合成”后的注解, + * 该注解对象的属性可能会与原始的对象属性不同。 + * + * @author huangchengxing + */ +public interface AnnotationSynthesizer { + + /** + * 获取合成注解来源最初来源 + * + * @return 合成注解来源最初来源 + */ + Object getSource(); + + /** + * 合成注解选择器 + * + * @return 注解选择器 + */ + SynthesizedAnnotationSelector getAnnotationSelector(); + + /** + * 获取合成注解后置处理器 + * + * @return 合成注解后置处理器 + */ + Collection getAnnotationPostProcessors(); + + /** + * 获取已合成的注解 + * + * @param annotationType 注解类型 + * @return 已合成的注解 + */ + SynthesizedAnnotation getSynthesizedAnnotation(Class annotationType); + + /** + * 获取全部的合成注解 + * + * @return 合成注解 + */ + Map, SynthesizedAnnotation> getAllSynthesizedAnnotation(); + + /** + * 获取合成注解 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 类型 + */ + T synthesize(Class annotationType); + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java index 869412563..bb0756f58 100755 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java @@ -3,10 +3,8 @@ package cn.hutool.core.annotation; import cn.hutool.core.annotation.scanner.*; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.exceptions.UtilException; -import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Opt; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReflectUtil; @@ -15,10 +13,8 @@ import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.*; -import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.Stream; /** * 注解工具类
@@ -320,75 +316,19 @@ public class AnnotationUtil { return annotationType.isAnnotationPresent(Inherited.class); } - /** - * 设置新的注解的属性(字段)值 - * - * @param annotation 注解对象 - * @param annotationField 注解属性(字段)名称 - * @param value 要更新的属性值 - * @since 5.5.2 - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - public static void setValue(Annotation annotation, String annotationField, Object value) { - final Map memberValues = (Map) ReflectUtil.getFieldValue(Proxy.getInvocationHandler(annotation), "memberValues"); - memberValues.put(annotationField, value); - } - - /** - * 获取别名支持后的注解 - * - * @param annotationEle 被注解的类 - * @param annotationType 注解类型Class - * @param 注解类型 - * @return 别名支持后的注解 - * @since 5.7.23 - */ - @SuppressWarnings("unchecked") - public static T getAnnotationAlias(AnnotatedElement annotationEle, Class annotationType) { - final T annotation = getAnnotation(annotationEle, annotationType); - return (T) Proxy.newProxyInstance(annotationType.getClassLoader(), new Class[]{annotationType}, new AnnotationProxy<>(annotation)); - } - - /** - * 将指定注解实例与其元注解转为合成注解 - * - * @param annotation 注解对象 - * @param annotationType 注解类 - * @param 注解类型 - * @return 合成注解 - * @see SyntheticAnnotation - */ - public static T getSynthesisAnnotation(Annotation annotation, Class annotationType) { - return SyntheticAnnotation.of(annotation).getAnnotation(annotationType); - } - - /** - * 获取元素上所有指定注解 - *

    - *
  • 若元素是类,则递归解析全部父类和全部父接口上的注解;
  • - *
  • 若元素是方法、属性或注解,则只解析其直接声明的注解;
  • - *
- * - * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @param annotationType 注解类 - * @param 注解类型 - * @return 合成注解 - * @see SyntheticAnnotation - */ - public static List getAllSynthesisAnnotations(AnnotatedElement annotatedEle, Class annotationType) { - AnnotationScanner[] scanners = new AnnotationScanner[]{ - new MetaAnnotationScanner(), new TypeAnnotationScanner(), new MethodAnnotationScanner(), new FieldAnnotationScanner() - }; - return AnnotationScanner.scanByAnySupported(annotatedEle, scanners).stream() - .map(annotation -> getSynthesisAnnotation(annotation, annotationType)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - /** * 扫描注解类,以及注解类的{@link Class}层级结构中的注解,将返回除了{@link #META_ANNOTATIONS}中指定的JDK默认注解外, * 按元注解对象与{@code annotationType}的距离和{@link Class#getAnnotations()}顺序排序的注解对象集合 * + *

比如:
+ * 若{@code annotationType}为 A,且A存在元注解B,B又存在元注解C和D,则有: + *

+	 *                              |-> C.class [@a, @b]
+	 *     A.class -> B.class [@a] -|
+	 *                              |-> D.class [@a, @c]
+	 * 
+ * 扫描A,则该方法最终将返回 {@code [@a, @a, @b, @a, @c]} + * * @param annotationType 注解类 * @return 注解对象集合 * @see MetaAnnotationScanner @@ -408,6 +348,16 @@ public class AnnotationUtil { * * 注解根据其声明类/接口被扫描的顺序排序,若注解都在同一个{@link Class}中被声明,则还会遵循{@link Class#getAnnotations()}的顺序。 * + *

比如:
+ * 若{@code targetClass}为{@code A.class},且{@code A.class}存在父类{@code B.class}、父接口{@code C.class}, + * 三个类的注解声明情况如下: + *

+	 *                   |-> B.class [@a, @b]
+	 *     A.class [@a] -|
+	 *                   |-> C.class [@a, @c]
+	 * 
+ * 则该方法最终将返回 {@code [@a, @a, @b, @a, @c]} + * * @param targetClass 类 * @return 注解对象集合 * @see TypeAnnotationScanner @@ -428,6 +378,14 @@ public class AnnotationUtil { * * 方法上的注解根据方法的声明类/接口被扫描的顺序排序,若注解都在同一个类的同一个方法中被声明,则还会遵循{@link Method#getAnnotations()}的顺序。 * + *

比如:
+ * 若方法X声明于{@code A.class},且重载/重写自父类{@code B.class},并且父类中的方法X由重写至其实现的接口{@code C.class}, + * 三个类的注解声明情况如下: + *

+	 *     A#X()[@a] -> B#X()[@b] -> C#X()[@c]
+	 * 
+ * 则该方法最终将返回 {@code [@a, @b, @c]} + * * @param method 方法 * @return 注解对象集合 * @see MethodAnnotationScanner @@ -436,6 +394,152 @@ public class AnnotationUtil { return new MethodAnnotationScanner(true).getIfSupport(method); } + /** + * 设置新的注解的属性(字段)值 + * + * @param annotation 注解对象 + * @param annotationField 注解属性(字段)名称 + * @param value 要更新的属性值 + * @since 5.5.2 + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public static void setValue(Annotation annotation, String annotationField, Object value) { + final Map memberValues = (Map) ReflectUtil.getFieldValue(Proxy.getInvocationHandler(annotation), "memberValues"); + memberValues.put(annotationField, value); + } + + /** + * 该注解对象是否为通过代理类生成的合成注解 + * + * @param annotation 注解对象 + * @return 是否 + * @see SynthesizedAnnotationProxy#isProxyAnnotation(Class) + */ + public static boolean isSynthesizedAnnotation(Annotation annotation) { + return SynthesizedAnnotationProxy.isProxyAnnotation(annotation.getClass()); + } + + /** + * 获取别名支持后的注解 + * + * @param annotationEle 被注解的类 + * @param annotationType 注解类型Class + * @param 注解类型 + * @return 别名支持后的注解 + * @since 5.7.23 + */ + public static T getAnnotationAlias(AnnotatedElement annotationEle, Class annotationType) { + final T annotation = getAnnotation(annotationEle, annotationType); + return aggregatingFromAnnotation(annotation).synthesize(annotationType); + } + + /** + * 将指定注解实例与其元注解转为合成注解 + * + * @param annotationType 注解类 + * @param annotations 注解对象 + * @param 注解类型 + * @return 合成注解 + * @see SynthesizedAggregateAnnotation + */ + public static T getSynthesizedAnnotation(Class annotationType, Annotation... annotations) { + // TODO 缓存合成注解信息,避免重复解析 + return Opt.ofNullable(annotations) + .filter(ArrayUtil::isNotEmpty) + .map(AnnotationUtil::aggregatingFromAnnotationWithMeta) + .map(a -> a.synthesize(annotationType)) + .get(); + } + + /** + *

获取元素上距离指定元素最接近的合成注解 + *

    + *
  • 若元素是类,则递归解析全部父类和全部父接口上的注解;
  • + *
  • 若元素是方法、属性或注解,则只解析其直接声明的注解;
  • + *
+ * + *

注解合成规则如下: + * 若{@code AnnotatedEle}按顺序从上到下声明了A,B,C三个注解,且三注解存在元注解如下: + *

+	 *    A -> M3
+	 *    B -> M1 -> M2 -> M3
+	 *    C -> M2 -> M3
+	 * 
+ * 此时入参{@code annotationType}类型为{@code M2},则最终将优先返回基于根注解B合成的合成注解 + * + * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @param annotationType 注解类 + * @param 注解类型 + * @return 合成注解 + * @see SynthesizedAggregateAnnotation + */ + public static T getSynthesizedAnnotation(AnnotatedElement annotatedEle, Class annotationType) { + T target = annotatedEle.getAnnotation(annotationType); + 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); + } + + /** + * 获取元素上所有指定注解 + *
    + *
  • 若元素是类,则递归解析全部父类和全部父接口上的注解;
  • + *
  • 若元素是方法、属性或注解,则只解析其直接声明的注解;
  • + *
+ * + *

注解合成规则如下: + * 若{@code AnnotatedEle}按顺序从上到下声明了A,B,C三个注解,且三注解存在元注解如下: + *

+	 *    A -> M1 -> M2
+	 *    B -> M3 -> M1 -> M2
+	 *    C -> M2
+	 * 
+ * 此时入参{@code annotationType}类型为{@code M1},则最终将返回基于根注解A与根注解B合成的合成注解。 + * + * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @param annotationType 注解类 + * @param 注解类型 + * @return 合成注解 + * @see SynthesizedAggregateAnnotation + */ + public static List getAllSynthesizedAnnotations(AnnotatedElement annotatedEle, Class annotationType) { + 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) + .collect(Collectors.toList()); + } + + /** + * 对指定注解对象进行聚合 + * + * @param annotations 注解对象 + * @return 聚合注解 + */ + public static SynthesizedAggregateAnnotation aggregatingFromAnnotation(Annotation... annotations) { + return new GenericSynthesizedAggregateAnnotation(Arrays.asList(annotations), EmptyAnnotationScanner.INSTANCE); + } + + /** + * 对指定注解对象及其元注解进行聚合 + * + * @param annotations 注解对象 + * @return 聚合注解 + */ + public static SynthesizedAggregateAnnotation aggregatingFromAnnotationWithMeta(Annotation... annotations) { + return new GenericSynthesizedAggregateAnnotation(Arrays.asList(annotations), new MetaAnnotationScanner()); + } + /** * 方法是否为注解属性方法。
* 方法无参数,且有返回值的方法认为是注解属性的方法。 @@ -446,36 +550,4 @@ public class AnnotationUtil { return method.getParameterCount() == 0 && method.getReturnType() != void.class; } - /** - * 获取注解的全部属性值获取方法 - * - * @param annotationType 注解 - * @return 注解的全部属性值 - * @throws IllegalArgumentException 当别名属性在注解中不存在,或别名属性的值与原属性的值类型不一致时抛出 - */ - static Map getAttributeMethods(Class annotationType) { - // 获取全部注解属性值 - Map attributeMethods = Stream.of(annotationType.getDeclaredMethods()) - .filter(AnnotationUtil::isAttributeMethod) - .collect(Collectors.toMap(Method::getName, Function.identity())); - // 处理别名 - attributeMethods.forEach((methodName, method) -> { - String alias = Opt.ofNullable(method.getAnnotation(Alias.class)) - .map(Alias::value) - .orElse(null); - if (ObjectUtil.isNull(alias)) { - return; - } - // 存在别名,则将原本的值替换为别名对应的值 - Assert.isTrue(attributeMethods.containsKey(alias), "No method for alias: [{}]", alias); - Method aliasAttributeMethod = attributeMethods.get(alias); - Assert.isTrue( - ObjectUtil.isNull(aliasAttributeMethod) || ClassUtil.isAssignable(method.getReturnType(), aliasAttributeMethod.getReturnType()), - "Return type of the alias method [{}] is inconsistent with the original [{}]", - aliasAttributeMethod.getClass(), method.getParameterTypes() - ); - attributeMethods.put(methodName, aliasAttributeMethod); - }); - return attributeMethods; - } } diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/CacheableAnnotationAttribute.java b/hutool-core/src/main/java/cn/hutool/core/annotation/CacheableAnnotationAttribute.java new file mode 100644 index 000000000..e4c8bc94c --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/CacheableAnnotationAttribute.java @@ -0,0 +1,63 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +/** + * {@link AnnotationAttribute}的基本实现 + * + * @author huangchengxing + */ +public class CacheableAnnotationAttribute implements AnnotationAttribute { + + private boolean valueInvoked; + private Object value; + + private boolean defaultValueInvoked; + private Object defaultValue; + + private final Annotation annotation; + private final Method attribute; + + public CacheableAnnotationAttribute(Annotation annotation, Method attribute) { + Assert.notNull(annotation, "annotation must not null"); + Assert.notNull(attribute, "attribute must not null"); + this.annotation = annotation; + this.attribute = attribute; + this.valueInvoked = false; + this.defaultValueInvoked = false; + } + + @Override + public Annotation getAnnotation() { + return this.annotation; + } + + @Override + public Method getAttribute() { + return this.attribute; + } + + @Override + public Object getValue() { + if (!valueInvoked) { + valueInvoked = true; + value = ReflectUtil.invoke(annotation, attribute); + } + return value; + } + + @Override + public boolean isValueEquivalentToDefaultValue() { + if (!defaultValueInvoked) { + defaultValue = attribute.getDefaultValue(); + defaultValueInvoked = true; + } + return ObjectUtil.equals(getValue(), defaultValue); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/CacheableSynthesizedAnnotationAttributeProcessor.java b/hutool-core/src/main/java/cn/hutool/core/annotation/CacheableSynthesizedAnnotationAttributeProcessor.java index 8461ac2ac..ab7ae9ad6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/CacheableSynthesizedAnnotationAttributeProcessor.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/CacheableSynthesizedAnnotationAttributeProcessor.java @@ -1,5 +1,6 @@ package cn.hutool.core.annotation; +import cn.hutool.core.lang.Assert; import cn.hutool.core.map.multi.RowKeyTable; import cn.hutool.core.map.multi.Table; import cn.hutool.core.util.ObjectUtil; @@ -8,26 +9,40 @@ import java.util.Collection; import java.util.Comparator; /** - * 带缓存功能的{@link SynthesizedAnnotationAttributeProcessor}实现, + *

带缓存功能的{@link SynthesizedAnnotationAttributeProcessor}实现, * 构建时需要传入比较器,获取属性值时将根据比较器对合成注解进行排序, * 然后选择具有所需属性的,排序最靠前的注解用于获取属性值 * + *

通过该处理器获取合成注解属性值时会出现隐式别名, + * 即子注解和元注解中同时存在类型和名称皆相同的属性时,元注解中属性总是会被该属性覆盖, + * 并且该覆盖关系并不会通过{@link Alias}或{@link Link}被传递到关联的属性中。 + * * @author huangchengxing */ public class CacheableSynthesizedAnnotationAttributeProcessor implements SynthesizedAnnotationAttributeProcessor { private final Table, Object> valueCaches = new RowKeyTable<>(); - private final Comparator annotationComparator; + private final Comparator annotationComparator; /** * 创建一个带缓存的注解值选择器 * * @param annotationComparator 注解比较器,排序更靠前的注解将被优先用于获取值 */ - public CacheableSynthesizedAnnotationAttributeProcessor(Comparator annotationComparator) { + public CacheableSynthesizedAnnotationAttributeProcessor(Comparator annotationComparator) { + Assert.notNull(annotationComparator, "annotationComparator must not null"); this.annotationComparator = annotationComparator; } + /** + * 创建一个带缓存的注解值选择器, + * 默认按{@link SynthesizedAnnotation#getVerticalDistance()}和{@link SynthesizedAnnotation#getHorizontalDistance()}排序, + * 越靠前的越优先被取值。 + */ + public CacheableSynthesizedAnnotationAttributeProcessor() { + this(Hierarchical.DEFAULT_HIERARCHICAL_COMPARATOR); + } + @SuppressWarnings("unchecked") @Override public T getAttributeValue(String attributeName, Class attributeType, Collection synthesizedAnnotations) { @@ -39,7 +54,7 @@ public class CacheableSynthesizedAnnotationAttributeProcessor implements Synthes value = synthesizedAnnotations.stream() .filter(ma -> ma.hasAttribute(attributeName, attributeType)) .min(annotationComparator) - .map(ma -> ma.getAttribute(attributeName)) + .map(ma -> ma.getAttributeValue(attributeName)) .orElse(null); valueCaches.put(attributeName, attributeType, value); return (T)value; diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/CombinationAnnotationElement.java b/hutool-core/src/main/java/cn/hutool/core/annotation/CombinationAnnotationElement.java index e2e901d66..780dc681e 100755 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/CombinationAnnotationElement.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/CombinationAnnotationElement.java @@ -1,19 +1,13 @@ package cn.hutool.core.annotation; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.TableMap; import java.io.Serializable; import java.lang.annotation.Annotation; -import java.lang.annotation.Documented; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; import java.lang.reflect.AnnotatedElement; import java.util.Arrays; import java.util.Collection; import java.util.Map; -import java.util.Set; import java.util.function.Predicate; /** @@ -126,7 +120,9 @@ public class CombinationAnnotationElement implements AnnotatedElement, Serializa // 直接注解 for (Annotation annotation : annotations) { annotationType = annotation.annotationType(); - if (AnnotationUtil.isNotJdkMateAnnotation(annotationType)) { + // issue#I5FQGW@Gitee:跳过元注解和已经处理过的注解,防止递归调用 + if (AnnotationUtil.isNotJdkMateAnnotation(annotationType) + && false == declaredAnnotationMap.containsKey(annotationType)) { if(test(annotation)){ declaredAnnotationMap.put(annotationType, annotation); } @@ -145,7 +141,9 @@ public class CombinationAnnotationElement implements AnnotatedElement, Serializa Class annotationType; for (Annotation annotation : annotations) { annotationType = annotation.annotationType(); - if (AnnotationUtil.isNotJdkMateAnnotation(annotationType)) { + // issue#I5FQGW@Gitee:跳过元注解和已经处理过的注解,防止递归调用 + if (AnnotationUtil.isNotJdkMateAnnotation(annotationType) + && false == declaredAnnotationMap.containsKey(annotationType)) { if(test(annotation)){ annotationMap.put(annotationType, annotation); } diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/ForceAliasFor.java b/hutool-core/src/main/java/cn/hutool/core/annotation/ForceAliasFor.java new file mode 100644 index 000000000..7549a1525 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/ForceAliasFor.java @@ -0,0 +1,35 @@ +package cn.hutool.core.annotation; + +import java.lang.annotation.*; + +/** + *

{@link Link}的子注解。表示“原始属性”将强制作为“关联属性”的别名。效果等同于在“原始属性”上添加{@link Alias}注解, + * 任何情况下,获取“关联属性”的值都将直接返回“原始属性”的值 + * 注意,该注解与{@link Link}、{@link AliasFor}或{@link MirrorFor}一起使用时,将只有被声明在最上面的注解会生效 + * + * @author huangchengxing + * @see Link + * @see RelationType#FORCE_ALIAS_FOR + */ +@Link(type = RelationType.FORCE_ALIAS_FOR) +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +public @interface ForceAliasFor { + + /** + * 产生关联的注解类型,当不指定时,默认指注释的属性所在的类 + * + * @return 关联注解类型 + */ + @Link(annotation = Link.class, attribute = "annotation", type = RelationType.FORCE_ALIAS_FOR) + Class annotation() default Annotation.class; + + /** + * {@link #annotation()}指定注解中关联的属性 + * + * @return 关联的属性 + */ + @Link(annotation = Link.class, attribute = "attribute", type = RelationType.FORCE_ALIAS_FOR) + String attribute() default ""; +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/ForceAliasedAnnotationAttribute.java b/hutool-core/src/main/java/cn/hutool/core/annotation/ForceAliasedAnnotationAttribute.java new file mode 100644 index 000000000..312219c39 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/ForceAliasedAnnotationAttribute.java @@ -0,0 +1,49 @@ +package cn.hutool.core.annotation; + +/** + * 表示一个被指定了强制别名的注解属性。 + * 当调用{@link #getValue()}时,总是返回{@link #linked}的值 + * + * @author huangchengxing + * @see AliasAnnotationPostProcessor + * @see AliasLinkAnnotationPostProcessor + * @see RelationType#ALIAS_FOR + * @see RelationType#FORCE_ALIAS_FOR + */ +public class ForceAliasedAnnotationAttribute extends AbstractWrappedAnnotationAttribute { + + protected ForceAliasedAnnotationAttribute(AnnotationAttribute origin, AnnotationAttribute linked) { + super(origin, linked); + } + + /** + * 总是返回{@link #linked}的{@link AnnotationAttribute#getValue()}的返回值 + * + * @return {@link #linked}的{@link AnnotationAttribute#getValue()}的返回值 + */ + @Override + public Object getValue() { + return linked.getValue(); + } + + /** + * 总是返回{@link #linked}的{@link AnnotationAttribute#isValueEquivalentToDefaultValue()}的返回值 + * + * @return {@link #linked}的{@link AnnotationAttribute#isValueEquivalentToDefaultValue()}的返回值 + */ + @Override + public boolean isValueEquivalentToDefaultValue() { + return linked.isValueEquivalentToDefaultValue(); + } + + /** + * 总是返回{@link #linked}的{@link AnnotationAttribute#getAttributeType()}的返回值 + * + * @return {@link #linked}的{@link AnnotationAttribute#getAttributeType()}的返回值 + */ + @Override + public Class getAttributeType() { + return linked.getAttributeType(); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/GenericSynthesizedAggregateAnnotation.java b/hutool-core/src/main/java/cn/hutool/core/annotation/GenericSynthesizedAggregateAnnotation.java new file mode 100644 index 000000000..8a5f7b7b9 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/GenericSynthesizedAggregateAnnotation.java @@ -0,0 +1,318 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.annotation.scanner.AnnotationScanner; +import cn.hutool.core.annotation.scanner.MetaAnnotationScanner; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.ObjectUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.*; + +/** + * {@link SynthesizedAggregateAnnotation}的基本实现,表示基于多个注解对象, + * 或多个根注解对象与他们的多层元注解对象的聚合得到的注解。 + * + *

假设现有注解A,若指定的{@link #annotationScanner}支持扫描注解A的元注解, + * 且A上存在元注解B,B上存在元注解C,则对注解A进行解析,将得到包含根注解A,以及其元注解B、C在内的合成元注解聚合{@link GenericSynthesizedAggregateAnnotation}。 + * 从{@link AnnotatedElement}的角度来说,得到的合成注解是一个同时承载有ABC三个注解对象的被注解元素, + * 因此通过调用{@link AnnotatedElement}的相关方法将返回对应符合语义的注解对象。 + * + *

在扫描指定根注解及其元注解时,若在不同的层级出现了类型相同的注解实例, + * 将会根据实例化时指定的{@link SynthesizedAnnotationSelector}选择最优的注解, + * 完成对根注解及其元注解的扫描后,合成注解中每种类型的注解对象都将有且仅有一个。
+ * 默认情况下,将使用{@link SynthesizedAnnotationSelector#NEAREST_AND_OLDEST_PRIORITY}作为选择器, + * 此时若出现扫描时得到了多个同类型的注解对象,有且仅有最接近根注解的注解对象会被作为有效注解。 + * + *

当扫描的注解对象经过{@link SynthesizedAnnotationSelector}处理后, + * 将会被转为{@link MetaAnnotation},并使用在实例化时指定的{@link AliasAnnotationPostProcessor} + * 进行后置处理。
+ * 默认情况下,将注册以下后置处理器以对{@link Alias}与{@link Link}和其扩展注解提供支持: + *

    + *
  • {@link AliasAnnotationPostProcessor};
  • + *
  • {@link MirrorLinkAnnotationPostProcessor};
  • + *
  • {@link AliasLinkAnnotationPostProcessor};
  • + *
+ * 若用户需要自行扩展,则需要保证上述三个处理器被正确注入当前实例。 + * + *

{@link GenericSynthesizedAggregateAnnotation}支持通过{@link #getAttributeValue(String, Class)}, + * 或通过{@link #synthesize(Class)}获得注解代理对象后获取指定类型的注解属性值, + * 返回的属性值将根据合成注解中对应原始注解属性上的{@link Alias}与{@link Link}注解而有所变化。 + * 通过当前实例获取属性值时,将经过{@link SynthesizedAnnotationAttributeProcessor}的处理。
+ * 默认情况下,实例将会注册{@link CacheableSynthesizedAnnotationAttributeProcessor}, + * 该处理器将令元注解中与子注解类型与名称皆一致的属性被子注解的属性覆盖,并且缓存最终获取到的属性值。 + * + * @author huangchengxing + * @see AnnotationUtil + * @see SynthesizedAnnotationProxy + * @see SynthesizedAnnotationSelector + * @see SynthesizedAnnotationAttributeProcessor + * @see SynthesizedAnnotationPostProcessor + * @see AnnotationSynthesizer + * @see AnnotationScanner + */ +public class GenericSynthesizedAggregateAnnotation + extends AbstractAnnotationSynthesizer> + implements SynthesizedAggregateAnnotation { + + /** + * 根对象 + */ + private final Object root; + + /** + * 距离根对象的垂直距离 + */ + private final int verticalDistance; + + /** + * 距离根对象的水平距离 + */ + private final int horizontalDistance; + + /** + * 合成注解属性处理器 + */ + private final SynthesizedAnnotationAttributeProcessor attributeProcessor; + + /** + * 基于指定根注解,为其与其元注解的层级结构中的全部注解构造一个合成注解。 + * 当层级结构中出现了相同的注解对象时,将优先选择以距离根注解最近,且优先被扫描的注解对象, + * 当获取值时,同样遵循该规则。 + * + * @param source 源注解 + */ + public GenericSynthesizedAggregateAnnotation(Annotation... source) { + this(Arrays.asList(source), new MetaAnnotationScanner()); + } + + /** + * 基于指定根注解,为其层级结构中的全部注解构造一个合成注解。 + * 若扫描器支持对注解的层级结构进行扫描,则若层级结构中出现了相同的注解对象时, + * 将优先选择以距离根注解最近,且优先被扫描的注解对象,并且当获取注解属性值时同样遵循该规则。 + * + * @param source 源注解 + * @param annotationScanner 注解扫描器,该扫描器必须支持扫描注解类 + */ + public GenericSynthesizedAggregateAnnotation(List source, AnnotationScanner annotationScanner) { + this( + source, SynthesizedAnnotationSelector.NEAREST_AND_OLDEST_PRIORITY, + new CacheableSynthesizedAnnotationAttributeProcessor(), + Arrays.asList( + SynthesizedAnnotationPostProcessor.ALIAS_ANNOTATION_POST_PROCESSOR, + SynthesizedAnnotationPostProcessor.MIRROR_LINK_ANNOTATION_POST_PROCESSOR, + SynthesizedAnnotationPostProcessor.ALIAS_LINK_ANNOTATION_POST_PROCESSOR + ), + annotationScanner + ); + } + + /** + * 基于指定根注解,为其层级结构中的全部注解构造一个合成注解 + * + * @param source 当前查找的注解对象 + * @param annotationSelector 合成注解选择器 + * @param attributeProcessor 注解属性处理器 + * @param annotationPostProcessors 注解后置处理器 + * @param annotationScanner 注解扫描器,该扫描器必须支持扫描注解类 + */ + public GenericSynthesizedAggregateAnnotation( + List source, + SynthesizedAnnotationSelector annotationSelector, + SynthesizedAnnotationAttributeProcessor attributeProcessor, + Collection annotationPostProcessors, + AnnotationScanner annotationScanner) { + this( + null, 0, 0, + source, annotationSelector, attributeProcessor, annotationPostProcessors, annotationScanner + ); + } + + /** + * 基于指定根注解,为其层级结构中的全部注解构造一个合成注解 + * + * @param root 根对象 + * @param verticalDistance 距离根对象的水平距离 + * @param horizontalDistance 距离根对象的垂直距离 + * @param source 当前查找的注解对象 + * @param annotationSelector 合成注解选择器 + * @param attributeProcessor 注解属性处理器 + * @param annotationPostProcessors 注解后置处理器 + * @param annotationScanner 注解扫描器,该扫描器必须支持扫描注解类 + */ + GenericSynthesizedAggregateAnnotation( + Object root, int verticalDistance, int horizontalDistance, + List source, + SynthesizedAnnotationSelector annotationSelector, + SynthesizedAnnotationAttributeProcessor attributeProcessor, + Collection annotationPostProcessors, + AnnotationScanner annotationScanner) { + super(source, annotationSelector, annotationPostProcessors, annotationScanner); + Assert.notNull(attributeProcessor, "attributeProcessor must not null"); + + this.root = ObjectUtil.defaultIfNull(root, this); + this.verticalDistance = verticalDistance; + this.horizontalDistance = horizontalDistance; + this.attributeProcessor = attributeProcessor; + } + + /** + * 获取根对象 + * + * @return 根对象 + */ + @Override + public Object getRoot() { + return root; + } + + /** + * 获取与根对象的垂直距离 + * + * @return 与根对象的垂直距离 + */ + @Override + public int getVerticalDistance() { + return verticalDistance; + } + + /** + * 获取与根对象的水平距离 + * + * @return 获取与根对象的水平距离 + */ + @Override + public int getHorizontalDistance() { + return horizontalDistance; + } + + /** + * 按广度优先扫描{@link #source}上的元注解 + */ + @Override + protected Map, SynthesizedAnnotation> loadAnnotations() { + Map, SynthesizedAnnotation> annotationMap = new LinkedHashMap<>(); + + // 根注解默认水平坐标为0,根注解的元注解坐标从1开始 + for (int i = 0; i < source.size(); i++) { + final Annotation sourceAnnotation = source.get(i); + Assert.isFalse(AnnotationUtil.isSynthesizedAnnotation(sourceAnnotation), "source [{}] has been synthesized"); + annotationMap.put(sourceAnnotation.annotationType(), new MetaAnnotation(sourceAnnotation, sourceAnnotation, 0, i)); + Assert.isTrue( + annotationScanner.support(sourceAnnotation.annotationType()), + "annotation scanner [{}] cannot support scan [{}]", + annotationScanner, sourceAnnotation.annotationType() + ); + annotationScanner.scan( + (index, annotation) -> { + SynthesizedAnnotation oldAnnotation = annotationMap.get(annotation.annotationType()); + SynthesizedAnnotation newAnnotation = new MetaAnnotation(sourceAnnotation, annotation, index + 1, annotationMap.size()); + if (ObjectUtil.isNull(oldAnnotation)) { + annotationMap.put(annotation.annotationType(), newAnnotation); + } else { + annotationMap.put(annotation.annotationType(), annotationSelector.choose(oldAnnotation, newAnnotation)); + } + }, + sourceAnnotation.annotationType(), null + ); + } + return annotationMap; + } + + /** + * 获取合成注解属性处理器 + * + * @return 合成注解属性处理器 + */ + @Override + public SynthesizedAnnotationAttributeProcessor getAnnotationAttributeProcessor() { + return this.attributeProcessor; + } + + /** + * 根据指定的属性名与属性类型获取对应的属性值,若存在{@link Alias}则获取{@link Alias#value()}指定的别名属性的值 + *

当不同层级的注解之间存在同名同类型属性时,将优先获取更接近根注解的属性 + * + * @param attributeName 属性名 + * @param attributeType 属性类型 + * @return 属性 + */ + @Override + public Object getAttributeValue(String attributeName, Class attributeType) { + return attributeProcessor.getAttributeValue(attributeName, attributeType, synthesizedAnnotationMap.values()); + } + + /** + * 获取合成注解中包含的指定注解 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + @Override + public T getAnnotation(Class annotationType) { + return Opt.ofNullable(annotationType) + .map(synthesizedAnnotationMap::get) + .map(SynthesizedAnnotation::getAnnotation) + .map(annotationType::cast) + .orElse(null); + } + + /** + * 当前合成注解中是否存在指定元注解 + * + * @param annotationType 注解类型 + * @return 是否 + */ + @Override + public boolean isAnnotationPresent(Class annotationType) { + return synthesizedAnnotationMap.containsKey(annotationType); + } + + /** + * 获取合成注解中包含的全部注解 + * + * @return 注解对象 + */ + @Override + public Annotation[] getAnnotations() { + return synthesizedAnnotationMap.values().stream() + .map(SynthesizedAnnotation::getAnnotation) + .toArray(Annotation[]::new); + } + + /** + * 若合成注解在存在指定元注解,则使用动态代理生成一个对应的注解实例 + * + * @param annotationType 注解类型 + * @return 合成注解对象 + * @see SynthesizedAnnotationProxy#create(Class, AnnotationAttributeValueProvider, SynthesizedAnnotation) + */ + @Override + public T synthesize(Class annotationType, SynthesizedAnnotation annotation) { + return SynthesizedAnnotationProxy.create(annotationType, this, annotation); + } + + /** + * 注解包装类,表示{@link #source}以及{@link #source}所属层级结构中的全部关联注解对象 + * + * @author huangchengxing + */ + public static class MetaAnnotation extends GenericSynthesizedAnnotation { + + /** + * 创建一个合成注解 + * + * @param root 根对象 + * @param annotation 被合成的注解对象 + * @param verticalDistance 距离根对象的水平距离 + * @param horizontalDistance 距离根对象的垂直距离 + */ + protected MetaAnnotation(Annotation root, Annotation annotation, int verticalDistance, int horizontalDistance) { + super(root, annotation, verticalDistance, horizontalDistance); + } + + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/GenericSynthesizedAnnotation.java b/hutool-core/src/main/java/cn/hutool/core/annotation/GenericSynthesizedAnnotation.java new file mode 100644 index 000000000..303424995 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/GenericSynthesizedAnnotation.java @@ -0,0 +1,197 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ObjectUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * {@link SynthesizedAnnotation}的基本实现 + * + * @param 根对象类型 + * @param 注解类型 + * @author huangchengxing + */ +public class GenericSynthesizedAnnotation implements SynthesizedAnnotation { + + private final R root; + private final T annotation; + private final Map attributeMethodCaches; + private final int verticalDistance; + private final int horizontalDistance; + + /** + * 创建一个合成注解 + * + * @param root 根对象 + * @param annotation 被合成的注解对象 + * @param verticalDistance 距离根对象的水平距离 + * @param horizontalDistance 距离根对象的垂直距离 + */ + protected GenericSynthesizedAnnotation( + R root, T annotation, int verticalDistance, int horizontalDistance) { + this.root = root; + this.annotation = annotation; + this.verticalDistance = verticalDistance; + this.horizontalDistance = horizontalDistance; + this.attributeMethodCaches = new HashMap<>(); + this.attributeMethodCaches.putAll(loadAttributeMethods()); + } + + /** + * 加载注解属性 + * + * @return 注解属性 + */ + protected Map loadAttributeMethods() { + return Stream.of(ClassUtil.getDeclaredMethods(annotation.annotationType())) + .filter(AnnotationUtil::isAttributeMethod) + .collect(Collectors.toMap(Method::getName, method -> new CacheableAnnotationAttribute(annotation, method))); + } + + /** + * 元注解是否存在该属性 + * + * @param attributeName 属性名 + * @return 是否存在该属性 + */ + public boolean hasAttribute(String attributeName) { + return attributeMethodCaches.containsKey(attributeName); + } + + /** + * 元注解是否存在该属性,且该属性的值类型是指定类型或其子类 + * + * @param attributeName 属性名 + * @param returnType 返回值类型 + * @return 是否存在该属性 + */ + @Override + public boolean hasAttribute(String attributeName, Class returnType) { + return Opt.ofNullable(attributeMethodCaches.get(attributeName)) + .filter(method -> ClassUtil.isAssignable(returnType, method.getAttributeType())) + .isPresent(); + } + + /** + * 获取该注解的全部属性 + * + * @return 注解属性 + */ + @Override + public Map getAttributes() { + return this.attributeMethodCaches; + } + + /** + * 设置属性值 + * + * @param attributeName 属性名称 + * @param attribute 注解属性 + */ + @Override + public void setAttribute(String attributeName, AnnotationAttribute attribute) { + attributeMethodCaches.put(attributeName, attribute); + } + + /** + * 替换属性值 + * + * @param attributeName 属性名 + * @param operator 替换操作 + */ + @Override + public void replaceAttribute(String attributeName, UnaryOperator operator) { + AnnotationAttribute old = attributeMethodCaches.get(attributeName); + if (ObjectUtil.isNotNull(old)) { + attributeMethodCaches.put(attributeName, operator.apply(old)); + } + } + + /** + * 获取属性值 + * + * @param attributeName 属性名 + * @return 属性值 + */ + @Override + public Object getAttributeValue(String attributeName) { + return Opt.ofNullable(attributeMethodCaches.get(attributeName)) + .map(AnnotationAttribute::getValue) + .get(); + } + + /** + * 获取该合成注解对应的根节点 + * + * @return 合成注解对应的根节点 + */ + @Override + public R getRoot() { + return root; + } + + /** + * 获取被合成的注解对象 + * + * @return 注解对象 + */ + @Override + public T getAnnotation() { + return annotation; + } + + /** + * 获取该合成注解与根对象的垂直距离。 + * 默认情况下,该距离即为当前注解与根对象之间相隔的层级数。 + * + * @return 合成注解与根对象的垂直距离 + */ + @Override + public int getVerticalDistance() { + return verticalDistance; + } + + /** + * 获取该合成注解与根对象的水平距离。 + * 默认情况下,该距离即为当前注解与根对象之间相隔的已经被扫描到的注解数。 + * + * @return 合成注解与根对象的水平距离 + */ + @Override + public int getHorizontalDistance() { + return horizontalDistance; + } + + /** + * 获取被合成的注解类型 + * + * @return 被合成的注解类型 + */ + @Override + public Class annotationType() { + return annotation.annotationType(); + } + + /** + * 获取注解属性值 + * + * @param attributeName 属性名称 + * @param attributeType 属性类型 + * @return 注解属性值 + */ + @Override + public Object getAttributeValue(String attributeName, Class attributeType) { + return Opt.ofNullable(attributeMethodCaches.get(attributeName)) + .filter(method -> ClassUtil.isAssignable(attributeType, method.getAttributeType())) + .map(AnnotationAttribute::getValue) + .get(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/Hierarchical.java b/hutool-core/src/main/java/cn/hutool/core/annotation/Hierarchical.java new file mode 100644 index 000000000..00c993822 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/Hierarchical.java @@ -0,0 +1,155 @@ +package cn.hutool.core.annotation; + + +import java.util.Comparator; + +/** + *

描述以一个参照物为对象,存在于该参照物的层级结构中的对象。 + * + *

该对象可通过{@link #getVerticalDistance()}与{@link #getHorizontalDistance()} + * 描述其在以参照物为基点的坐标坐标系中的位置。
+ * 在需要对该接口的实现类进行按优先级排序时,距离{@link #getRoot()}对象越近,则该实现类的优先级越高。 + * 默认提供了{@link #DEFAULT_HIERARCHICAL_COMPARATOR}用于实现该比较规则。
+ * 一般情况下,{@link #getRoot()}返回值相同的对象之间的比较才有意义。 + * + *

此外,还提供了{@link Selector}接口用于根据一定的规则从两个{@link Hierarchical}实现类中选择并返回一个最合适的对象, + * 默认提供了四个实现类: + *

    + *
  • {@link Selector#NEAREST_AND_OLDEST_PRIORITY}: 返回距离根对象更近的对象,当距离一样时优先返回旧对象;
  • + *
  • {@link Selector#NEAREST_AND_NEWEST_PRIORITY}: 返回距离根对象更近的对象,当距离一样时优先返回新对象;
  • + *
  • {@link Selector#FARTHEST_AND_OLDEST_PRIORITY}: 返回距离根对象更远的对象,当距离一样时优先返回旧对象;
  • + *
  • {@link Selector#FARTHEST_AND_NEWEST_PRIORITY}: 返回距离根对象更远的对象,当距离一样时优先返回新对象;
  • + *
+ * + * @author huangchengxing + */ +public interface Hierarchical extends Comparable { + + // ====================== compare ====================== + + /** + * 默认{@link #getHorizontalDistance()}与{@link #getVerticalDistance()}排序的比较器 + */ + Comparator DEFAULT_HIERARCHICAL_COMPARATOR = Comparator + .comparing(Hierarchical::getVerticalDistance) + .thenComparing(Hierarchical::getHorizontalDistance); + + /** + * 按{@link #getVerticalDistance()}和{@link #getHorizontalDistance()}排序 + * + * @param o {@link SynthesizedAnnotation}对象 + * @return 比较值 + */ + @Override + default int compareTo(Hierarchical o) { + return DEFAULT_HIERARCHICAL_COMPARATOR.compare(this, o); + } + + // ====================== hierarchical ====================== + + /** + * 参照物,即坐标为{@code (0, 0)}的对象。 + * 当对象本身即为参照物时,该方法应当返回其本身 + * + * @return 参照物 + */ + Object getRoot(); + + /** + * 获取该对象与参照物的垂直距离。 + * 默认情况下,该距离即为当前对象与参照物之间相隔的层级数。 + * + * @return 合成注解与根对象的垂直距离 + */ + int getVerticalDistance(); + + /** + * 获取该对象与参照物的水平距离。 + * 默认情况下,该距离即为当前对象在与参照物{@link #getVerticalDistance()}相同的情况下条, + * 该对象被扫描到的顺序。 + * + * @return 合成注解与根对象的水平距离 + */ + int getHorizontalDistance(); + + // ====================== selector ====================== + + /** + * {@link Hierarchical}选择器,用于根据一定的规则从两个{@link Hierarchical}实现类中选择并返回一个最合适的对象 + */ + @FunctionalInterface + interface Selector { + + /** + * 返回距离根对象更近的对象,当距离一样时优先返回旧对象 + */ + Selector NEAREST_AND_OLDEST_PRIORITY = new NearestAndOldestPrioritySelector(); + + /** + * 返回距离根对象更近的对象,当距离一样时优先返回新对象 + */ + Selector NEAREST_AND_NEWEST_PRIORITY = new NearestAndNewestPrioritySelector(); + + /** + * 返回距离根对象更远的对象,当距离一样时优先返回旧对象 + */ + Selector FARTHEST_AND_OLDEST_PRIORITY = new FarthestAndOldestPrioritySelector(); + + /** + * 返回距离根对象更远的对象,当距离一样时优先返回新对象 + */ + Selector FARTHEST_AND_NEWEST_PRIORITY = new FarthestAndNewestPrioritySelector(); + + /** + * 比较两个被合成的对象,选择其中的一个并返回 + * + * @param 复合注解类型 + * @param prev 上一对象,该参数不允许为空 + * @param next 下一对象,该参数不允许为空 + * @return 对象 + */ + T choose(T prev, T next); + + /** + * 返回距离根对象更近的注解,当距离一样时优先返回旧注解 + */ + class NearestAndOldestPrioritySelector implements Selector { + @Override + public T choose(T oldAnnotation, T newAnnotation) { + return newAnnotation.getVerticalDistance() < oldAnnotation.getVerticalDistance() ? newAnnotation : oldAnnotation; + } + } + + /** + * 返回距离根对象更近的注解,当距离一样时优先返回新注解 + */ + class NearestAndNewestPrioritySelector implements Selector { + @Override + public T choose(T oldAnnotation, T newAnnotation) { + return newAnnotation.getVerticalDistance() <= oldAnnotation.getVerticalDistance() ? newAnnotation : oldAnnotation; + } + } + + /** + * 返回距离根对象更远的注解,当距离一样时优先返回旧注解 + */ + class FarthestAndOldestPrioritySelector implements Selector { + @Override + public T choose(T oldAnnotation, T newAnnotation) { + return newAnnotation.getVerticalDistance() > oldAnnotation.getVerticalDistance() ? newAnnotation : oldAnnotation; + } + } + + /** + * 返回距离根对象更远的注解,当距离一样时优先返回新注解 + */ + class FarthestAndNewestPrioritySelector implements Selector { + @Override + public T choose(T oldAnnotation, T newAnnotation) { + return newAnnotation.getVerticalDistance() >= oldAnnotation.getVerticalDistance() ? newAnnotation : oldAnnotation; + } + } + + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/Link.java b/hutool-core/src/main/java/cn/hutool/core/annotation/Link.java new file mode 100644 index 000000000..8f4e20f3f --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/Link.java @@ -0,0 +1,49 @@ +package cn.hutool.core.annotation; + +import java.lang.annotation.*; + +/** + *

用于在同一注解中,或具有一定关联的不同注解的属性中,表明这些属性之间具有特定的关联关系。 + * 在通过{@link SynthesizedAggregateAnnotation}获取合成注解后,合成注解获取属性值时会根据该注解进行调整。
+ * + *

该注解存在三个字注解:{@link MirrorFor}、{@link ForceAliasFor}或{@link AliasFor}, + * 使用三个子注解等同于{@link Link}。但是需要注意的是, + * 当注解中的属性同时存在多个{@link Link}或基于{@link Link}的子注解时, + * 仅有声明在被注解的属性最上方的注解会生效,其余注解都将被忽略。 + * + * 注意:该注解的优先级低于{@link Alias} + * + * @author huangchengxing + * @see SynthesizedAggregateAnnotation + * @see RelationType + * @see AliasFor + * @see MirrorFor + * @see ForceAliasFor + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +public @interface Link { + + /** + * 产生关联的注解类型,当不指定时,默认指注释的属性所在的类 + * + * @return 关联的注解类型 + */ + Class annotation() default Annotation.class; + + /** + * {@link #annotation()}指定注解中关联的属性 + * + * @return 属性名 + */ + String attribute() default ""; + + /** + * {@link #attribute()}指定属性与当前注解的属性建的关联关系类型 + * + * @return 关系类型 + */ + RelationType type() default RelationType.MIRROR_FOR; + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/MirrorFor.java b/hutool-core/src/main/java/cn/hutool/core/annotation/MirrorFor.java new file mode 100644 index 000000000..7d69b34a0 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/MirrorFor.java @@ -0,0 +1,42 @@ +package cn.hutool.core.annotation; + +import java.lang.annotation.*; + +/** + *

{@link Link}的子注解。表示注解的属性与指定的属性互为镜像,通过一个属性将能够获得对方的值。
+ * 它们遵循下述规则: + *

    + *
  • 互为镜像的两个属性,必须同时通过指定模式为{@code MIRROR_FOR}的{@link Link}注解指定对方;
  • + *
  • 互为镜像的两个属性,类型必须一致;
  • + *
  • 互为镜像的两个属性在获取值,且两者的值皆不同时,必须且仅允许有一个非默认值,该值被优先返回;
  • + *
  • 互为镜像的两个属性,在值都为默认值或都不为默认值时,两者的值必须相等;
  • + *
+ * 注意,该注解与{@link Link}、{@link ForceAliasFor}或{@link AliasFor}一起使用时,将只有被声明在最上面的注解会生效 + * + * @author huangchengxing + * @see Link + * @see RelationType#MIRROR_FOR + */ +@Link(type = RelationType.MIRROR_FOR) +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +public @interface MirrorFor { + + /** + * 产生关联的注解类型,当不指定时,默认指注释的属性所在的类 + * + * @return 关联的注解类型 + */ + @Link(annotation = Link.class, attribute = "annotation", type = RelationType.FORCE_ALIAS_FOR) + Class annotation() default Annotation.class; + + /** + * {@link #annotation()}指定注解中关联的属性 + * + * @return 属性名 + */ + @Link(annotation = Link.class, attribute = "attribute", type = RelationType.FORCE_ALIAS_FOR) + String attribute() default ""; + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/MirrorLinkAnnotationPostProcessor.java b/hutool-core/src/main/java/cn/hutool/core/annotation/MirrorLinkAnnotationPostProcessor.java new file mode 100644 index 000000000..1fc0a203b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/MirrorLinkAnnotationPostProcessor.java @@ -0,0 +1,132 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.ObjectUtil; + +/** + *

用于处理注解对象中带有{@link Link}注解,且{@link Link#type()}为{@link RelationType#MIRROR_FOR}的属性。
+ * 当该处理器执行完毕后,原始合成注解中被{@link Link}注解的属性与{@link Link}注解指向的目标注解的属性, + * 都将会被被包装并替换为{@link MirroredAnnotationAttribute}。 + * + * @author huangchengxing + * @see RelationType#MIRROR_FOR + * @see MirroredAnnotationAttribute + */ +public class MirrorLinkAnnotationPostProcessor extends AbstractLinkAnnotationPostProcessor { + + private static final RelationType[] PROCESSED_RELATION_TYPES = new RelationType[]{ RelationType.MIRROR_FOR }; + + @Override + public int order() { + return Integer.MIN_VALUE + 1; + } + + /** + * 该处理器只处理{@link Link#type()}类型为{@link RelationType#MIRROR_FOR}的注解属性 + * + * @return 仅有{@link RelationType#MIRROR_FOR}数组 + */ + @Override + protected RelationType[] processTypes() { + return PROCESSED_RELATION_TYPES; + } + + /** + * 将存在镜像关系的合成注解属性分别包装为{@link MirroredAnnotationAttribute}对象, + * 并使用包装后{@link MirroredAnnotationAttribute}替换在它们对应合成注解实例中的{@link AnnotationAttribute} + * + * @param synthesizer 注解合成器 + * @param annotation {@code originalAttribute}上的{@link Link}注解对象 + * @param originalAnnotation 当前正在处理的{@link SynthesizedAnnotation}对象 + * @param originalAttribute {@code originalAnnotation}上的待处理的属性 + * @param linkedAnnotation {@link Link}指向的关联注解对象 + * @param linkedAttribute {@link Link}指向的{@code originalAnnotation}中的关联属性,该参数可能为空 + */ + @Override + protected void processLinkedAttribute( + AnnotationSynthesizer synthesizer, Link annotation, + SynthesizedAnnotation originalAnnotation, AnnotationAttribute originalAttribute, + SynthesizedAnnotation linkedAnnotation, AnnotationAttribute linkedAttribute) { + + // 镜像属性必然成对出现,因此此处必定存在三种情况: + // 1.两属性都不为镜像属性,此时继续进行后续处理; + // 2.两属性都为镜像属性,并且指向对方,此时无需后续处理; + // 3.两属性仅有任意一属性为镜像属性,此时镜像属性必然未指向当前原始属性,此时应该抛出异常; + if (originalAttribute instanceof MirroredAnnotationAttribute + || linkedAttribute instanceof MirroredAnnotationAttribute) { + checkMirrored(originalAttribute, linkedAttribute); + return; + } + + // 校验镜像关系 + checkMirrorRelation(annotation, originalAttribute, linkedAttribute); + // 包装这一对镜像属性,并替换原注解中的对应属性 + final AnnotationAttribute mirroredOriginalAttribute = new MirroredAnnotationAttribute(originalAttribute, linkedAttribute); + originalAnnotation.setAttribute(originalAttribute.getAttributeName(), mirroredOriginalAttribute); + final AnnotationAttribute mirroredTargetAttribute = new MirroredAnnotationAttribute(linkedAttribute, originalAttribute); + linkedAnnotation.setAttribute(annotation.attribute(), mirroredTargetAttribute); + } + + /** + * 检查映射关系是否正确 + */ + private void checkMirrored(AnnotationAttribute original, AnnotationAttribute mirror) { + final boolean originalAttributeMirrored = original instanceof MirroredAnnotationAttribute; + final boolean mirrorAttributeMirrored = mirror instanceof MirroredAnnotationAttribute; + + // 校验通过 + final boolean passed = originalAttributeMirrored && mirrorAttributeMirrored + && ObjectUtil.equals(((MirroredAnnotationAttribute)original).getLinked(), ((MirroredAnnotationAttribute)mirror).getOriginal()); + if (passed) { + return; + } + + // 校验失败,拼装异常信息用于抛出异常 + String errorMsg; + // 原始字段已经跟其他字段形成镜像 + if (originalAttributeMirrored && !mirrorAttributeMirrored) { + errorMsg = CharSequenceUtil.format( + "attribute [{}] cannot mirror for [{}], because it's already mirrored for [{}]", + original.getAttribute(), mirror.getAttribute(), ((MirroredAnnotationAttribute)original).getLinked() + ); + } + // 镜像字段已经跟其他字段形成镜像 + else if (!originalAttributeMirrored && mirrorAttributeMirrored) { + errorMsg = CharSequenceUtil.format( + "attribute [{}] cannot mirror for [{}], because it's already mirrored for [{}]", + mirror.getAttribute(), original.getAttribute(), ((MirroredAnnotationAttribute)mirror).getLinked() + ); + } + // 两者都形成了镜像,但是都未指向对方,理论上不会存在该情况 + else { + errorMsg = CharSequenceUtil.format( + "attribute [{}] cannot mirror for [{}], because [{}] already mirrored for [{}] and [{}] already mirrored for [{}]", + mirror.getAttribute(), original.getAttribute(), + mirror.getAttribute(), ((MirroredAnnotationAttribute)mirror).getLinked(), + original.getAttribute(), ((MirroredAnnotationAttribute)original).getLinked() + ); + } + + throw new IllegalArgumentException(errorMsg); + } + + /** + * 基本校验 + */ + private void checkMirrorRelation(Link annotation, AnnotationAttribute original, AnnotationAttribute mirror) { + // 镜像属性必须存在 + checkLinkedAttributeNotNull(original, mirror, annotation); + // 镜像属性返回值必须一致 + checkAttributeType(original, mirror); + // 镜像属性上必须存在对应的注解 + final Link mirrorAttributeAnnotation = getLinkAnnotation(mirror, RelationType.MIRROR_FOR); + Assert.isTrue( + ObjectUtil.isNotNull(mirrorAttributeAnnotation) && RelationType.MIRROR_FOR.equals(mirrorAttributeAnnotation.type()), + "mirror attribute [{}] of original attribute [{}] must marked by @Link, and also @LinkType.type() must is [{}]", + mirror.getAttribute(), original.getAttribute(), RelationType.MIRROR_FOR + ); + checkLinkedSelf(original, mirror); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/MirroredAnnotationAttribute.java b/hutool-core/src/main/java/cn/hutool/core/annotation/MirroredAnnotationAttribute.java new file mode 100644 index 000000000..6bb800079 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/MirroredAnnotationAttribute.java @@ -0,0 +1,48 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.lang.Assert; + +/** + * 表示存在对应镜像属性的注解属性,当获取值时将根据{@link RelationType#MIRROR_FOR}的规则进行处理 + * + * @author huangchengxing + * @see MirrorLinkAnnotationPostProcessor + * @see RelationType#MIRROR_FOR + */ +public class MirroredAnnotationAttribute extends AbstractWrappedAnnotationAttribute { + + public MirroredAnnotationAttribute(AnnotationAttribute origin, AnnotationAttribute linked) { + super(origin, linked); + } + + @Override + public Object getValue() { + final boolean originIsDefault = original.isValueEquivalentToDefaultValue(); + final boolean targetIsDefault = linked.isValueEquivalentToDefaultValue(); + final Object originValue = original.getValue(); + final Object targetValue = linked.getValue(); + + // 都为默认值,或都为非默认值时,两方法的返回值必须相等 + if (originIsDefault == targetIsDefault) { + Assert.equals( + originValue, targetValue, + "the values of attributes [{}] and [{}] that mirror each other are different: [{}] <==> [{}]", + original.getAttribute(), linked.getAttribute(), originValue, targetValue + ); + return originValue; + } + + // 两者有一者不为默认值时,优先返回非默认值 + return originIsDefault ? targetValue : originValue; + } + + /** + * 当{@link #original}与{@link #linked}都为默认值时返回{@code true} + * + * @return 是否 + */ + @Override + public boolean isValueEquivalentToDefaultValue() { + return original.isValueEquivalentToDefaultValue() && linked.isValueEquivalentToDefaultValue(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/RelationType.java b/hutool-core/src/main/java/cn/hutool/core/annotation/RelationType.java new file mode 100644 index 000000000..1c2248cf3 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/RelationType.java @@ -0,0 +1,50 @@ +package cn.hutool.core.annotation; + +/** + *

注解属性的关系类型
+ * 若将被{@link Link}注解的属性称为“原始属性”,而在{@link Link}注解中指向的注解属性称为“关联属性”, + * 则该枚举用于描述“原始属性”与“关联属性”在{@link SynthesizedAggregateAnnotation}处理过程中的作用关系。
+ * 根据在{@link Link#type()}中指定的关系类型的不同,通过{@link SynthesizedAggregateAnnotation}合成的注解的属性值也将有所变化。 + * + *

当一个注解中的所有属性同时具备多种关系时,将依次按下述顺序处理: + *

    + *
  1. 属性上的{@link Alias}注解;
  2. + *
  3. 属性上的{@link Link}注解,且{@link Link#type()}为{@link #MIRROR_FOR};
  4. + *
  5. 属性上的{@link Link}注解,且{@link Link#type()}为{@link #FORCE_ALIAS_FOR};
  6. + *
  7. 属性上的{@link Link}注解,且{@link Link#type()}为{@link #ALIAS_FOR};
  8. + *
+ * + * @author huangchengxing + * @see SynthesizedAggregateAnnotation + * @see Link + */ +public enum RelationType { + + /** + *

表示注解的属性与指定的属性互为镜像,通过一个属性将能够获得对方的值。
+ * 它们遵循下述规则: + *

    + *
  • 互为镜像的两个属性,必须同时通过指定模式为{@code MIRROR_FOR}的{@link Link}注解指定对方;
  • + *
  • 互为镜像的两个属性,类型必须一致;
  • + *
  • 互为镜像的两个属性在获取值,且两者的值皆不同时,必须且仅允许有一个非默认值,该值被优先返回;
  • + *
  • 互为镜像的两个属性,在值都为默认值或都不为默认值时,两者的值必须相等;
  • + *
+ */ + MIRROR_FOR, + + /** + *

表示“原始属性”将作为“关联属性”的别名。 + *

    + *
  • 当“原始属性”为默认值时,获取“关联属性”将返回“关联属性”本身的值;
  • + *
  • 当“原始属性”不为默认值时,获取“关联属性”将返回“原始属性”的值;
  • + *
+ */ + ALIAS_FOR, + + /** + *

表示“原始属性”将强制作为“关联属性”的别名。效果等同于在“原始属性”上添加{@link Alias}注解, + * 任何情况下,获取“关联属性”的值都将直接返回“原始属性”的值 + */ + FORCE_ALIAS_FOR; + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAggregateAnnotation.java b/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAggregateAnnotation.java new file mode 100644 index 000000000..e006f3f08 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAggregateAnnotation.java @@ -0,0 +1,102 @@ +package cn.hutool.core.annotation; + +import java.lang.annotation.Annotation; + +/** + *

表示基于特定规则聚合,将一组注解聚合而来的注解对象, + * 该注解对象允许根据一定规则“合成”一些跟原始注解属性不一样合成注解。 + * + *

合成注解一般被用于处理类层级结果中具有直接或间接关联的注解对象, + * 当实例被创建时,会获取到这些注解对象,并使用{@link SynthesizedAnnotationSelector}对类型相同的注解进行过滤, + * 并最终得到类型不重复的有效注解对象。这些有效注解将被包装为{@link SynthesizedAnnotation}, + * 然后最终用于“合成”一个{@link SynthesizedAggregateAnnotation}。
+ * {@link SynthesizedAnnotationSelector}是合成注解生命周期中的第一个钩子, + * 自定义选择器以拦截原始注解被扫描的过程。 + * + *

当合成注解完成对待合成注解的扫描,并完成了必要属性的加载后, + * 将会按顺序依次调用{@link SynthesizedAnnotationPostProcessor}, + * 注解后置处理器允许用于对完成注解的待合成注解进行二次调整, + * 该钩子一般用于根据{@link Link}注解对属性进行调整。
+ * {@link SynthesizedAnnotationPostProcessor}是合成注解生命周期中的第二个钩子, + * 自定义后置处理器以拦截原始在转为待合成注解后的初始化过程。 + * + *

合成注解允许通过{@link #synthesize(Class)}合成一个指定的注解对象, + * 该方法返回的注解对象可能是原始的注解对象,也有可能通过动态代理的方式生成, + * 该对象实例的属性不一定来自对象本身,而是来自于经过{@link SynthesizedAnnotationAttributeProcessor} + * 处理后的、用于合成当前实例的全部关联注解的相关属性。
+ * {@link SynthesizedAnnotationAttributeProcessor}是合成注解生命周期中的第三个钩子, + * 自定义属性处理器以拦截合成注解的取值过程。 + * + * @author huangchengxing + * @see AnnotationSynthesizer + * @see SynthesizedAnnotation + * @see SynthesizedAnnotationSelector + * @see SynthesizedAnnotationAttributeProcessor + * @see SynthesizedAnnotationPostProcessor + * @see GenericSynthesizedAggregateAnnotation + */ +public interface SynthesizedAggregateAnnotation extends AggregateAnnotation, Hierarchical, AnnotationSynthesizer, AnnotationAttributeValueProvider { + + // ================== hierarchical ================== + + /** + * 距离{@link #getRoot()}返回值的垂直距离, + * 默认聚合注解即为根对象,因此返回0 + * + * @return 距离{@link #getRoot()}返回值的水平距离, + */ + @Override + default int getVerticalDistance() { + return 0; + } + + /** + * 距离{@link #getRoot()}返回值的水平距离, + * 默认聚合注解即为根对象,因此返回0 + * + * @return 距离{@link #getRoot()}返回值的水平距离, + */ + @Override + default int getHorizontalDistance() { + return 0; + } + + // ================== synthesize ================== + + /** + * 获取在聚合中存在的指定注解对象 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + T getAnnotation(Class annotationType); + + /** + * 获取合成注解属性处理器 + * + * @return 合成注解属性处理器 + */ + SynthesizedAnnotationAttributeProcessor getAnnotationAttributeProcessor(); + + /** + * 获取当前的注解类型 + * + * @return 注解类型 + */ + @Override + default Class annotationType() { + return this.getClass(); + } + + /** + * 从聚合中获取指定类型的属性值 + * + * @param attributeName 属性名称 + * @param attributeType 属性类型 + * @return 属性值 + */ + @Override + Object getAttributeValue(String attributeName, Class attributeType); + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotation.java b/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotation.java index 62a3dc91a..418059974 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotation.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotation.java @@ -1,30 +1,21 @@ package cn.hutool.core.annotation; +import cn.hutool.core.collection.CollUtil; + import java.lang.annotation.Annotation; +import java.util.Map; +import java.util.function.UnaryOperator; /** - * 用于在{@link SyntheticAnnotation}中表示一个处于合成状态的注解对象 + *

用于在{@link SynthesizedAggregateAnnotation}中表示一个处于合成状态的注解对象。
+ * 当对多个合成注解排序时,默认使用{@link #DEFAULT_HIERARCHICAL_COMPARATOR}进行排序, + * 从保证合成注解按{@link #getVerticalDistance()}与{@link #getHorizontalDistance()}的返回值保持有序, + * 从而使得距离根元素更接近的注解对象在被处理是具有更高的优先级。 * * @author huangchengxing - * @see SyntheticAnnotation + * @see SynthesizedAggregateAnnotation */ -public interface SynthesizedAnnotation extends Annotation { - - /** - * 获取该合成注解对应的根节点 - * - * @return 合成注解对应的根节点 - */ - Object getRoot(); - - /** - * 该合成注解是为根对象 - * - * @return 根对象 - */ - default boolean isRoot() { - return getRoot() == this; - } +public interface SynthesizedAnnotation extends Annotation, Hierarchical, AnnotationAttributeValueProvider { /** * 获取被合成的注解对象 @@ -39,6 +30,7 @@ public interface SynthesizedAnnotation extends Annotation { * * @return 合成注解与根对象的垂直距离 */ + @Override int getVerticalDistance(); /** @@ -47,6 +39,7 @@ public interface SynthesizedAnnotation extends Annotation { * * @return 合成注解与根对象的水平距离 */ + @Override int getHorizontalDistance(); /** @@ -58,12 +51,46 @@ public interface SynthesizedAnnotation extends Annotation { */ boolean hasAttribute(String attributeName, Class returnType); + /** + * 获取该注解的全部属性 + * + * @return 注解属性 + */ + Map getAttributes(); + + /** + * 设置该注解的全部属性 + * + * @param attributes 注解属性 + */ + default void setAttributes(Map attributes) { + if (CollUtil.isNotEmpty(attributes)) { + attributes.forEach(this::setAttribute); + } + } + + /** + * 设置属性值 + * + * @param attributeName 属性名称 + * @param attribute 注解属性 + */ + void setAttribute(String attributeName, AnnotationAttribute attribute); + + /** + * 替换属性值 + * + * @param attributeName 属性名 + * @param operator 替换操作 + */ + void replaceAttribute(String attributeName, UnaryOperator operator); + /** * 获取属性值 * * @param attributeName 属性名 * @return 属性值 */ - Object getAttribute(String attributeName); + Object getAttributeValue(String attributeName); } diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotationAttributeProcessor.java b/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotationAttributeProcessor.java index ad0464f5b..7a7debdc1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotationAttributeProcessor.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotationAttributeProcessor.java @@ -3,7 +3,7 @@ package cn.hutool.core.annotation; import java.util.Collection; /** - * 合成注解属性选择器。用于在{@link SyntheticAnnotation}中从指定类型的合成注解里获取到对应的属性值 + * 合成注解属性选择器。用于在{@link SynthesizedAggregateAnnotation}中从指定类型的合成注解里获取到对应的属性值 * * @author huangchengxing */ diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotationPostProcessor.java b/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotationPostProcessor.java new file mode 100644 index 000000000..91e701be8 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotationPostProcessor.java @@ -0,0 +1,71 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.comparator.CompareUtil; + +import java.util.Comparator; + +/** + *

被合成注解后置处理器,用于在{@link SynthesizedAggregateAnnotation}加载完所有待合成注解后, + * 再对加载好的{@link SynthesizedAnnotation}进行后置处理。
+ * 当多个{@link SynthesizedAnnotationPostProcessor}需要一起执行时,将按照{@link #order()}的返回值进行排序, + * 该值更小的处理器将被优先执行。 + * + *

该接口存在多个实现类,调用者应当保证在任何时候,对一批后置处理器的调用顺序都符合: + *

    + *
  • {@link AliasAnnotationPostProcessor};
  • + *
  • {@link MirrorLinkAnnotationPostProcessor};
  • + *
  • {@link AliasLinkAnnotationPostProcessor};
  • + *
  • 其他后置处理器;
  • + *
+ * + * @author huangchengxing + * @see AliasAnnotationPostProcessor + * @see MirrorLinkAnnotationPostProcessor + * @see AliasLinkAnnotationPostProcessor + */ +public interface SynthesizedAnnotationPostProcessor extends Comparable { + + /** + * 属性上带有{@link Alias}的注解对象的后置处理器 + */ + AliasAnnotationPostProcessor ALIAS_ANNOTATION_POST_PROCESSOR = new AliasAnnotationPostProcessor(); + + /** + * 属性上带有{@link Link},且与其他注解的属性存在镜像关系的注解对象的后置处理器 + */ + MirrorLinkAnnotationPostProcessor MIRROR_LINK_ANNOTATION_POST_PROCESSOR = new MirrorLinkAnnotationPostProcessor(); + + /** + * 属性上带有{@link Link},且与其他注解的属性存在别名关系的注解对象的后置处理器 + */ + AliasLinkAnnotationPostProcessor ALIAS_LINK_ANNOTATION_POST_PROCESSOR = new AliasLinkAnnotationPostProcessor(); + + /** + * 在一组后置处理器中被调用的顺序,越小越靠前 + * + * @return 排序值 + */ + default int order() { + return Integer.MAX_VALUE; + } + + /** + * 比较两个后置处理器的{@link #order()}返回值 + * + * @param o 比较对象 + * @return 大小 + */ + @Override + default int compareTo(SynthesizedAnnotationPostProcessor o) { + return CompareUtil.compare(this, o, Comparator.comparing(SynthesizedAnnotationPostProcessor::order)); + } + + /** + * 给定指定被合成注解与其所属的合成注解聚合器实例,经过处理后返回最终 + * + * @param synthesizedAnnotation 合成的注解 + * @param synthesizer 注解合成器 + */ + void process(SynthesizedAnnotation synthesizedAnnotation, AnnotationSynthesizer synthesizer); + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotationProxy.java b/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotationProxy.java new file mode 100644 index 000000000..233d84940 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotationProxy.java @@ -0,0 +1,160 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 合成注解代理类,用于为{@link SynthesizedAnnotation}生成对应的合成注解代理对象 + * + * @author huangchengxing + * @see SynthesizedAnnotation + * @see AnnotationAttributeValueProvider + */ +public class SynthesizedAnnotationProxy implements InvocationHandler { + + private final AnnotationAttributeValueProvider annotationAttributeValueProvider; + private final SynthesizedAnnotation annotation; + private final Map> methods; + + /** + * 创建一个代理注解,生成的代理对象将是{@link SyntheticProxyAnnotation}与指定的注解类的子类。 + * + * @param 注解类型 + * @param annotationType 注解类型 + * @param annotationAttributeValueProvider 注解属性值获取器 + * @param annotation 合成注解 + * @return 代理注解 + */ + @SuppressWarnings("unchecked") + public static T create( + Class annotationType, + AnnotationAttributeValueProvider annotationAttributeValueProvider, + SynthesizedAnnotation annotation) { + if (ObjectUtil.isNull(annotation)) { + return null; + } + final SynthesizedAnnotationProxy proxyHandler = new SynthesizedAnnotationProxy(annotationAttributeValueProvider, annotation); + if (ObjectUtil.isNull(annotation)) { + return null; + } + return (T) Proxy.newProxyInstance( + annotationType.getClassLoader(), + new Class[]{annotationType, SyntheticProxyAnnotation.class}, + proxyHandler + ); + } + + /** + * 创建一个代理注解,生成的代理对象将是{@link SyntheticProxyAnnotation}与指定的注解类的子类。 + * + * @param 注解类型 + * @param annotationType 注解类型 + * @param annotation 合成注解 + * @return 代理注解 + */ + public static T create( + Class annotationType, SynthesizedAnnotation annotation) { + return create(annotationType, annotation, annotation); + } + + /** + * 该类是否为通过{@code SynthesizedAnnotationProxy}生成的代理类 + * + * @param annotationType 注解类型 + * @return 是否 + */ + public static boolean isProxyAnnotation(Class annotationType) { + return ClassUtil.isAssignable(SyntheticProxyAnnotation.class, annotationType); + } + + SynthesizedAnnotationProxy(AnnotationAttributeValueProvider annotationAttributeValueProvider, SynthesizedAnnotation annotation) { + Assert.notNull(annotationAttributeValueProvider, "annotationAttributeValueProvider must not null"); + Assert.notNull(annotation, "annotation must not null"); + this.annotationAttributeValueProvider = annotationAttributeValueProvider; + this.annotation = annotation; + this.methods = new HashMap<>(9); + loadMethods(); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return Opt.ofNullable(methods.get(method.getName())) + .map(m -> m.apply(method, args)) + .orElseGet(() -> ReflectUtil.invoke(this, method, args)); + } + + // ========================= 代理方法 ========================= + + void loadMethods() { + methods.put("toString", (method, args) -> proxyToString()); + methods.put("hashCode", (method, args) -> proxyHashCode()); + methods.put("getSynthesizedAnnotation", (method, args) -> proxyGetSynthesizedAnnotation()); + methods.put("getRoot", (method, args) -> annotation.getRoot()); + methods.put("getVerticalDistance", (method, args) -> annotation.getVerticalDistance()); + methods.put("getHorizontalDistance", (method, args) -> annotation.getHorizontalDistance()); + methods.put("hasAttribute", (method, args) -> annotation.hasAttribute((String) args[0], (Class) args[1])); + methods.put("getAttributes", (method, args) -> annotation.getAttributes()); + methods.put("setAttribute", (method, args) -> { + throw new UnsupportedOperationException("proxied annotation can not reset attributes"); + }); + methods.put("getAttributeValue", (method, args) -> annotation.getAttributeValue((String) args[0])); + methods.put("annotationType", (method, args) -> annotation.annotationType()); + for (final Method declaredMethod : ClassUtil.getDeclaredMethods(annotation.getAnnotation().annotationType())) { + methods.put(declaredMethod.getName(), (method, args) -> proxyAttributeValue(method)); + } + } + + private String proxyToString() { + final String attributes = Stream.of(ClassUtil.getDeclaredMethods(annotation.getAnnotation().annotationType())) + .filter(AnnotationUtil::isAttributeMethod) + .map(method -> CharSequenceUtil.format( + "{}={}", method.getName(), proxyAttributeValue(method)) + ) + .collect(Collectors.joining(", ")); + return CharSequenceUtil.format("@{}({})", annotation.annotationType().getName(), attributes); + } + + private int proxyHashCode() { + return Objects.hash(annotationAttributeValueProvider, annotation); + } + + private Object proxyGetSynthesizedAnnotation() { + return annotation; + } + + private Object proxyAttributeValue(Method attributeMethod) { + return annotationAttributeValueProvider.getAttributeValue(attributeMethod.getName(), attributeMethod.getReturnType()); + } + + /** + * 通过代理类生成的合成注解 + * + * @author huangchengxing + */ + interface SyntheticProxyAnnotation extends SynthesizedAnnotation { + + /** + * 获取该代理注解对应的已合成注解 + * + * @return 理注解对应的已合成注解 + */ + SynthesizedAnnotation getSynthesizedAnnotation(); + + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotationSelector.java b/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotationSelector.java index d0591bdfd..fa2b91eb7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotationSelector.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/SynthesizedAnnotationSelector.java @@ -2,7 +2,7 @@ package cn.hutool.core.annotation; /** * 注解选择器,指定两个注解,选择其中一个返回。
- * 该接口用于在{@link SyntheticAnnotation}中用于从一批相同的注解对象中筛选最终用于合成注解对象。 + * 该接口用于在{@link SynthesizedAggregateAnnotation}中用于从一批相同的注解对象中筛选最终用于合成注解对象。 * * @author huangchengxing */ @@ -45,7 +45,7 @@ public interface SynthesizedAnnotationSelector { class NearestAndOldestPrioritySelector implements SynthesizedAnnotationSelector { @Override public T choose(T oldAnnotation, T newAnnotation) { - return newAnnotation.getVerticalDistance() < oldAnnotation.getVerticalDistance() ? newAnnotation : oldAnnotation; + return Hierarchical.Selector.NEAREST_AND_OLDEST_PRIORITY.choose(oldAnnotation, newAnnotation); } } @@ -55,7 +55,7 @@ public interface SynthesizedAnnotationSelector { class NearestAndNewestPrioritySelector implements SynthesizedAnnotationSelector { @Override public T choose(T oldAnnotation, T newAnnotation) { - return newAnnotation.getVerticalDistance() <= oldAnnotation.getVerticalDistance() ? newAnnotation : oldAnnotation; + return Hierarchical.Selector.NEAREST_AND_NEWEST_PRIORITY.choose(oldAnnotation, newAnnotation); } } @@ -65,7 +65,7 @@ public interface SynthesizedAnnotationSelector { class FarthestAndOldestPrioritySelector implements SynthesizedAnnotationSelector { @Override public T choose(T oldAnnotation, T newAnnotation) { - return newAnnotation.getVerticalDistance() > oldAnnotation.getVerticalDistance() ? newAnnotation : oldAnnotation; + return Hierarchical.Selector.FARTHEST_AND_OLDEST_PRIORITY.choose(oldAnnotation, newAnnotation); } } @@ -75,7 +75,7 @@ public interface SynthesizedAnnotationSelector { class FarthestAndNewestPrioritySelector implements SynthesizedAnnotationSelector { @Override public T choose(T oldAnnotation, T newAnnotation) { - return newAnnotation.getVerticalDistance() >= oldAnnotation.getVerticalDistance() ? newAnnotation : oldAnnotation; + return Hierarchical.Selector.FARTHEST_AND_NEWEST_PRIORITY.choose(oldAnnotation, newAnnotation); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java b/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java deleted file mode 100644 index 55c0c44eb..000000000 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotation.java +++ /dev/null @@ -1,119 +0,0 @@ -package cn.hutool.core.annotation; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; - -/** - * 表示基于特定规则聚合的一组注解对象 - * - *

合成注解一般被用于处理类层级结果中具有直接或间接关联的注解对象, - * 当实例被创建时,会获取到这些注解对象,并使用{@link SynthesizedAnnotationSelector}对类型相同的注解进行过滤, - * 并最终得到类型不重复的有效注解对象。这些有效注解将被包装为{@link SynthesizedAnnotation}, - * 然后最终用于“合成”一个{@link SynthesizedAnnotation}。 - * - *

合成注解可以作为一个特殊的{@link Annotation}或者{@link AnnotatedElement}, - * 当调用{@link Annotation}的方法时,应当返回当前实例本身的有效信息, - * 而当调用{@link AnnotatedElement}的方法时,应当返回用于合成该对象的相关注解的信息。 - * - *

合成注解允许通过{@link #syntheticAnnotation(Class)}合成一个指定的注解对象, - * 该方法返回的注解对象可能是原始的注解对象,也有可能通过动态代理的方式生成, - * 该对象实例的属性不一定来自对象本身,而是来自于经过{@link SynthesizedAnnotationAttributeProcessor} - * 处理后的、用于合成当前实例的全部关联注解的相关属性。 - * - * @author huangchengxing - * @see SynthesizedAnnotation - * @see SynthesizedAnnotationSelector - * @see SynthesizedAnnotationAttributeProcessor - * @see SyntheticMetaAnnotation - */ -public interface SyntheticAnnotation extends Annotation, AnnotatedElement { - - /** - * 获取合成注解选择器 - * - * @return 合成注解选择器 - */ - SynthesizedAnnotationSelector getAnnotationSelector(); - - /** - * 获取合成注解属性处理器 - * - * @return 合成注解属性处理器 - */ - SynthesizedAnnotationAttributeProcessor getAttributeProcessor(); - - /** - * 获取已合成的注解 - * - * @param annotationType 注解类型 - * @return 已合成的注解 - */ - SynthesizedAnnotation getSynthesizedAnnotation(Class annotationType); - - /** - * 获取当前的注解类型 - * - * @return 注解类型 - */ - @Override - default Class annotationType() { - return this.getClass(); - } - - /** - * 获取指定注解对象 - * - * @param annotationType 注解类型 - * @param 注解类型 - * @return 注解对象 - */ - @Override - T getAnnotation(Class annotationType); - - /** - * 是否存在指定注解 - * - * @param annotationType 注解类型 - * @return 是否 - */ - @Override - boolean isAnnotationPresent(Class annotationType); - - /** - * 获取全部注解 - * - * @return 注解对象 - */ - @Override - Annotation[] getAnnotations(); - - /** - * 获取合成注解 - * - * @param annotationType 注解类型 - * @param 注解类型 - * @return 类型 - */ - T syntheticAnnotation(Class annotationType); - - /** - * 获取属性值 - * - * @param attributeName 属性名称 - * @param attributeType 属性类型 - * @return 属性值 - */ - Object getAttribute(String attributeName, Class attributeType); - - /** - * 基于指定根注解,构建包括其元注解在内的合成注解 - * - * @param rootAnnotation 根注解 - * @param 注解类型 - * @return 合成注解 - */ - static SyntheticAnnotation of(T rootAnnotation) { - return new SyntheticMetaAnnotation(rootAnnotation); - } - -} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotationProxy.java b/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotationProxy.java deleted file mode 100644 index 5cf2d1723..000000000 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticAnnotationProxy.java +++ /dev/null @@ -1,141 +0,0 @@ -package cn.hutool.core.annotation; - -import cn.hutool.core.lang.Opt; -import cn.hutool.core.util.ClassUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.ReflectUtil; -import cn.hutool.core.util.StrUtil; - -import java.lang.annotation.Annotation; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.function.BiFunction; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * 合成注解代理类 - * - * @author huangchengxing - */ -class SyntheticAnnotationProxy implements InvocationHandler { - - private final SyntheticAnnotation syntheticAnnotation; - private final SynthesizedAnnotation annotation; - private final Map> methods; - - SyntheticAnnotationProxy(SyntheticAnnotation syntheticAnnotation, SynthesizedAnnotation annotation) { - this.syntheticAnnotation = syntheticAnnotation; - this.annotation = annotation; - this.methods = new HashMap<>(9); - loadMethods(); - } - - /** - * 创建一个代理注解,生成的代理对象将是{@link SyntheticProxyAnnotation}与指定的注解类的子类。 - *

    - *
  • 当作为{@code annotationType}所指定的类型使用时,其属性将通过合成它的{@link SyntheticAnnotation}获取;
  • - *
  • 当作为{@link SyntheticProxyAnnotation}或{@link SynthesizedAnnotation}使用时,将可以获得原始注解实例的相关信息;
  • - *
- * - * @param annotationType 注解类型 - * @param syntheticAnnotation 合成注解 - * @return 代理注解 - */ - @SuppressWarnings("unchecked") - static T create( - Class annotationType, SyntheticAnnotation syntheticAnnotation) { - final SynthesizedAnnotation annotation = syntheticAnnotation.getSynthesizedAnnotation(annotationType); - final SyntheticAnnotationProxy proxyHandler = new SyntheticAnnotationProxy(syntheticAnnotation, annotation); - if (ObjectUtil.isNull(annotation)) { - return null; - } - return (T) Proxy.newProxyInstance( - annotationType.getClassLoader(), - new Class[]{annotationType, SyntheticProxyAnnotation.class}, - proxyHandler - ); - } - - static boolean isProxyAnnotation(Class targetClass) { - return ClassUtil.isAssignable(SyntheticProxyAnnotation.class, targetClass); - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - return Opt.ofNullable(methods.get(method.getName())) - .map(m -> m.apply(method, args)) - .orElseGet(() -> ReflectUtil.invoke(this, method, args)); - } - - // ========================= 代理方法 ========================= - - void loadMethods() { - methods.put("toString", (method, args) -> proxyToString()); - methods.put("hashCode", (method, args) -> proxyHashCode()); - methods.put("getSyntheticAnnotation", (method, args) -> proxyGetSyntheticAnnotation()); - methods.put("getSynthesizedAnnotation", (method, args) -> proxyGetSynthesizedAnnotation()); - methods.put("getRoot", (method, args) -> annotation.getRoot()); - methods.put("isRoot", (method, args) -> annotation.isRoot()); - methods.put("getVerticalDistance", (method, args) -> annotation.getVerticalDistance()); - methods.put("getHorizontalDistance", (method, args) -> annotation.getHorizontalDistance()); - methods.put("hasAttribute", (method, args) -> annotation.hasAttribute((String)args[0], (Class)args[1])); - methods.put("getAttribute", (method, args) -> annotation.getAttribute((String)args[0])); - methods.put("annotationType", (method, args) -> annotation.annotationType()); - for (final Method declaredMethod : annotation.getAnnotation().annotationType().getDeclaredMethods()) { - methods.put(declaredMethod.getName(), (method, args) -> proxyAttributeValue(method)); - } - } - - private String proxyToString() { - final String attributes = Stream.of(annotation.annotationType().getDeclaredMethods()) - .filter(AnnotationUtil::isAttributeMethod) - .map(method -> StrUtil.format("{}={}", method.getName(), syntheticAnnotation.getAttribute(method.getName(), method.getReturnType()))) - .collect(Collectors.joining(", ")); - return StrUtil.format("@{}({})", annotation.annotationType().getName(), attributes); - } - - private int proxyHashCode() { - return Objects.hash(syntheticAnnotation, annotation); - } - - private Object proxyGetSyntheticAnnotation() { - return syntheticAnnotation; - } - - private Object proxyGetSynthesizedAnnotation() { - return annotation; - } - - private Object proxyAttributeValue(Method attributeMethod) { - return syntheticAnnotation.getAttribute(attributeMethod.getName(), attributeMethod.getReturnType()); - } - - /** - * 通过代理类生成的合成注解 - * - * @author huangchengxing - */ - interface SyntheticProxyAnnotation extends SynthesizedAnnotation { - - /** - * 获取该注解所属的合成注解 - * - * @return 合成注解 - */ - SyntheticAnnotation getSyntheticAnnotation(); - - /** - * 获取该代理注解对应的已合成注解 - * - * @return 理注解对应的已合成注解 - */ - SynthesizedAnnotation getSynthesizedAnnotation(); - - } - -} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticMetaAnnotation.java b/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticMetaAnnotation.java deleted file mode 100644 index c2a7a97d8..000000000 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/SyntheticMetaAnnotation.java +++ /dev/null @@ -1,369 +0,0 @@ -package cn.hutool.core.annotation; - -import cn.hutool.core.annotation.scanner.MetaAnnotationScanner; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.lang.Opt; -import cn.hutool.core.util.ClassUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.ReflectUtil; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.util.Comparator; -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * 表示一个根注解与根注解上的多层元注解合成的注解 - * - *

假设现有注解A,A上存在元注解B,B上存在元注解C,则对A解析得到的合成注解X,则CBA都是X的元注解,X为根注解。
- * 通过{@link #isAnnotationPresent(Class)}可确定指定类型是注解是否是该合成注解的元注解,即是否为当前实例的“父类”。 - * 若指定注解是当前实例的元注解,则通过{@link #getAnnotation(Class)}可获得动态代理生成的对应的注解实例。
- * 需要注意的是,由于认为合并注解X以最初的根注解A作为元注解,因此{@link #getAnnotations()}或{@link #getDeclaredAnnotations()} - * 都将只能获得A。 - * - *

若认为该合成注解X在第0层,则根注解A在第1层,B在第2层......以此类推, - * 则相同或不同的层级中可能会出现类型相同的注解对象,此时将通过{@link SynthesizedAnnotationSelector}选择出最合适的注解对象, - * 该注解对象将在合成注解中作为唯一有效的元注解用于进行相关操作。
- * 默认情况下,将选择{@link SynthesizedAnnotationSelector#NEAREST_AND_OLDEST_PRIORITY}选择器实例, - * 即层级越低的注解离根注解距离近,则该注解优先级越高,即遵循“就近原则”。 - * - *

合成注解中获取到的注解中可能会具有一些同名且同类型的属性, - * 此时将根据{@link SynthesizedAnnotationAttributeProcessor}决定如何从这些注解的相同属性中获取属性值。
- * 默认情况下,将选择{@link CacheableSynthesizedAnnotationAttributeProcessor}用于获取属性, - * 该处理器将选择距离根注解最近的注解中的属性用于获取属性值,{@link #getAnnotation(Class)}获得的代理类实例的属性值遵循该规则。
- * 举个例子:若CBA同时存在属性y,则将X视为C,B或者A时,获得的y属性的值都与最底层元注解A的值保持一致。 - * 若两相同注解处于同一层级,则按照从其上一级“子注解”的{@link AnnotatedElement#getAnnotations()}的调用顺序排序。 - * - *

别名在合成注解中仍然有效,若注解X中任意属性上存在{@link Alias}注解,则{@link Alias#value()}指定的属性值将会覆盖注解属性的本身的值。
- * {@link Alias}注解仅能指定注解X中存在的属性作为别名,不允许指定元注解或子类注解的属性。 - * - * @author huangchengxing - * @see AnnotationUtil - * @see SynthesizedAnnotationSelector - */ -public class SyntheticMetaAnnotation implements SyntheticAnnotation { - - /** - * 根注解,即当前查找的注解 - */ - private final Annotation source; - - /** - * 包含根注解以及其元注解在内的全部注解实例 - */ - private final Map, SynthesizedAnnotation> metaAnnotationMap; - - /** - * 合成注解选择器 - */ - private final SynthesizedAnnotationSelector annotationSelector; - - /** - * 合成注解属性处理器 - */ - private final SynthesizedAnnotationAttributeProcessor attributeProcessor; - - /** - * 基于指定根注解,为其层级结构中的全部注解构造一个合成注解。 - * 当层级结构中出现了相同的注解对象时,将优先选择以距离根注解最近,且优先被扫描的注解对象, - * 当获取值时,同样遵循该规则。 - * - * @param source 源注解 - */ - public SyntheticMetaAnnotation(Annotation source) { - this( - source, SynthesizedAnnotationSelector.NEAREST_AND_OLDEST_PRIORITY, - new CacheableSynthesizedAnnotationAttributeProcessor( - Comparator.comparing(SynthesizedAnnotation::getVerticalDistance) - .thenComparing(SynthesizedAnnotation::getHorizontalDistance) - ) - ); - } - - /** - * 基于指定根注解,为其层级结构中的全部注解构造一个合成注解 - * - * @param annotation 当前查找的注解对象 - * @param annotationSelector 合成注解选择器 - * @param attributeProcessor 注解属性处理器 - */ - public SyntheticMetaAnnotation( - Annotation annotation, - SynthesizedAnnotationSelector annotationSelector, - SynthesizedAnnotationAttributeProcessor attributeProcessor) { - Assert.notNull(annotation, "annotation must not null"); - Assert.notNull(annotationSelector, "annotationSelector must not null"); - Assert.notNull(attributeProcessor, "attributeProcessor must not null"); - - this.source = annotation; - this.annotationSelector = annotationSelector; - this.attributeProcessor = attributeProcessor; - this.metaAnnotationMap = new LinkedHashMap<>(); - loadMetaAnnotations(); - } - - /** - * 获取根注解 - * - * @return 根注解 - */ - public Annotation getSource() { - return source; - } - - /** - * 获取已解析的元注解信息 - * - * @return 已解析的元注解信息 - */ - Map, SynthesizedAnnotation> getMetaAnnotationMap() { - return metaAnnotationMap; - } - - /** - * 获取合成注解选择器 - * - * @return 合成注解选择器 - */ - @Override - public SynthesizedAnnotationSelector getAnnotationSelector() { - return this.annotationSelector; - } - - /** - * 获取合成注解属性处理器 - * - * @return 合成注解属性处理器 - */ - @Override - public SynthesizedAnnotationAttributeProcessor getAttributeProcessor() { - return this.attributeProcessor; - } - - /** - * 获取已合成的注解 - * - * @param annotationType 注解类型 - * @return 已合成的注解 - */ - @Override - public SynthesizedAnnotation getSynthesizedAnnotation(Class annotationType) { - return metaAnnotationMap.get(annotationType); - } - - /** - * 获取根注解类型 - * - * @return 注解类型 - */ - @Override - public Class annotationType() { - return this.getClass(); - } - - /** - * 根据指定的属性名与属性类型获取对应的属性值,若存在{@link Alias}则获取{@link Alias#value()}指定的别名属性的值 - *

当不同层级的注解之间存在同名同类型属性时,将优先获取更接近根注解的属性 - * - * @param attributeName 属性名 - * @param attributeType 属性类型 - * @return 属性 - */ - @Override - public Object getAttribute(String attributeName, Class attributeType) { - return attributeProcessor.getAttributeValue(attributeName, attributeType, metaAnnotationMap.values()); - } - - /** - * 获取被合成的注解 - * - * @param annotationType 注解类型 - * @param 注解类型 - * @return 注解对象 - */ - @Override - public T getAnnotation(Class annotationType) { - return Opt.ofNullable(annotationType) - .map(metaAnnotationMap::get) - .map(SynthesizedAnnotation::getAnnotation) - .map(annotationType::cast) - .orElse(null); - } - - /** - * 当前合成注解中是否存在指定元注解 - * - * @param annotationType 注解类型 - * @return 是否 - */ - @Override - public boolean isAnnotationPresent(Class annotationType) { - return metaAnnotationMap.containsKey(annotationType); - } - - /** - * 获取全部注解 - * - * @return 注解对象 - */ - @Override - public Annotation[] getAnnotations() { - return getMetaAnnotationMap().values().toArray(new MetaAnnotation[0]); - } - - /** - * 若合成注解在存在指定元注解,则使用动态代理生成一个对应的注解实例 - * - * @param annotationType 注解类型 - * @return 合成注解对象 - * @see SyntheticAnnotationProxy#create(Class, SyntheticAnnotation) - */ - @Override - public T syntheticAnnotation(Class annotationType) { - return SyntheticAnnotationProxy.create(annotationType, this); - } - - /** - * 获取根注解直接声明的注解,该方法正常情况下当只返回原注解 - * - * @return 直接声明注解 - */ - @Override - public Annotation[] getDeclaredAnnotations() { - return new Annotation[]{getSource()}; - } - - /** - * 广度优先遍历并缓存该根注解上的全部元注解 - */ - private void loadMetaAnnotations() { - Assert.isFalse(SyntheticAnnotationProxy.isProxyAnnotation(source.getClass()), "source [{}] has been synthesized"); - // 扫描元注解 - metaAnnotationMap.put(source.annotationType(), new MetaAnnotation(source, source, 0, 0)); - new MetaAnnotationScanner().scan( - (index, annotation) -> { - SynthesizedAnnotation oldAnnotation = metaAnnotationMap.get(annotation.annotationType()); - SynthesizedAnnotation newAnnotation = new MetaAnnotation(source, annotation, index, metaAnnotationMap.size()); - if (ObjectUtil.isNull(oldAnnotation)) { - metaAnnotationMap.put(annotation.annotationType(), newAnnotation); - } else { - metaAnnotationMap.put(annotation.annotationType(), annotationSelector.choose(oldAnnotation, newAnnotation)); - } - }, - source.annotationType(), null - ); - } - - /** - * 元注解包装类 - * - * @author huangchengxing - */ - public static class MetaAnnotation implements Annotation, SynthesizedAnnotation { - - private final Annotation root; - private final Annotation annotation; - private final Map attributeMethodCaches; - private final int verticalDistance; - private final int horizontalDistance; - - public MetaAnnotation(Annotation root, Annotation annotation, int verticalDistance, int horizontalDistance) { - this.root = root; - this.annotation = annotation; - this.verticalDistance = verticalDistance; - this.horizontalDistance = horizontalDistance; - this.attributeMethodCaches = AnnotationUtil.getAttributeMethods(annotation.annotationType()); - } - - /** - * 获取注解类型 - * - * @return 注解类型 - */ - @Override - public Class annotationType() { - return annotation.annotationType(); - } - - /** - * 获取根注解 - * - * @return 根注解 - */ - @Override - public Annotation getRoot() { - return this.root; - } - - /** - * 获取元注解 - * - * @return 元注解 - */ - @Override - public Annotation getAnnotation() { - return annotation; - } - - /** - * 获取该合成注解与根注解之间相隔的层级数 - * - * @return 该合成注解与根注解之间相隔的层级数 - */ - @Override - public int getVerticalDistance() { - return verticalDistance; - } - - /** - * 获取该合成注解与根注解之间相隔的注解树 - * - * @return 该合成注解与根注解之间相隔的注解树 - */ - @Override - public int getHorizontalDistance() { - return horizontalDistance; - } - - /** - * 元注解是否存在该属性 - * - * @param attributeName 属性名 - * @return 是否存在该属性 - */ - public boolean hasAttribute(String attributeName) { - return attributeMethodCaches.containsKey(attributeName); - } - - /** - * 元注解是否存在该属性,且该属性的值类型是指定类型或其子类 - * - * @param attributeName 属性名 - * @param returnType 返回值类型 - * @return 是否存在该属性 - */ - @Override - public boolean hasAttribute(String attributeName, Class returnType) { - return Opt.ofNullable(attributeMethodCaches.get(attributeName)) - .filter(method -> ClassUtil.isAssignable(returnType, method.getReturnType())) - .isPresent(); - } - - /** - * 获取元注解的属性值 - * - * @param attributeName 属性名 - * @return 元注解的属性值 - */ - @Override - public Object getAttribute(String attributeName) { - return Opt.ofNullable(attributeMethodCaches.get(attributeName)) - .map(method -> ReflectUtil.invoke(annotation, method)) - .orElse(null); - } - - } - -} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/WrappedAnnotationAttribute.java b/hutool-core/src/main/java/cn/hutool/core/annotation/WrappedAnnotationAttribute.java new file mode 100644 index 000000000..6ca5a3f52 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/WrappedAnnotationAttribute.java @@ -0,0 +1,125 @@ +package cn.hutool.core.annotation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Collection; + +/** + *

表示一个被包装过的{@link AnnotationAttribute}, + * 该实例中的一些方法可能会被代理到另一个注解属性对象中, + * 从而使得通过原始的注解属性的方法获取到另一注解属性的值。
+ * 除了{@link #getValue()}以外,其他方法的返回值应当尽可能与{@link #getOriginal()} + * 返回的{@link AnnotationAttribute}对象的方法返回值一致。 + * + *

当包装类被包装了多层后,则规则生效优先级按包装的先后顺序倒序排序, + * 比如a、b互为镜像,此时a、b两属性应当都被{@link MirroredAnnotationAttribute}包装, + * 若再指定c为a的别名字段,则c、a、b都要在原基础上再次包装一层{@link AliasedAnnotationAttribute}。
+ * 此时a、b同时被包装了两层,则执行时,优先执行{@link AliasedAnnotationAttribute}的逻辑, + * 当该规则不生效时,比如c只有默认值,此时上一次的{@link MirroredAnnotationAttribute}的逻辑才会生效。 + * + *

被包装的{@link AnnotationAttribute}实际结构为一颗二叉树, + * 当包装类再次被包装时,实际上等于又添加了一个新的根节点, + * 此时需要同时更新树的全部关联叶子节点。 + * + * @author huangchengxing + * @see AnnotationAttribute + * @see ForceAliasedAnnotationAttribute + * @see AliasedAnnotationAttribute + * @see MirroredAnnotationAttribute + */ +public interface WrappedAnnotationAttribute extends AnnotationAttribute { + + // =========================== 新增方法 =========================== + + /** + * 获取被包装的{@link AnnotationAttribute}对象,该对象也可能是{@link AnnotationAttribute} + * + * @return 被包装的{@link AnnotationAttribute}对象 + */ + AnnotationAttribute getOriginal(); + + /** + * 获取最初的被包装的{@link AnnotationAttribute} + * + * @return 最初的被包装的{@link AnnotationAttribute} + */ + AnnotationAttribute getNonWrappedOriginal(); + + /** + * 获取包装{@link #getOriginal()}的{@link AnnotationAttribute}对象,该对象也可能是{@link AnnotationAttribute} + * + * @return 包装对象 + */ + AnnotationAttribute getLinked(); + + /** + * 遍历以当前实例为根节点的树结构,获取所有未被包装的属性 + * + * @return 叶子节点 + */ + Collection getAllLinkedNonWrappedAttributes(); + + // =========================== 代理实现 =========================== + + /** + * 获取注解对象 + * + * @return 注解对象 + */ + @Override + default Annotation getAnnotation() { + return getOriginal().getAnnotation(); + } + + /** + * 获取注解属性对应的方法 + * + * @return 注解属性对应的方法 + */ + @Override + default Method getAttribute() { + return getOriginal().getAttribute(); + } + + /** + * 该注解属性的值是否等于默认值
+ * 默认仅当{@link #getOriginal()}与{@link #getLinked()}返回的注解属性 + * 都为默认值时,才返回{@code true} + * + * @return 该注解属性的值是否等于默认值 + */ + @Override + boolean isValueEquivalentToDefaultValue(); + + /** + * 获取属性类型 + * + * @return 属性类型 + */ + @Override + default Class getAttributeType() { + return getOriginal().getAttributeType(); + } + + /** + * 获取属性上的注解 + * + * @param annotationType 注解类型 + * @return 注解对象 + */ + @Override + default T getAnnotation(Class annotationType) { + return getOriginal().getAnnotation(annotationType); + } + + /** + * 当前注解属性是否已经被{@link WrappedAnnotationAttribute}包装 + * + * @return boolean + */ + @Override + default boolean isWrapped() { + return true; + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/AbstractTypeAnnotationScanner.java b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/AbstractTypeAnnotationScanner.java index a30b61593..8c8ba7ad9 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/AbstractTypeAnnotationScanner.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/AbstractTypeAnnotationScanner.java @@ -24,8 +24,7 @@ public abstract class AbstractTypeAnnotationScanner> filter, Set> excludeTypes) { + protected AbstractTypeAnnotationScanner(boolean includeSuperClass, boolean includeInterfaces, Predicate> filter, Set> excludeTypes) { Assert.notNull(filter, "filter must not null"); Assert.notNull(excludeTypes, "excludeTypes must not null"); - this.includeSupperClass = includeSupperClass; + this.includeSuperClass = includeSuperClass; this.includeInterfaces = includeInterfaces; this.filter = filter; this.excludeTypes = excludeTypes; @@ -82,8 +81,8 @@ public abstract class AbstractTypeAnnotationScanner> accessedTypes, Class targetClass) { return ObjectUtil.isNull(targetClass) @@ -246,13 +246,13 @@ public abstract class AbstractTypeAnnotationScanner> nextClassQueue, Class targetClass) { - if (includeSupperClass) { + if (includeSuperClass) { final Class superClass = targetClass.getSuperclass(); if (!ObjectUtil.equals(superClass, Object.class) && ObjectUtil.isNotNull(superClass)) { nextClassQueue.add(superClass); diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/EmptyAnnotationScanner.java b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/EmptyAnnotationScanner.java new file mode 100644 index 000000000..62dc560fa --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/EmptyAnnotationScanner.java @@ -0,0 +1,33 @@ +package cn.hutool.core.annotation.scanner; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.Collections; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Predicate; + +/** + * 默认不扫描任何元素的扫描器 + * + * @author huangchengxing + */ +public class EmptyAnnotationScanner implements AnnotationScanner { + + public static final EmptyAnnotationScanner INSTANCE = new EmptyAnnotationScanner(); + + @Override + public boolean support(AnnotatedElement annotatedEle) { + return true; + } + + @Override + public List getAnnotations(AnnotatedElement annotatedEle) { + return Collections.emptyList(); + } + + @Override + public void scan(BiConsumer consumer, AnnotatedElement annotatedEle, Predicate filter) { + // do nothing + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MetaAnnotationScanner.java b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MetaAnnotationScanner.java index 22886d047..5188a9218 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MetaAnnotationScanner.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MetaAnnotationScanner.java @@ -1,16 +1,13 @@ package cn.hutool.core.annotation.scanner; import cn.hutool.core.annotation.AnnotationUtil; -import cn.hutool.core.collection.CollStreamUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ObjectUtil; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; -import java.util.ArrayList; -import java.util.Deque; -import java.util.List; +import java.util.*; import java.util.function.BiConsumer; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -84,6 +81,7 @@ public class MetaAnnotationScanner implements AnnotationScanner { @Override public void scan(BiConsumer consumer, AnnotatedElement annotatedEle, Predicate filter) { filter = ObjectUtil.defaultIfNull(filter, t -> true); + Set> accessed = new HashSet<>(); final Deque>> deque = CollUtil.newLinkedList(CollUtil.newArrayList((Class)annotatedEle)); int distance = 0; do { @@ -96,7 +94,14 @@ public class MetaAnnotationScanner implements AnnotationScanner { for (final Annotation metaAnnotation : metaAnnotations) { consumer.accept(distance, metaAnnotation); } - deque.addLast(CollStreamUtil.toList(metaAnnotations, Annotation::annotationType)); + accessed.add(type); + List> next = metaAnnotations.stream() + .map(Annotation::annotationType) + .filter(t -> !accessed.contains(t)) + .collect(Collectors.toList()); + if (CollUtil.isNotEmpty(next)) { + deque.addLast(next); + } } distance++; } while (includeSupperMetaAnnotation && !deque.isEmpty()); diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MethodAnnotationScanner.java b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MethodAnnotationScanner.java index 8202c8d80..18be3ea1b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MethodAnnotationScanner.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/scanner/MethodAnnotationScanner.java @@ -80,7 +80,7 @@ public class MethodAnnotationScanner extends AbstractTypeAnnotationScanner targetClass) { final Method sourceMethod = (Method) source; - return Stream.of(targetClass.getDeclaredMethods()) + return Stream.of(ClassUtil.getDeclaredMethods(targetClass)) .filter(superMethod -> !superMethod.isBridge()) .filter(superMethod -> hasSameSignature(sourceMethod, superMethod)) .map(AnnotatedElement::getAnnotations) @@ -96,7 +96,7 @@ public class MethodAnnotationScanner extends AbstractTypeAnnotationScanner List sub(List list, int start, int end) { + public static List + sub(List list, int start, int end) { return sub(list, start, end, 1); } diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/Partition.java b/hutool-core/src/main/java/cn/hutool/core/collection/Partition.java index fa555b974..b5dbdaed1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/Partition.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/Partition.java @@ -1,5 +1,7 @@ package cn.hutool.core.collection; +import cn.hutool.core.lang.Assert; + import java.util.AbstractList; import java.util.List; @@ -21,18 +23,18 @@ public class Partition extends AbstractList> { /** * 列表分区 * - * @param list 被分区的列表 - * @param size 每个分区的长度 + * @param list 被分区的列表,非空 + * @param size 每个分区的长度,必须>0 */ public Partition(List list, int size) { - this.list = list; - this.size = Math.min(size, list.size()); + this.list = Assert.notNull(list); + this.size = Math.min(list.size(), size); } @Override public List get(int index) { - int start = index * size; - int end = Math.min(start + size, list.size()); + final int start = index * size; + final int end = Math.min(start + size, list.size()); return list.subList(start, end); } @@ -41,11 +43,9 @@ public class Partition extends AbstractList> { // 此处采用动态计算,以应对list变 final int size = this.size; final int total = list.size(); - int length = total / size; - if(total % size > 0){ - length += 1; - } - return length; + // 类似于判断余数,当总数非整份size时,多余的数>=1,则相当于被除数多一个size,做到+1目的 + // 类似于:if(total % size > 0){length += 1;} + return (total + size - 1) / size; } @Override diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java index 367d67916..53f36ba59 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java @@ -94,6 +94,8 @@ public class TemporalAccessorConverter extends AbstractConverter + * 起始日期和结束日期可以互换 + * + * @param date 被检查的日期 + * @param beginDate 起始日期(包含) + * @param endDate 结束日期(包含) + * @return 是否在范围内 + * @since 5.8.5 + */ + public static boolean isIn(ChronoLocalDateTime date, ChronoLocalDateTime beginDate, ChronoLocalDateTime endDate){ + return TemporalAccessorUtil.isIn(date, beginDate, endDate); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java index 067164e0d..ae3975aa4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java @@ -162,4 +162,22 @@ public class TemporalAccessorUtil extends TemporalUtil{ return result; } + + /** + * 当前日期是否在日期指定范围内
+ * 起始日期和结束日期可以互换 + * + * @param date 被检查的日期 + * @param beginDate 起始日期(包含) + * @param endDate 结束日期(包含) + * @return 是否在范围内 + * @since 5.8.5 + */ + public static boolean isIn(TemporalAccessor date, TemporalAccessor beginDate, TemporalAccessor endDate){ + final long thisMills = toEpochMilli(date); + final long beginMills = toEpochMilli(beginDate); + final long endMills = toEpochMilli(endDate); + + return thisMills >= Math.min(beginMills, endMills) && thisMills <= Math.max(beginMills, endMills); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileTypeUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileTypeUtil.java index 95736387a..edc265e2a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileTypeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileTypeUtil.java @@ -38,9 +38,8 @@ public class FileTypeUtil { FILE_TYPE_MAP.put("4749463837", "gif"); // GIF (gif) FILE_TYPE_MAP.put("4749463839", "gif"); // GIF (gif) FILE_TYPE_MAP.put("49492a00227105008037", "tif"); // TIFF (tif) - FILE_TYPE_MAP.put("424d228c010000000000", "bmp"); // 16色位图(bmp) - FILE_TYPE_MAP.put("424d8240090000000000", "bmp"); // 24色位图(bmp) - FILE_TYPE_MAP.put("424d8e1b030000000000", "bmp"); // 256色位图(bmp) + // https://github.com/sindresorhus/file-type/blob/main/core.js#L90 + FILE_TYPE_MAP.put("424d", "bmp"); // 位图(bmp) FILE_TYPE_MAP.put("41433130313500000000", "dwg"); // CAD (dwg) FILE_TYPE_MAP.put("7b5c727466315c616e73", "rtf"); // Rich Text Format (rtf) FILE_TYPE_MAP.put("38425053000100000000", "psd"); // Photoshop (psd) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Dict.java b/hutool-core/src/main/java/cn/hutool/core/lang/Dict.java index 058aeba4c..14424aa7b 100755 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Dict.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Dict.java @@ -19,6 +19,7 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; +import java.util.function.BiFunction; /** * 字典对象,扩充了HashMap中的方法 @@ -567,6 +568,11 @@ public class Dict extends LinkedHashMap implements BasicTypeGett } // -------------------------------------------------------------------- Get end + @Override + public boolean containsKey(Object key) { + return super.containsKey(customKey((String) key)); + } + @Override public Object get(Object key) { return super.get(customKey((String) key)); @@ -587,6 +593,48 @@ public class Dict extends LinkedHashMap implements BasicTypeGett return (Dict) super.clone(); } + @Override + public Object remove(Object key) { + return super.remove(customKey((String) key)); + } + + @Override + public boolean remove(Object key, Object value) { + return super.remove(customKey((String) key), value); + } + + @Override + public boolean replace(String key, Object oldValue, Object newValue) { + return super.replace(customKey(key), oldValue, newValue); + } + + @Override + public Object replace(String key, Object value) { + return super.replace(customKey(key), value); + } + + //---------------------------------------------------------------------------- Override default methods start + @Override + public Object getOrDefault(Object key, Object defaultValue) { + return super.getOrDefault(customKey((String) key), defaultValue); + } + + @Override + public Object computeIfPresent(final String key, final BiFunction remappingFunction) { + return super.computeIfPresent(customKey(key), remappingFunction); + } + + @Override + public Object compute(final String key, final BiFunction remappingFunction) { + return super.compute(customKey(key), remappingFunction); + } + + @Override + public Object merge(final String key, final Object value, final BiFunction remappingFunction) { + return super.merge(customKey(key), value, remappingFunction); + } + //---------------------------------------------------------------------------- Override default methods end + /** * 将Key转为小写 * @@ -616,4 +664,5 @@ public class Dict extends LinkedHashMap implements BasicTypeGett Arrays.stream(fields).forEach(f -> set(LambdaUtil.getFieldName(f), f.callWithRuntimeException())); return this; } + } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/ForestMap.java b/hutool-core/src/main/java/cn/hutool/core/map/ForestMap.java index fd0951f8d..6af809b94 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/ForestMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/ForestMap.java @@ -275,6 +275,18 @@ public interface ForestMap extends Map> { .orElse(false); } + /** + * 获取指定节点的值 + * + * @param key 节点的key + * @return 节点值,若节点不存在,或节点值为null都将返回null + */ + default V getNodeValue(K key) { + return Opt.ofNullable(get(key)) + .map(TreeEntry::getValue) + .get(); + } + // ===================== 子节点相关方法 ===================== /** diff --git a/hutool-core/src/main/java/cn/hutool/core/map/TransMap.java b/hutool-core/src/main/java/cn/hutool/core/map/TransMap.java index 2fa508c53..e6608aec9 100755 --- a/hutool-core/src/main/java/cn/hutool/core/map/TransMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/TransMap.java @@ -70,7 +70,7 @@ public abstract class TransMap extends MapWrapper { @Override public boolean replace(K key, V oldValue, V newValue) { - return super.replace(customKey(key), customValue(oldValue), customValue(values())); + return super.replace(customKey(key), customValue(oldValue), customValue(newValue)); } @Override diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java index dbacd2278..a2e69510d 100755 --- a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java @@ -1021,7 +1021,7 @@ public class ArrayUtil extends PrimitiveArrayUtil { * 获取数组中指定多个下标元素值,组成新数组 * * @param 数组元素类型 - * @param array 数组 + * @param array 数组,如果提供为{@code null}则返回{@code null} * @param indexes 下标列表 * @return 结果 */ @@ -1029,10 +1029,13 @@ public class ArrayUtil extends PrimitiveArrayUtil { if (null == array) { return null; } + if(null == indexes){ + return newArray(array.getClass().getComponentType(), 0); + } final T[] result = newArray(array.getClass().getComponentType(), indexes.length); - for (int i : indexes) { - result[i] = get(array, i); + for (int i = 0; i < indexes.length; i++) { + result[i] = ArrayUtil.get(array, indexes[i]); } return result; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/PageUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/PageUtil.java index 6fcb73bce..d62d881ff 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/PageUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/PageUtil.java @@ -175,15 +175,28 @@ public class PageUtil { * @return 总页数 */ public static int totalPage(int totalCount, int pageSize) { + return totalPage((long) totalCount,pageSize); + } + + /** + * 根据总数计算总页数 + * + * @param totalCount 总数 + * @param pageSize 每页数 + * @return 总页数 + * @since 5.8.5 + */ + public static int totalPage(long totalCount, int pageSize) { if (pageSize == 0) { return 0; } - return totalCount % pageSize == 0 ? (totalCount / pageSize) : (totalCount / pageSize + 1); + return Math.toIntExact(totalCount % pageSize == 0 ? (totalCount / pageSize) : (totalCount / pageSize + 1)); } /** * 分页彩虹算法
- * 来自:https://github.com/iceroot/iceroot/blob/master/src/main/java/com/icexxx/util/IceUtil.java
+ * 来自:
+ * https://github.com/iceroot/iceroot/blob/master/src/main/java/com/icexxx/util/IceUtil.java
* 通过传入的信息,生成一个分页列表显示 * * @param pageNo 当前页 @@ -230,7 +243,8 @@ public class PageUtil { /** * 分页彩虹算法(默认展示10页)
- * 来自:https://github.com/iceroot/iceroot/blob/master/src/main/java/com/icexxx/util/IceUtil.java + * 来自: + * https://github.com/iceroot/iceroot/blob/master/src/main/java/com/icexxx/util/IceUtil.java * * @param currentPage 当前页 * @param pageCount 总页数 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index 9be7aa9f5..9fbe76341 100755 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -206,18 +206,36 @@ public class ReUtil { * @since 4.0.13 */ public static List getAllGroups(Pattern pattern, CharSequence content, boolean withGroup0) { + return getAllGroups(pattern, content, withGroup0, false); + } + + /** + * 获得匹配的字符串匹配到的所有分组 + * + * @param pattern 编译后的正则模式 + * @param content 被匹配的内容 + * @param withGroup0 是否包括分组0,此分组表示全匹配的信息 + * @param findAll 是否查找所有匹配到的内容,{@code false}表示只读取第一个匹配到的内容 + * @return 匹配后得到的字符串数组,按照分组顺序依次列出,未匹配到返回空列表,任何一个参数为null返回null + * @since 4.0.13 + */ + public static List getAllGroups(Pattern pattern, CharSequence content, boolean withGroup0, boolean findAll) { if (null == content || null == pattern) { return null; } ArrayList result = new ArrayList<>(); final Matcher matcher = pattern.matcher(content); - if (matcher.find()) { + while (matcher.find()) { final int startGroup = withGroup0 ? 0 : 1; final int groupCount = matcher.groupCount(); for (int i = startGroup; i <= groupCount; i++) { result.add(matcher.group(i)); } + + if(false == findAll){ + break; + } } return result; } diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/AbstractWrappedAnnotationAttributeTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/AbstractWrappedAnnotationAttributeTest.java new file mode 100644 index 000000000..66059a18d --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/AbstractWrappedAnnotationAttributeTest.java @@ -0,0 +1,106 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ReflectUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.*; +import java.lang.reflect.Method; + +public class AbstractWrappedAnnotationAttributeTest { + + @Test + public void workTest() { + Annotation annotation = ClassForTest1.class.getAnnotation(AnnotationForTest1.class); + Method valueMethod = ReflectUtil.getMethod(AnnotationForTest1.class, "value1"); + CacheableAnnotationAttribute valueAttribute = new CacheableAnnotationAttribute(annotation, valueMethod); + Method nameMethod = ReflectUtil.getMethod(AnnotationForTest1.class, "name1"); + CacheableAnnotationAttribute nameAttribute = new CacheableAnnotationAttribute(annotation, nameMethod); + AbstractWrappedAnnotationAttribute nameWrapper = new TestWrappedAnnotationAttribute(nameAttribute, valueAttribute); + + Assert.assertEquals(nameWrapper.getAnnotation(), annotation); + + // 注解属性 + Assert.assertEquals(annotation, nameWrapper.getAnnotation()); + Assert.assertEquals(annotation.annotationType(), nameWrapper.getAnnotationType()); + Assert.assertEquals(nameAttribute, nameWrapper.getOriginal()); + Assert.assertEquals(valueAttribute, nameWrapper.getLinked()); + + // 方法属性 + Assert.assertEquals(nameMethod.getName(), nameWrapper.getAttributeName()); + Assert.assertEquals(nameMethod.getReturnType(), nameWrapper.getAttributeType()); + Assert.assertTrue(nameWrapper.isWrapped()); + Assert.assertEquals("value1", nameWrapper.getValue()); + } + + @Test + public void multiWrapperTest() { + // 包装第一层: name1 + value1 + Annotation annotation1 = ClassForTest1.class.getAnnotation(AnnotationForTest1.class); + Method value1Method = ReflectUtil.getMethod(AnnotationForTest1.class, "value1"); + CacheableAnnotationAttribute value1Attribute = new CacheableAnnotationAttribute(annotation1, value1Method); + Method name1Method = ReflectUtil.getMethod(AnnotationForTest1.class, "name1"); + CacheableAnnotationAttribute name1Attribute = new CacheableAnnotationAttribute(annotation1, name1Method); + AbstractWrappedAnnotationAttribute wrapper1 = new TestWrappedAnnotationAttribute(name1Attribute, value1Attribute); + Assert.assertEquals(name1Attribute, wrapper1.getNonWrappedOriginal()); + Assert.assertEquals(CollUtil.newArrayList(name1Attribute, value1Attribute), wrapper1.getAllLinkedNonWrappedAttributes()); + + // 包装第二层:( name1 + value1 ) + value2 + Annotation annotation2 = ClassForTest1.class.getAnnotation(AnnotationForTest2.class); + Method value2Method = ReflectUtil.getMethod(AnnotationForTest2.class, "value2"); + CacheableAnnotationAttribute value2Attribute = new CacheableAnnotationAttribute(annotation2, value2Method); + AbstractWrappedAnnotationAttribute wrapper2 = new TestWrappedAnnotationAttribute(wrapper1, value2Attribute); + Assert.assertEquals(name1Attribute, wrapper2.getNonWrappedOriginal()); + Assert.assertEquals(CollUtil.newArrayList(name1Attribute, value1Attribute, value2Attribute), wrapper2.getAllLinkedNonWrappedAttributes()); + + // 包装第二层:value3 + ( ( name1 + value1 ) + value2 ) + Annotation annotation3 = ClassForTest1.class.getAnnotation(AnnotationForTest3.class); + Method value3Method = ReflectUtil.getMethod(AnnotationForTest3.class, "value3"); + CacheableAnnotationAttribute value3Attribute = new CacheableAnnotationAttribute(annotation3, value3Method); + AbstractWrappedAnnotationAttribute wrapper3 = new TestWrappedAnnotationAttribute(value3Attribute, wrapper2); + Assert.assertEquals(value3Attribute, wrapper3.getNonWrappedOriginal()); + Assert.assertEquals(CollUtil.newArrayList(value3Attribute, name1Attribute, value1Attribute, value2Attribute), wrapper3.getAllLinkedNonWrappedAttributes()); + + } + + static class TestWrappedAnnotationAttribute extends AbstractWrappedAnnotationAttribute { + protected TestWrappedAnnotationAttribute(AnnotationAttribute original, AnnotationAttribute linked) { + super(original, linked); + } + @Override + public Object getValue() { + return linked.getValue(); + } + + @Override + public boolean isValueEquivalentToDefaultValue() { + return getOriginal().isValueEquivalentToDefaultValue() && getLinked().isValueEquivalentToDefaultValue(); + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface AnnotationForTest1 { + String value1() default ""; + String name1() default ""; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface AnnotationForTest2 { + String value2() default ""; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface AnnotationForTest3 { + String value3() default ""; + } + + @AnnotationForTest1(name1 = "name1", value1 = "value1") + @AnnotationForTest2(value2 = "value2") + @AnnotationForTest3(value3 = "value3") + static class ClassForTest1 {} + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/AliasAnnotationPostProcessorTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/AliasAnnotationPostProcessorTest.java new file mode 100644 index 000000000..8675bca82 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/AliasAnnotationPostProcessorTest.java @@ -0,0 +1,195 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.*; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.UnaryOperator; + +public class AliasAnnotationPostProcessorTest { + + @Test + public void processTest() { + AliasAnnotationPostProcessor processor = new AliasAnnotationPostProcessor(); + + Map, SynthesizedAnnotation> annotationMap = new HashMap<>(); + SynthesizedAggregateAnnotation synthesizedAnnotationAggregator = new TestSynthesizedAggregateAnnotation(annotationMap); + AnnotationForTest annotation = ClassForTest.class.getAnnotation(AnnotationForTest.class); + SynthesizedAnnotation synthesizedAnnotation = new TestSynthesizedAnnotation(synthesizedAnnotationAggregator, annotation); + annotationMap.put(annotation.annotationType(), synthesizedAnnotation); + + processor.process(synthesizedAnnotation, synthesizedAnnotationAggregator); + AnnotationAttribute valueAttribute = synthesizedAnnotation.getAttributes().get("value"); + Assert.assertEquals(ReflectUtil.getMethod(AnnotationForTest.class, "value"), valueAttribute.getAttribute()); + Assert.assertTrue(valueAttribute.isWrapped()); + Assert.assertEquals(ForceAliasedAnnotationAttribute.class, valueAttribute.getClass()); + + AnnotationAttribute nameAttribute = synthesizedAnnotation.getAttributes().get("name"); + Assert.assertEquals(ReflectUtil.getMethod(AnnotationForTest.class, "name"), nameAttribute.getAttribute()); + Assert.assertFalse(nameAttribute.isWrapped()); + Assert.assertEquals(CacheableAnnotationAttribute.class, nameAttribute.getClass()); + + Assert.assertEquals(nameAttribute, ((WrappedAnnotationAttribute)valueAttribute).getLinked()); + } + + @AnnotationForTest + static class ClassForTest {} + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface AnnotationForTest { + @Alias("name") + String value() default ""; + String name() default ""; + } + + static class TestSynthesizedAggregateAnnotation implements SynthesizedAggregateAnnotation { + + private final Map, SynthesizedAnnotation> annotationMap; + + public TestSynthesizedAggregateAnnotation(Map, SynthesizedAnnotation> annotationMap) { + this.annotationMap = annotationMap; + } + + @Override + public Object getSource() { + return null; + } + + @Override + public SynthesizedAnnotationSelector getAnnotationSelector() { + return null; + } + + @Override + public SynthesizedAnnotationAttributeProcessor getAnnotationAttributeProcessor() { + return null; + } + + @Override + public Collection getAnnotationPostProcessors() { + return null; + } + + @Override + public SynthesizedAnnotation getSynthesizedAnnotation(Class annotationType) { + return annotationMap.get(annotationType); + } + + @Override + public Map, SynthesizedAnnotation> getAllSynthesizedAnnotation() { + return null; + } + + @Override + public T getAnnotation(Class annotationType) { + return null; + } + + @Override + public boolean isAnnotationPresent(Class annotationType) { + return false; + } + + @Override + public Annotation[] getAnnotations() { + return new Annotation[0]; + } + + @Override + public T synthesize(Class annotationType) { + return null; + } + + @Override + public Object getAttributeValue(String attributeName, Class attributeType) { + return null; + } + + @Override + public Object getRoot() { + return null; + } + } + + static class TestSynthesizedAnnotation implements SynthesizedAnnotation { + + private final Annotation annotation; + private final SynthesizedAggregateAnnotation owner; + private final Map attributeMap; + + public TestSynthesizedAnnotation(SynthesizedAggregateAnnotation owner, Annotation annotation) { + this.owner = owner; + this.attributeMap = new HashMap<>(); + this.annotation = annotation; + for (Method declaredMethod : annotation.annotationType().getDeclaredMethods()) { + attributeMap.put(declaredMethod.getName(), new CacheableAnnotationAttribute(annotation, declaredMethod)); + } + } + + @Override + public Object getRoot() { + return null; + } + + @Override + public Annotation getAnnotation() { + return annotation; + } + + @Override + public int getVerticalDistance() { + return 0; + } + + @Override + public int getHorizontalDistance() { + return 0; + } + + @Override + public boolean hasAttribute(String attributeName, Class returnType) { + return false; + } + + @Override + public Map getAttributes() { + return attributeMap; + } + + @Override + public void setAttribute(String attributeName, AnnotationAttribute attribute) { + attributeMap.put(attributeName, attribute); + } + + @Override + public void replaceAttribute(String attributeName, UnaryOperator operator) { + AnnotationAttribute annotationAttribute = attributeMap.get(attributeName); + if (ObjectUtil.isNotNull(annotationAttribute)) { + attributeMap.put(attributeName, operator.apply(annotationAttribute)); + } + } + + @Override + public Object getAttributeValue(String attributeName) { + return null; + } + + @Override + public Class annotationType() { + return annotation.annotationType(); + } + + @Override + public Object getAttributeValue(String attributeName, Class attributeType) { + return null; + } + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/AliasLinkAnnotationPostProcessorTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/AliasLinkAnnotationPostProcessorTest.java new file mode 100644 index 000000000..040ab8aac --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/AliasLinkAnnotationPostProcessorTest.java @@ -0,0 +1,223 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.*; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.UnaryOperator; + +public class AliasLinkAnnotationPostProcessorTest { + + @Test + public void processForceAliasForTest() { + AliasLinkAnnotationPostProcessor processor = new AliasLinkAnnotationPostProcessor(); + + Map, SynthesizedAnnotation> annotationMap = new HashMap<>(); + SynthesizedAggregateAnnotation synthesizedAnnotationAggregator = new TestSynthesizedAggregateAnnotation(annotationMap); + AnnotationForTest annotation = ClassForTest.class.getAnnotation(AnnotationForTest.class); + SynthesizedAnnotation synthesizedAnnotation = new TestSynthesizedAnnotation(synthesizedAnnotationAggregator, annotation); + annotationMap.put(annotation.annotationType(), synthesizedAnnotation); + + processor.process(synthesizedAnnotation, synthesizedAnnotationAggregator); + AnnotationAttribute valueAttribute = synthesizedAnnotation.getAttributes().get("value"); + Assert.assertEquals(ReflectUtil.getMethod(AnnotationForTest.class, "value"), valueAttribute.getAttribute()); + Assert.assertFalse(valueAttribute.isWrapped()); + Assert.assertEquals(CacheableAnnotationAttribute.class, valueAttribute.getClass()); + + AnnotationAttribute nameAttribute = synthesizedAnnotation.getAttributes().get("name"); + Assert.assertEquals(ReflectUtil.getMethod(AnnotationForTest.class, "name"), nameAttribute.getAttribute()); + Assert.assertTrue(nameAttribute.isWrapped()); + Assert.assertEquals(ForceAliasedAnnotationAttribute.class, nameAttribute.getClass()); + + Assert.assertEquals(valueAttribute, ((WrappedAnnotationAttribute)nameAttribute).getLinked()); + } + + @Test + public void processAliasForTest() { + AliasLinkAnnotationPostProcessor processor = new AliasLinkAnnotationPostProcessor(); + + Map, SynthesizedAnnotation> annotationMap = new HashMap<>(); + SynthesizedAggregateAnnotation synthesizedAnnotationAggregator = new TestSynthesizedAggregateAnnotation(annotationMap); + AnnotationForTest annotation = ClassForTest.class.getAnnotation(AnnotationForTest.class); + SynthesizedAnnotation synthesizedAnnotation = new TestSynthesizedAnnotation(synthesizedAnnotationAggregator, annotation); + annotationMap.put(annotation.annotationType(), synthesizedAnnotation); + + processor.process(synthesizedAnnotation, synthesizedAnnotationAggregator); + AnnotationAttribute valueAttribute = synthesizedAnnotation.getAttributes().get("value2"); + Assert.assertEquals(ReflectUtil.getMethod(AnnotationForTest.class, "value2"), valueAttribute.getAttribute()); + Assert.assertFalse(valueAttribute.isWrapped()); + Assert.assertEquals(CacheableAnnotationAttribute.class, valueAttribute.getClass()); + + AnnotationAttribute nameAttribute = synthesizedAnnotation.getAttributes().get("name2"); + Assert.assertEquals(ReflectUtil.getMethod(AnnotationForTest.class, "name2"), nameAttribute.getAttribute()); + Assert.assertTrue(nameAttribute.isWrapped()); + Assert.assertEquals(AliasedAnnotationAttribute.class, nameAttribute.getClass()); + + Assert.assertEquals(valueAttribute, ((WrappedAnnotationAttribute)nameAttribute).getLinked()); + } + + @AnnotationForTest + static class ClassForTest {} + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface AnnotationForTest { + @Link(attribute = "name", type = RelationType.FORCE_ALIAS_FOR) + String value() default ""; + String name() default ""; + + @Link(attribute = "name2", type = RelationType.ALIAS_FOR) + String value2() default ""; + String name2() default ""; + } + + static class TestSynthesizedAggregateAnnotation implements SynthesizedAggregateAnnotation { + + private final Map, SynthesizedAnnotation> annotationMap; + + public TestSynthesizedAggregateAnnotation(Map, SynthesizedAnnotation> annotationMap) { + this.annotationMap = annotationMap; + } + + @Override + public Object getSource() { + return null; + } + + @Override + public SynthesizedAnnotationSelector getAnnotationSelector() { + return null; + } + + @Override + public SynthesizedAnnotationAttributeProcessor getAnnotationAttributeProcessor() { + return null; + } + + @Override + public Collection getAnnotationPostProcessors() { + return null; + } + + @Override + public SynthesizedAnnotation getSynthesizedAnnotation(Class annotationType) { + return annotationMap.get(annotationType); + } + + @Override + public Map, SynthesizedAnnotation> getAllSynthesizedAnnotation() { + return null; + } + + @Override + public T getAnnotation(Class annotationType) { + return null; + } + + @Override + public boolean isAnnotationPresent(Class annotationType) { + return false; + } + + @Override + public Annotation[] getAnnotations() { + return new Annotation[0]; + } + + @Override + public T synthesize(Class annotationType) { + return null; + } + + @Override + public Object getAttributeValue(String attributeName, Class attributeType) { + return null; + } + + @Override + public Object getRoot() { + return null; + } + } + + static class TestSynthesizedAnnotation implements SynthesizedAnnotation { + + private final Annotation annotation; + private final SynthesizedAggregateAnnotation owner; + private final Map attributeMap; + + public TestSynthesizedAnnotation(SynthesizedAggregateAnnotation owner, Annotation annotation) { + this.owner = owner; + this.attributeMap = new HashMap<>(); + this.annotation = annotation; + for (Method declaredMethod : annotation.annotationType().getDeclaredMethods()) { + attributeMap.put(declaredMethod.getName(), new CacheableAnnotationAttribute(annotation, declaredMethod)); + } + } + + @Override + public Object getRoot() { + return null; + } + + @Override + public Annotation getAnnotation() { + return annotation; + } + + @Override + public int getVerticalDistance() { + return 0; + } + + @Override + public int getHorizontalDistance() { + return 0; + } + + @Override + public boolean hasAttribute(String attributeName, Class returnType) { + return false; + } + + @Override + public Map getAttributes() { + return attributeMap; + } + + @Override + public void setAttribute(String attributeName, AnnotationAttribute attribute) { + attributeMap.put(attributeName, attribute); + } + + @Override + public void replaceAttribute(String attributeName, UnaryOperator operator) { + AnnotationAttribute annotationAttribute = attributeMap.get(attributeName); + if (ObjectUtil.isNotNull(annotationAttribute)) { + attributeMap.put(attributeName, operator.apply(annotationAttribute)); + } + } + + @Override + public Object getAttributeValue(String attributeName) { + return null; + } + + @Override + public Class annotationType() { + return annotation.annotationType(); + } + + @Override + public Object getAttributeValue(String attributeName, Class attributeType) { + return null; + } + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/AliasedAnnotationAttributeTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/AliasedAnnotationAttributeTest.java new file mode 100644 index 000000000..8c0bc8cbc --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/AliasedAnnotationAttributeTest.java @@ -0,0 +1,78 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.util.ReflectUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.*; +import java.lang.reflect.Method; + +public class AliasedAnnotationAttributeTest { + + @Test + public void baseInfoTest() { + // 组合属性 + final Annotation annotation = ClassForTest1.class.getAnnotation(AnnotationForTest.class); + final Method valueMethod = ReflectUtil.getMethod(AnnotationForTest.class, "value"); + final CacheableAnnotationAttribute valueAttribute = new CacheableAnnotationAttribute(annotation, valueMethod); + final Method nameMethod = ReflectUtil.getMethod(AnnotationForTest.class, "name"); + final CacheableAnnotationAttribute nameAttribute = new CacheableAnnotationAttribute(annotation, nameMethod); + final AliasedAnnotationAttribute valueAnnotationAttribute = new AliasedAnnotationAttribute(valueAttribute, nameAttribute); + + // 注解属性 + Assert.assertEquals(annotation, valueAnnotationAttribute.getAnnotation()); + Assert.assertEquals(annotation.annotationType(), valueAnnotationAttribute.getAnnotationType()); + + // 方法属性 + Assert.assertEquals(valueMethod.getAnnotation(Alias.class), valueAnnotationAttribute.getAnnotation(Alias.class)); + Assert.assertEquals(valueMethod.getName(), valueAnnotationAttribute.getAttributeName()); + Assert.assertEquals(nameMethod.getReturnType(), valueAnnotationAttribute.getAttributeType()); + } + + @Test + public void workWhenValueDefaultTest() { + // 组合属性 + final Annotation annotation = ClassForTest1.class.getAnnotation(AnnotationForTest.class); + final Method valueMethod = ReflectUtil.getMethod(AnnotationForTest.class, "value"); + final CacheableAnnotationAttribute valueAttribute = new CacheableAnnotationAttribute(annotation, valueMethod); + final Method nameMethod = ReflectUtil.getMethod(AnnotationForTest.class, "name"); + final CacheableAnnotationAttribute nameAttribute = new CacheableAnnotationAttribute(annotation, nameMethod); + final AliasedAnnotationAttribute annotationAttribute = new AliasedAnnotationAttribute(valueAttribute, nameAttribute); + + // 值处理 + Assert.assertEquals("name", annotationAttribute.getValue()); + Assert.assertFalse(annotationAttribute.isValueEquivalentToDefaultValue()); + Assert.assertTrue(annotationAttribute.isWrapped()); + } + + @Test + public void workWhenValueNonDefaultTest() { + // 组合属性 + final Annotation annotation = ClassForTest2.class.getAnnotation(AnnotationForTest.class); + final Method valueMethod = ReflectUtil.getMethod(AnnotationForTest.class, "value"); + final CacheableAnnotationAttribute valueAttribute = new CacheableAnnotationAttribute(annotation, valueMethod); + final Method nameMethod = ReflectUtil.getMethod(AnnotationForTest.class, "name"); + final CacheableAnnotationAttribute nameAttribute = new CacheableAnnotationAttribute(annotation, nameMethod); + final AliasedAnnotationAttribute annotationAttribute = new AliasedAnnotationAttribute(valueAttribute, nameAttribute); + + // 值处理 + Assert.assertEquals("value", annotationAttribute.getValue()); + Assert.assertFalse(annotationAttribute.isValueEquivalentToDefaultValue()); + Assert.assertTrue(annotationAttribute.isWrapped()); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface AnnotationForTest { + @Alias("value") + String value() default ""; + String name() default ""; + } + + @AnnotationForTest(name = "name", value = "value") + static class ClassForTest1 {} + + @AnnotationForTest(value = "value") + static class ClassForTest2 {} + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationUtilTest.java index 17407fbf5..0e5803c1e 100755 --- a/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationUtilTest.java @@ -16,14 +16,14 @@ public class AnnotationUtilTest { public void getCombinationAnnotationsTest(){ final Annotation[] annotations = AnnotationUtil.getAnnotations(ClassWithAnnotation.class, true); Assert.assertNotNull(annotations); - Assert.assertEquals(3, annotations.length); + Assert.assertEquals(2, annotations.length); } @Test public void getCombinationAnnotationsWithClassTest(){ final AnnotationForTest[] annotations = AnnotationUtil.getCombinationAnnotations(ClassWithAnnotation.class, AnnotationForTest.class); Assert.assertNotNull(annotations); - Assert.assertEquals(2, annotations.length); + Assert.assertEquals(1, annotations.length); Assert.assertEquals("测试", annotations[0].value()); } @@ -42,6 +42,7 @@ public class AnnotationUtilTest { // 加别名适配 final AnnotationForTest annotation = AnnotationUtil.getAnnotationAlias(ClassWithAnnotation.class, AnnotationForTest.class); Assert.assertEquals("测试", annotation.retry()); + Assert.assertTrue(AnnotationUtil.isSynthesizedAnnotation(annotation)); } @AnnotationForTest("测试") diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/CacheableAnnotationAttributeTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/CacheableAnnotationAttributeTest.java new file mode 100644 index 000000000..c1b92fcd1 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/CacheableAnnotationAttributeTest.java @@ -0,0 +1,61 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.util.ReflectUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.*; +import java.lang.reflect.Method; + +public class CacheableAnnotationAttributeTest { + + @Test + public void baseInfoTest() { + final Annotation annotation = ClassForTest1.class.getAnnotation(AnnotationForTest.class); + final Method attribute = ReflectUtil.getMethod(AnnotationForTest.class, "value"); + final CacheableAnnotationAttribute annotationAttribute = new CacheableAnnotationAttribute(annotation, attribute); + // 注解属性 + Assert.assertEquals(annotation, annotationAttribute.getAnnotation()); + Assert.assertEquals(annotation.annotationType(), annotationAttribute.getAnnotationType()); + // 方法属性 + Assert.assertEquals(attribute.getName(), annotationAttribute.getAttributeName()); + Assert.assertEquals(attribute.getReturnType(), annotationAttribute.getAttributeType()); + } + + @Test + public void workWhenValueDefaultTest() { + final Annotation annotation = ClassForTest1.class.getAnnotation(AnnotationForTest.class); + final Method attribute = ReflectUtil.getMethod(AnnotationForTest.class, "value"); + final CacheableAnnotationAttribute annotationAttribute = new CacheableAnnotationAttribute(annotation, attribute); + + // 值处理 + Assert.assertEquals("", annotationAttribute.getValue()); + Assert.assertTrue(annotationAttribute.isValueEquivalentToDefaultValue()); + Assert.assertFalse(annotationAttribute.isWrapped()); + } + + @Test + public void workWhenValueNonDefaultTest() { + final Annotation annotation = ClassForTest2.class.getAnnotation(AnnotationForTest.class); + final Method attribute = ReflectUtil.getMethod(AnnotationForTest.class, "value"); + final CacheableAnnotationAttribute annotationAttribute = new CacheableAnnotationAttribute(annotation, attribute); + + // 值处理 + Assert.assertEquals("test", annotationAttribute.getValue()); + Assert.assertFalse(annotationAttribute.isValueEquivalentToDefaultValue()); + Assert.assertFalse(annotationAttribute.isWrapped()); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface AnnotationForTest { + String value() default ""; + } + + @AnnotationForTest("") + static class ClassForTest1 {} + + @AnnotationForTest("test") + static class ClassForTest2 {} + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/CacheableSynthesizedAnnotationAttributeProcessorTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/CacheableSynthesizedAnnotationAttributeProcessorTest.java new file mode 100644 index 000000000..facdd1263 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/CacheableSynthesizedAnnotationAttributeProcessorTest.java @@ -0,0 +1,99 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.lang.Opt; +import cn.hutool.core.map.MapBuilder; +import cn.hutool.core.util.ClassUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Map; +import java.util.function.UnaryOperator; + +public class CacheableSynthesizedAnnotationAttributeProcessorTest { + + @Test + public void getAttributeValueTest() { + CacheableSynthesizedAnnotationAttributeProcessor processor = new CacheableSynthesizedAnnotationAttributeProcessor(); + + Map values1 = MapBuilder. create().put("name", "name1").put("value", 111).build(); + SynthesizedAnnotation annotation1 = new TestSynthesizedAnnotation(1, 0, values1); + Map values2 = MapBuilder. create().put("name", "name2").put("value", "value2").build(); + SynthesizedAnnotation annotation2 = new TestSynthesizedAnnotation(0, 0, values2); + + Assert.assertEquals("name2", processor.getAttributeValue("name", String.class, Arrays.asList(annotation1, annotation2))); + Assert.assertEquals(Integer.valueOf(111), processor.getAttributeValue("value", Integer.class, Arrays.asList(annotation1, annotation2))); + } + + static class TestSynthesizedAnnotation implements SynthesizedAnnotation { + + private final int verticalDistance; + private final int horizontalDistance; + private final Map value; + + public TestSynthesizedAnnotation(int verticalDistance, int horizontalDistance, Map value) { + this.verticalDistance = verticalDistance; + this.horizontalDistance = horizontalDistance; + this.value = value; + } + + @Override + public Object getRoot() { + return null; + } + + @Override + public Annotation getAnnotation() { + return null; + } + + @Override + public int getVerticalDistance() { + return verticalDistance; + } + + @Override + public int getHorizontalDistance() { + return horizontalDistance; + } + + @Override + public boolean hasAttribute(String attributeName, Class returnType) { + return Opt.ofNullable(value.get(attributeName)) + .map(t -> ClassUtil.isAssignable(returnType, t.getClass())) + .orElse(false); + } + + @Override + public Map getAttributes() { + return null; + } + + @Override + public void setAttribute(String attributeName, AnnotationAttribute attribute) { + + } + + @Override + public void replaceAttribute(String attributeName, UnaryOperator operator) { + + } + + @Override + public Object getAttributeValue(String attributeName) { + return value.get(attributeName); + } + + @Override + public Class annotationType() { + return null; + } + + @Override + public Object getAttributeValue(String attributeName, Class attributeType) { + return null; + } + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/ForceAliasedAnnotationAttributeTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/ForceAliasedAnnotationAttributeTest.java new file mode 100644 index 000000000..d5af87a62 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/ForceAliasedAnnotationAttributeTest.java @@ -0,0 +1,76 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.util.ReflectUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.*; +import java.lang.reflect.Method; + +public class ForceAliasedAnnotationAttributeTest { + + @Test + public void baseInfoTest() { + // 组合属性 + final Annotation annotation = ClassForTest1.class.getAnnotation(AnnotationForTest.class); + final Method valueMethod = ReflectUtil.getMethod(AnnotationForTest.class, "value"); + final CacheableAnnotationAttribute valueAttribute = new CacheableAnnotationAttribute(annotation, valueMethod); + final Method nameMethod = ReflectUtil.getMethod(AnnotationForTest.class, "name"); + final CacheableAnnotationAttribute nameAttribute = new CacheableAnnotationAttribute(annotation, nameMethod); + final ForceAliasedAnnotationAttribute valueAnnotationAttribute = new ForceAliasedAnnotationAttribute(valueAttribute, nameAttribute); + + // 注解属性 + Assert.assertEquals(annotation, valueAnnotationAttribute.getAnnotation()); + Assert.assertEquals(annotation.annotationType(), valueAnnotationAttribute.getAnnotationType()); + + // 方法属性 + Assert.assertEquals(valueMethod.getName(), valueAnnotationAttribute.getAttributeName()); + Assert.assertEquals(valueMethod.getReturnType(), valueAnnotationAttribute.getAttributeType()); + } + + @Test + public void workWhenValueDefaultTest() { + // 组合属性 + final Annotation annotation = ClassForTest1.class.getAnnotation(AnnotationForTest.class); + final Method valueMethod = ReflectUtil.getMethod(AnnotationForTest.class, "value"); + final CacheableAnnotationAttribute valueAttribute = new CacheableAnnotationAttribute(annotation, valueMethod); + final Method nameMethod = ReflectUtil.getMethod(AnnotationForTest.class, "name"); + final CacheableAnnotationAttribute nameAttribute = new CacheableAnnotationAttribute(annotation, nameMethod); + final AliasedAnnotationAttribute valueAnnotationAttribute = new AliasedAnnotationAttribute(valueAttribute, nameAttribute); + + // 值处理 + Assert.assertEquals("name", valueAnnotationAttribute.getValue()); + Assert.assertFalse(valueAnnotationAttribute.isValueEquivalentToDefaultValue()); + Assert.assertTrue(valueAnnotationAttribute.isWrapped()); + } + + @Test + public void workWhenValueNonDefaultTest() { + // 组合属性 + final Annotation annotation = ClassForTest2.class.getAnnotation(AnnotationForTest.class); + final Method valueMethod = ReflectUtil.getMethod(AnnotationForTest.class, "value"); + final CacheableAnnotationAttribute valueAttribute = new CacheableAnnotationAttribute(annotation, valueMethod); + final Method nameMethod = ReflectUtil.getMethod(AnnotationForTest.class, "name"); + final CacheableAnnotationAttribute nameAttribute = new CacheableAnnotationAttribute(annotation, nameMethod); + final ForceAliasedAnnotationAttribute valueAnnotationAttribute = new ForceAliasedAnnotationAttribute(valueAttribute, nameAttribute); + + // 值处理 + Assert.assertEquals("", valueAnnotationAttribute.getValue()); + Assert.assertTrue(valueAnnotationAttribute.isValueEquivalentToDefaultValue()); + Assert.assertTrue(valueAnnotationAttribute.isWrapped()); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface AnnotationForTest { + String value() default ""; + String name() default ""; + } + + @AnnotationForTest(name = "name", value = "value") + static class ClassForTest1 {} + + @AnnotationForTest(value = "value") + static class ClassForTest2 {} + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/GenericSynthesizedAggregateAnnotationTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/GenericSynthesizedAggregateAnnotationTest.java new file mode 100644 index 000000000..c59b9d6bd --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/GenericSynthesizedAggregateAnnotationTest.java @@ -0,0 +1,370 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.util.ReflectUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; + +/** + * 合成注解{@link GenericSynthesizedAggregateAnnotation}的测试用例 + * + * @author huangchengxing + */ +public class GenericSynthesizedAggregateAnnotationTest { + + @Test + public void baseSynthesisAnnotationWorkTest() { + // AnnotatedClass -> @ChildAnnotation -> @ParentAnnotation -> @GrandParentAnnotation + // -> @GrandParentAnnotation + final GrandParentAnnotation grandParentAnnotation = ChildAnnotation.class.getAnnotation(GrandParentAnnotation.class); + final ParentAnnotation parentAnnotation = ChildAnnotation.class.getAnnotation(ParentAnnotation.class); + final ChildAnnotation childAnnotation = AnnotatedClass.class.getAnnotation(ChildAnnotation.class); + final GenericSynthesizedAggregateAnnotation syntheticMetaAnnotation = new GenericSynthesizedAggregateAnnotation(childAnnotation); + + // Annotation & AnnotatedElement + Assert.assertEquals(GenericSynthesizedAggregateAnnotation.class, syntheticMetaAnnotation.annotationType()); + Assert.assertTrue(syntheticMetaAnnotation.isAnnotationPresent(GrandParentAnnotation.class)); + Assert.assertTrue(syntheticMetaAnnotation.isAnnotationPresent(ParentAnnotation.class)); + Assert.assertTrue(syntheticMetaAnnotation.isAnnotationPresent(ChildAnnotation.class)); + Assert.assertEquals(grandParentAnnotation, syntheticMetaAnnotation.getAnnotation(GrandParentAnnotation.class)); + Assert.assertEquals(parentAnnotation, syntheticMetaAnnotation.getAnnotation(ParentAnnotation.class)); + Assert.assertEquals(childAnnotation, syntheticMetaAnnotation.getAnnotation(ChildAnnotation.class)); + Assert.assertEquals( + Arrays.asList(childAnnotation, grandParentAnnotation, parentAnnotation), + Arrays.asList(syntheticMetaAnnotation.getAnnotations()) + ); + + // 扩展方法 + Assert.assertNotNull(syntheticMetaAnnotation.getSynthesizedAnnotation(GrandParentAnnotation.class)); + Assert.assertNotNull(syntheticMetaAnnotation.getSynthesizedAnnotation(ParentAnnotation.class)); + Assert.assertNotNull(syntheticMetaAnnotation.getSynthesizedAnnotation(ChildAnnotation.class)); + Assert.assertEquals(3, syntheticMetaAnnotation.getAllSynthesizedAnnotation().size()); + + // 属性 + Assert.assertEquals(SynthesizedAnnotationSelector.NEAREST_AND_OLDEST_PRIORITY, syntheticMetaAnnotation.getAnnotationSelector()); + Assert.assertEquals(CacheableSynthesizedAnnotationAttributeProcessor.class, syntheticMetaAnnotation.getAnnotationAttributeProcessor().getClass()); + Assert.assertEquals(3, syntheticMetaAnnotation.getAnnotationPostProcessors().size()); + } + + @Test + public void synthesisAnnotationAttributeTest() { + final ChildAnnotation rootAnnotation = AnnotatedClass.class.getAnnotation(ChildAnnotation.class); + GenericSynthesizedAggregateAnnotation syntheticMetaAnnotation = new GenericSynthesizedAggregateAnnotation(rootAnnotation); + Assert.assertEquals(syntheticMetaAnnotation.getSource(), Collections.singletonList(rootAnnotation)); + Assert.assertEquals(syntheticMetaAnnotation.annotationType(), GenericSynthesizedAggregateAnnotation.class); + Assert.assertEquals(3, syntheticMetaAnnotation.getAnnotations().length); + + Assert.assertEquals("Child!", syntheticMetaAnnotation.getAttributeValue("childValue", String.class)); + Assert.assertEquals("Child!", syntheticMetaAnnotation.getAttributeValue("childValueAlias", String.class)); + Assert.assertEquals("Child's Parent!", syntheticMetaAnnotation.getAttributeValue("parentValue", String.class)); + Assert.assertEquals("Child's GrandParent!", syntheticMetaAnnotation.getAttributeValue("grandParentValue", String.class)); + } + + @Test + public void syntheticAnnotationTest() { + final ChildAnnotation rootAnnotation = AnnotatedClass.class.getAnnotation(ChildAnnotation.class); + GenericSynthesizedAggregateAnnotation syntheticMetaAnnotation = new GenericSynthesizedAggregateAnnotation(rootAnnotation); + + final ChildAnnotation childAnnotation = syntheticMetaAnnotation.synthesize(ChildAnnotation.class); + SynthesizedAnnotation childSyntheticAnnotation = syntheticMetaAnnotation.getSynthesizedAnnotation(ChildAnnotation.class); + Assert.assertNotNull(childSyntheticAnnotation); + Assert.assertTrue(childSyntheticAnnotation.hasAttribute("childValue", String.class)); + Assert.assertEquals(AnnotatedClass.class.getAnnotation(ChildAnnotation.class), childSyntheticAnnotation.getRoot()); + Assert.assertEquals(AnnotatedClass.class.getAnnotation(ChildAnnotation.class), childSyntheticAnnotation.getAnnotation()); + Assert.assertTrue(syntheticMetaAnnotation.isAnnotationPresent(ChildAnnotation.class)); + Assert.assertNotNull(childAnnotation); + Assert.assertEquals("Child!", childAnnotation.childValue()); + Assert.assertEquals("Child!", childAnnotation.childValueAlias()); + Assert.assertEquals(childAnnotation.grandParentType(), Integer.class); + Assert.assertThrows(IllegalArgumentException.class, () -> new GenericSynthesizedAggregateAnnotation(childAnnotation)); + + final ParentAnnotation parentAnnotation = syntheticMetaAnnotation.synthesize(ParentAnnotation.class); + SynthesizedAnnotation parentSyntheticAnnotation = syntheticMetaAnnotation.getSynthesizedAnnotation(ParentAnnotation.class); + Assert.assertNotNull(parentSyntheticAnnotation); + Assert.assertTrue(parentSyntheticAnnotation.hasAttribute("parentValue", String.class)); + Assert.assertEquals(AnnotatedClass.class.getAnnotation(ChildAnnotation.class), parentSyntheticAnnotation.getRoot()); + Assert.assertEquals(ChildAnnotation.class.getAnnotation(ParentAnnotation.class), parentSyntheticAnnotation.getAnnotation()); + Assert.assertNotNull(parentAnnotation); + Assert.assertEquals("Child's Parent!", parentAnnotation.parentValue()); + Assert.assertEquals("java.lang.Void", parentAnnotation.grandParentType()); + Assert.assertThrows(IllegalArgumentException.class, () -> new GenericSynthesizedAggregateAnnotation(parentAnnotation)); + + final GrandParentAnnotation grandParentAnnotation = syntheticMetaAnnotation.synthesize(GrandParentAnnotation.class); + SynthesizedAnnotation grandParentSyntheticAnnotation = syntheticMetaAnnotation.getSynthesizedAnnotation(GrandParentAnnotation.class); + Assert.assertNotNull(grandParentSyntheticAnnotation); + Assert.assertTrue(grandParentSyntheticAnnotation.hasAttribute("grandParentType", Class.class)); + Assert.assertEquals(AnnotatedClass.class.getAnnotation(ChildAnnotation.class), grandParentSyntheticAnnotation.getRoot()); + Assert.assertEquals(ChildAnnotation.class.getAnnotation(GrandParentAnnotation.class), grandParentSyntheticAnnotation.getAnnotation()); + Assert.assertTrue(syntheticMetaAnnotation.isAnnotationPresent(GrandParentAnnotation.class)); + Assert.assertNotNull(grandParentAnnotation); + Assert.assertEquals("Child's GrandParent!", grandParentAnnotation.grandParentValue()); + Assert.assertEquals(grandParentAnnotation.grandParentType(), Integer.class); + Assert.assertThrows(IllegalArgumentException.class, () -> new GenericSynthesizedAggregateAnnotation(grandParentAnnotation)); + } + + @Test + public void linkTest() { + final Method method = ReflectUtil.getMethod(AnnotationForLinkTest.class, "value"); + final SynthesizedAggregateAnnotation synthesizedAnnotationAggregator = new GenericSynthesizedAggregateAnnotation(method.getAnnotation(AliasFor.class)); + final Link link = synthesizedAnnotationAggregator.synthesize(Link.class); + Assert.assertEquals(AnnotationForLinkTest.class, link.annotation()); + Assert.assertEquals("name", link.attribute()); + } + + @Test + public void mirrorAttributeTest() { + AnnotationForMirrorTest annotation = ClassForMirrorTest.class.getAnnotation(AnnotationForMirrorTest.class); + SynthesizedAggregateAnnotation synthetic = new GenericSynthesizedAggregateAnnotation(annotation); + AnnotationForMirrorTest syntheticAnnotation = synthetic.synthesize(AnnotationForMirrorTest.class); + Assert.assertEquals("Foo", syntheticAnnotation.name()); + Assert.assertEquals("Foo", syntheticAnnotation.value()); + + annotation = ClassForMirrorTest2.class.getAnnotation(AnnotationForMirrorTest.class); + synthetic = new GenericSynthesizedAggregateAnnotation(annotation); + syntheticAnnotation = synthetic.synthesize(AnnotationForMirrorTest.class); + Assert.assertEquals("Foo", syntheticAnnotation.name()); + Assert.assertEquals("Foo", syntheticAnnotation.value()); + + annotation = ClassForMirrorTest3.class.getAnnotation(AnnotationForMirrorTest.class); + synthetic = new GenericSynthesizedAggregateAnnotation(annotation); + syntheticAnnotation = synthetic.synthesize(AnnotationForMirrorTest.class); + AnnotationForMirrorTest finalSyntheticAnnotation = syntheticAnnotation; + Assert.assertThrows(IllegalArgumentException.class, finalSyntheticAnnotation::name); + } + + @Test + public void aliasForTest() { + AnnotationForAliasForTest annotation = ClassForAliasForTest.class.getAnnotation(AnnotationForAliasForTest.class); + SynthesizedAggregateAnnotation synthetic = new GenericSynthesizedAggregateAnnotation(annotation); + MetaAnnotationForAliasForTest metaAnnotation = synthetic.synthesize(MetaAnnotationForAliasForTest.class); + Assert.assertEquals("Meta", metaAnnotation.name()); + AnnotationForAliasForTest childAnnotation = synthetic.synthesize(AnnotationForAliasForTest.class); + Assert.assertEquals("", childAnnotation.value()); + + annotation = ClassForAliasForTest2.class.getAnnotation(AnnotationForAliasForTest.class); + synthetic = new GenericSynthesizedAggregateAnnotation(annotation); + metaAnnotation = synthetic.synthesize(MetaAnnotationForAliasForTest.class); + Assert.assertEquals("Foo", metaAnnotation.name()); + childAnnotation = synthetic.synthesize(AnnotationForAliasForTest.class); + Assert.assertEquals("Foo", childAnnotation.value()); + } + + @Test + public void forceAliasForTest() { + AnnotationForceForAliasForTest annotation = ClassForForceAliasForTest.class.getAnnotation(AnnotationForceForAliasForTest.class); + SynthesizedAggregateAnnotation synthetic = new GenericSynthesizedAggregateAnnotation(annotation); + MetaAnnotationForForceAliasForTest metaAnnotation = synthetic.synthesize(MetaAnnotationForForceAliasForTest.class); + Assert.assertEquals("", metaAnnotation.name()); + AnnotationForceForAliasForTest childAnnotation = synthetic.synthesize(AnnotationForceForAliasForTest.class); + Assert.assertEquals("", childAnnotation.value()); + + annotation = ClassForForceAliasForTest2.class.getAnnotation(AnnotationForceForAliasForTest.class); + synthetic = new GenericSynthesizedAggregateAnnotation(annotation); + metaAnnotation = synthetic.synthesize(MetaAnnotationForForceAliasForTest.class); + Assert.assertEquals("Foo", metaAnnotation.name()); + childAnnotation = synthetic.synthesize(AnnotationForceForAliasForTest.class); + Assert.assertEquals("Foo", childAnnotation.value()); + } + + @Test + public void aliasForAndMirrorTest() { + AnnotationForMirrorThenAliasForTest annotation = ClassForAliasForAndMirrorTest.class.getAnnotation(AnnotationForMirrorThenAliasForTest.class); + SynthesizedAggregateAnnotation synthetic = new GenericSynthesizedAggregateAnnotation(annotation); + MetaAnnotationForMirrorThenAliasForTest metaAnnotation = synthetic.synthesize(MetaAnnotationForMirrorThenAliasForTest.class); + Assert.assertEquals("test", metaAnnotation.name()); + Assert.assertEquals("test", metaAnnotation.value()); + AnnotationForMirrorThenAliasForTest childAnnotation = synthetic.synthesize(AnnotationForMirrorThenAliasForTest.class); + Assert.assertEquals("test", childAnnotation.childValue()); + } + + @Test + public void multiAliasForTest() { + final AnnotationForMultiAliasForTest annotation = ClassForMultiAliasForTest.class.getAnnotation(AnnotationForMultiAliasForTest.class); + final SynthesizedAggregateAnnotation synthetic = new GenericSynthesizedAggregateAnnotation(annotation); + + final MetaAnnotationForMultiAliasForTest1 metaAnnotation1 = synthetic.synthesize(MetaAnnotationForMultiAliasForTest1.class); + Assert.assertEquals("test", metaAnnotation1.name()); + Assert.assertEquals("test", metaAnnotation1.value1()); + final MetaAnnotationForMultiAliasForTest2 metaAnnotation2 = synthetic.synthesize(MetaAnnotationForMultiAliasForTest2.class); + Assert.assertEquals("test", metaAnnotation2.value2()); + final AnnotationForMultiAliasForTest childAnnotation = synthetic.synthesize(AnnotationForMultiAliasForTest.class); + Assert.assertEquals("test", childAnnotation.value3()); + } + + @Test + public void implicitAliasTest() { + final AnnotationForImplicitAliasTest annotation = ClassForImplicitAliasTest.class.getAnnotation(AnnotationForImplicitAliasTest.class); + final SynthesizedAggregateAnnotation synthetic = new GenericSynthesizedAggregateAnnotation(annotation); + + final MetaAnnotationForImplicitAliasTest metaAnnotation = synthetic.synthesize(MetaAnnotationForImplicitAliasTest.class); + Assert.assertEquals("Meta", metaAnnotation.name()); + Assert.assertEquals("Foo", metaAnnotation.value()); + final AnnotationForImplicitAliasTest childAnnotation = synthetic.synthesize(AnnotationForImplicitAliasTest.class); + Assert.assertEquals("Foo", childAnnotation.value()); + } + + // 注解结构如下: + // AnnotatedClass -> @ChildAnnotation -> @ParentAnnotation -> @GrandParentAnnotation + // -> @GrandParentAnnotation + @ChildAnnotation(childValueAlias = "Child!", grandParentType = Integer.class) + static class AnnotatedClass {} + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.ANNOTATION_TYPE }) + @interface GrandParentAnnotation { + String grandParentValue() default ""; + Class grandParentType() default Void.class; + } + + @GrandParentAnnotation(grandParentValue = "Parent's GrandParent!") // 覆盖元注解@GrandParentAnnotation的属性 + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.TYPE }) + @interface ParentAnnotation { + String parentValue() default ""; + String grandParentType() default "java.lang.Void"; + } + + @GrandParentAnnotation(grandParentValue = "Child's GrandParent!") // 重复的元注解,靠近根注解的优先级高 + @ParentAnnotation(parentValue = "Child's Parent!") // 覆盖元注解@ParentAnnotation的属性 + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface ChildAnnotation { + String childValueAlias() default ""; + @Alias("childValueAlias") + String childValue() default ""; + Class grandParentType() default Void.class; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface AnnotationForMirrorTest { + //@Link(attribute = "name") + @MirrorFor(attribute = "name") + String value() default ""; + //@Link(attribute = "value") + @MirrorFor(attribute = "value") + String name() default ""; + } + @AnnotationForMirrorTest("Foo") + static class ClassForMirrorTest {} + @AnnotationForMirrorTest(name = "Foo") + static class ClassForMirrorTest2 {} + @AnnotationForMirrorTest(value = "Aoo", name = "Foo") + static class ClassForMirrorTest3 {} + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface MetaAnnotationForAliasForTest { + String name() default ""; + } + @MetaAnnotationForAliasForTest(name = "Meta") + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface AnnotationForAliasForTest { + @AliasFor( + annotation = MetaAnnotationForAliasForTest.class, + attribute = "name" + ) + String value() default ""; + } + @AnnotationForAliasForTest + static class ClassForAliasForTest {} + @AnnotationForAliasForTest("Foo") + static class ClassForAliasForTest2 {} + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface MetaAnnotationForForceAliasForTest { + String name() default ""; + } + @MetaAnnotationForForceAliasForTest(name = "Meta") + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface AnnotationForceForAliasForTest { + //@Link( + // annotation = MetaAnnotationForForceAliasForTest.class, + // attribute = "name", + // type = RelationType.FORCE_ALIAS_FOR + //) + @ForceAliasFor(annotation = MetaAnnotationForForceAliasForTest.class, attribute = "name") + String value() default ""; + } + @AnnotationForceForAliasForTest + static class ClassForForceAliasForTest {} + @AnnotationForceForAliasForTest("Foo") + static class ClassForForceAliasForTest2 {} + + @interface AnnotationForLinkTest { + @AliasFor(attribute = "name", annotation = AnnotationForLinkTest.class) + String value() default "value"; + String name() default "name"; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface MetaAnnotationForMirrorThenAliasForTest { + @MirrorFor(attribute = "value") + String name() default ""; + @MirrorFor(attribute = "name") + String value() default ""; + } + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @MetaAnnotationForMirrorThenAliasForTest("Meta") + @interface AnnotationForMirrorThenAliasForTest { + @AliasFor(attribute = "name", annotation = MetaAnnotationForMirrorThenAliasForTest.class) + String childValue() default "value"; + } + @AnnotationForMirrorThenAliasForTest(childValue = "test") + static class ClassForAliasForAndMirrorTest{} + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface MetaAnnotationForMultiAliasForTest1 { + @MirrorFor(attribute = "value1") + String name() default ""; + @MirrorFor(attribute = "name") + String value1() default ""; + } + @MetaAnnotationForMultiAliasForTest1 + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface MetaAnnotationForMultiAliasForTest2 { + @AliasFor(attribute = "name", annotation = MetaAnnotationForMultiAliasForTest1.class) + String value2() default ""; + } + @MetaAnnotationForMultiAliasForTest2 + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface AnnotationForMultiAliasForTest { + @AliasFor(attribute = "value2", annotation = MetaAnnotationForMultiAliasForTest2.class) + String value3() default "value"; + } + @AnnotationForMultiAliasForTest(value3 = "test") + static class ClassForMultiAliasForTest{} + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface MetaAnnotationForImplicitAliasTest { + @MirrorFor(attribute = "value") + String name() default ""; + @MirrorFor(attribute = "name") + String value() default ""; + } + @MetaAnnotationForImplicitAliasTest("Meta") + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface AnnotationForImplicitAliasTest { + String value() default ""; + } + @AnnotationForImplicitAliasTest("Foo") + static class ClassForImplicitAliasTest {} + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/MirrorLinkAnnotationPostProcessorTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/MirrorLinkAnnotationPostProcessorTest.java new file mode 100644 index 000000000..e65f4e611 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/MirrorLinkAnnotationPostProcessorTest.java @@ -0,0 +1,198 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.*; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.UnaryOperator; + +public class MirrorLinkAnnotationPostProcessorTest { + + @Test + public void processTest() { + MirrorLinkAnnotationPostProcessor processor = new MirrorLinkAnnotationPostProcessor(); + + Map, SynthesizedAnnotation> annotationMap = new HashMap<>(); + SynthesizedAggregateAnnotation synthesizedAnnotationAggregator = new TestSynthesizedAggregateAnnotation(annotationMap); + AnnotationForTest annotation = ClassForTest.class.getAnnotation(AnnotationForTest.class); + SynthesizedAnnotation synthesizedAnnotation = new TestSynthesizedAnnotation(synthesizedAnnotationAggregator, annotation); + annotationMap.put(annotation.annotationType(), synthesizedAnnotation); + + processor.process(synthesizedAnnotation, synthesizedAnnotationAggregator); + AnnotationAttribute valueAttribute = synthesizedAnnotation.getAttributes().get("value"); + Assert.assertEquals(ReflectUtil.getMethod(AnnotationForTest.class, "value"), valueAttribute.getAttribute()); + Assert.assertTrue(valueAttribute.isWrapped()); + Assert.assertEquals(MirroredAnnotationAttribute.class, valueAttribute.getClass()); + + AnnotationAttribute nameAttribute = synthesizedAnnotation.getAttributes().get("name"); + Assert.assertEquals(ReflectUtil.getMethod(AnnotationForTest.class, "name"), nameAttribute.getAttribute()); + Assert.assertTrue(nameAttribute.isWrapped()); + Assert.assertEquals(MirroredAnnotationAttribute.class, nameAttribute.getClass()); + + Assert.assertEquals(((WrappedAnnotationAttribute)nameAttribute).getLinked(), ((WrappedAnnotationAttribute)valueAttribute).getOriginal()); + Assert.assertEquals(((WrappedAnnotationAttribute)nameAttribute).getOriginal(), ((WrappedAnnotationAttribute)valueAttribute).getLinked()); + } + + @AnnotationForTest + static class ClassForTest {} + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface AnnotationForTest { + @Link(attribute = "name", type = RelationType.MIRROR_FOR) + String value() default ""; + @Link(attribute = "value", type = RelationType.MIRROR_FOR) + String name() default ""; + } + + static class TestSynthesizedAggregateAnnotation implements SynthesizedAggregateAnnotation { + + private final Map, SynthesizedAnnotation> annotationMap; + + public TestSynthesizedAggregateAnnotation(Map, SynthesizedAnnotation> annotationMap) { + this.annotationMap = annotationMap; + } + + @Override + public Object getSource() { + return null; + } + + @Override + public SynthesizedAnnotationSelector getAnnotationSelector() { + return null; + } + + @Override + public SynthesizedAnnotationAttributeProcessor getAnnotationAttributeProcessor() { + return null; + } + + @Override + public Collection getAnnotationPostProcessors() { + return null; + } + + @Override + public SynthesizedAnnotation getSynthesizedAnnotation(Class annotationType) { + return annotationMap.get(annotationType); + } + + @Override + public Map, SynthesizedAnnotation> getAllSynthesizedAnnotation() { + return null; + } + + @Override + public T getAnnotation(Class annotationType) { + return null; + } + + @Override + public boolean isAnnotationPresent(Class annotationType) { + return false; + } + + @Override + public Annotation[] getAnnotations() { + return new Annotation[0]; + } + + @Override + public T synthesize(Class annotationType) { + return null; + } + + @Override + public Object getAttributeValue(String attributeName, Class attributeType) { + return null; + } + + @Override + public Object getRoot() { + return null; + } + + } + + static class TestSynthesizedAnnotation implements SynthesizedAnnotation { + + private final Annotation annotation; + private final SynthesizedAggregateAnnotation owner; + private final Map attributeMap; + + public TestSynthesizedAnnotation(SynthesizedAggregateAnnotation owner, Annotation annotation) { + this.owner = owner; + this.attributeMap = new HashMap<>(); + this.annotation = annotation; + for (Method declaredMethod : annotation.annotationType().getDeclaredMethods()) { + attributeMap.put(declaredMethod.getName(), new CacheableAnnotationAttribute(annotation, declaredMethod)); + } + } + + @Override + public Object getRoot() { + return null; + } + + @Override + public Annotation getAnnotation() { + return annotation; + } + + @Override + public int getVerticalDistance() { + return 0; + } + + @Override + public int getHorizontalDistance() { + return 0; + } + + @Override + public boolean hasAttribute(String attributeName, Class returnType) { + return false; + } + + @Override + public Map getAttributes() { + return attributeMap; + } + + @Override + public void setAttribute(String attributeName, AnnotationAttribute attribute) { + attributeMap.put(attributeName, attribute); + } + + @Override + public void replaceAttribute(String attributeName, UnaryOperator operator) { + AnnotationAttribute annotationAttribute = attributeMap.get(attributeName); + if (ObjectUtil.isNotNull(annotationAttribute)) { + attributeMap.put(attributeName, operator.apply(annotationAttribute)); + } + } + + @Override + public Object getAttributeValue(String attributeName) { + return null; + } + + @Override + public Class annotationType() { + return annotation.annotationType(); + } + + @Override + public Object getAttributeValue(String attributeName, Class attributeType) { + return null; + } + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/MirroredAnnotationAttributeTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/MirroredAnnotationAttributeTest.java new file mode 100644 index 000000000..a4f648514 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/MirroredAnnotationAttributeTest.java @@ -0,0 +1,76 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.util.ReflectUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.*; +import java.lang.reflect.Method; + +public class MirroredAnnotationAttributeTest { + + @Test + public void baseInfoTest() { + // 组合属性 + final Annotation annotation = ClassForTest1.class.getAnnotation(AnnotationForTest.class); + final Method valueMethod = ReflectUtil.getMethod(AnnotationForTest.class, "value"); + final CacheableAnnotationAttribute valueAttribute = new CacheableAnnotationAttribute(annotation, valueMethod); + final Method nameMethod = ReflectUtil.getMethod(AnnotationForTest.class, "name"); + final CacheableAnnotationAttribute nameAttribute = new CacheableAnnotationAttribute(annotation, nameMethod); + final MirroredAnnotationAttribute nameAnnotationAttribute = new MirroredAnnotationAttribute(nameAttribute, valueAttribute); + + // 注解属性 + Assert.assertEquals(annotation, nameAnnotationAttribute.getAnnotation()); + Assert.assertEquals(annotation.annotationType(), nameAnnotationAttribute.getAnnotationType()); + + // 方法属性 + Assert.assertEquals(nameMethod.getName(), nameAnnotationAttribute.getAttributeName()); + Assert.assertEquals(nameMethod.getReturnType(), nameAnnotationAttribute.getAttributeType()); + } + + @Test + public void workWhenValueDefaultTest() { + // 组合属性 + final Annotation annotation = ClassForTest2.class.getAnnotation(AnnotationForTest.class); + final Method valueMethod = ReflectUtil.getMethod(AnnotationForTest.class, "value"); + final CacheableAnnotationAttribute valueAttribute = new CacheableAnnotationAttribute(annotation, valueMethod); + final Method nameMethod = ReflectUtil.getMethod(AnnotationForTest.class, "name"); + final CacheableAnnotationAttribute nameAttribute = new CacheableAnnotationAttribute(annotation, nameMethod); + final MirroredAnnotationAttribute nameAnnotationAttribute = new MirroredAnnotationAttribute(nameAttribute, valueAttribute); + + // 值处理 + Assert.assertEquals("", nameAnnotationAttribute.getValue()); + Assert.assertTrue(nameAnnotationAttribute.isValueEquivalentToDefaultValue()); + Assert.assertTrue(nameAnnotationAttribute.isWrapped()); + } + + @Test + public void workWhenValueNonDefaultTest() { + // 组合属性 + final Annotation annotation = ClassForTest1.class.getAnnotation(AnnotationForTest.class); + final Method valueMethod = ReflectUtil.getMethod(AnnotationForTest.class, "value"); + final CacheableAnnotationAttribute valueAttribute = new CacheableAnnotationAttribute(annotation, valueMethod); + final Method nameMethod = ReflectUtil.getMethod(AnnotationForTest.class, "name"); + final CacheableAnnotationAttribute nameAttribute = new CacheableAnnotationAttribute(annotation, nameMethod); + final MirroredAnnotationAttribute nameAnnotationAttribute = new MirroredAnnotationAttribute(nameAttribute, valueAttribute); + + // 值处理 + Assert.assertEquals("name", nameAnnotationAttribute.getValue()); + Assert.assertFalse(nameAnnotationAttribute.isValueEquivalentToDefaultValue()); + Assert.assertTrue(nameAnnotationAttribute.isWrapped()); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @interface AnnotationForTest { + String value() default ""; + String name() default ""; + } + + @AnnotationForTest(value = "name") + static class ClassForTest1 {} + + @AnnotationForTest + static class ClassForTest2 {} + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/SynthesizedAnnotationSelectorTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/SynthesizedAnnotationSelectorTest.java new file mode 100644 index 000000000..80f06aeef --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/SynthesizedAnnotationSelectorTest.java @@ -0,0 +1,146 @@ +package cn.hutool.core.annotation; + +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.Annotation; +import java.util.Map; +import java.util.function.UnaryOperator; + +public class SynthesizedAnnotationSelectorTest { + + @Test + public void chooseTest() { + final SynthesizedAnnotationSelector.NearestAndOldestPrioritySelector selector = (SynthesizedAnnotationSelector.NearestAndOldestPrioritySelector)SynthesizedAnnotationSelector.NEAREST_AND_OLDEST_PRIORITY; + + TestSynthesizedAnnotation annotation1 = new TestSynthesizedAnnotation(0, 0); + TestSynthesizedAnnotation annotation2 = new TestSynthesizedAnnotation(0, 0); + Assert.assertEquals(annotation1, selector.choose(annotation1, annotation2)); + + annotation1 = new TestSynthesizedAnnotation(0, 1); + annotation2 = new TestSynthesizedAnnotation(0, 0); + Assert.assertEquals(annotation1, selector.choose(annotation1, annotation2)); + + annotation1 = new TestSynthesizedAnnotation(1, 0); + annotation2 = new TestSynthesizedAnnotation(0, 0); + Assert.assertEquals(annotation2, selector.choose(annotation1, annotation2)); + } + + @Test + public void nearestAndNewestPriorityTest() { + final SynthesizedAnnotationSelector.NearestAndNewestPrioritySelector selector = (SynthesizedAnnotationSelector.NearestAndNewestPrioritySelector)SynthesizedAnnotationSelector.NEAREST_AND_NEWEST_PRIORITY; + + TestSynthesizedAnnotation annotation1 = new TestSynthesizedAnnotation(0, 0); + TestSynthesizedAnnotation annotation2 = new TestSynthesizedAnnotation(0, 0); + Assert.assertEquals(annotation2, selector.choose(annotation1, annotation2)); + + annotation1 = new TestSynthesizedAnnotation(0, 1); + annotation2 = new TestSynthesizedAnnotation(0, 0); + Assert.assertEquals(annotation2, selector.choose(annotation1, annotation2)); + + annotation1 = new TestSynthesizedAnnotation(0, 0); + annotation2 = new TestSynthesizedAnnotation(1, 0); + Assert.assertEquals(annotation1, selector.choose(annotation1, annotation2)); + } + + @Test + public void farthestAndOldestPriorityTest() { + final SynthesizedAnnotationSelector.FarthestAndOldestPrioritySelector selector = (SynthesizedAnnotationSelector.FarthestAndOldestPrioritySelector)SynthesizedAnnotationSelector.FARTHEST_AND_OLDEST_PRIORITY; + + TestSynthesizedAnnotation annotation1 = new TestSynthesizedAnnotation(0, 0); + TestSynthesizedAnnotation annotation2 = new TestSynthesizedAnnotation(0, 0); + Assert.assertEquals(annotation1, selector.choose(annotation1, annotation2)); + + annotation1 = new TestSynthesizedAnnotation(0, 1); + annotation2 = new TestSynthesizedAnnotation(0, 0); + Assert.assertEquals(annotation1, selector.choose(annotation1, annotation2)); + + annotation1 = new TestSynthesizedAnnotation(0, 0); + annotation2 = new TestSynthesizedAnnotation(1, 0); + Assert.assertEquals(annotation2, selector.choose(annotation1, annotation2)); + } + + @Test + public void farthestAndNewestPriorityTest() { + final SynthesizedAnnotationSelector.FarthestAndNewestPrioritySelector selector = (SynthesizedAnnotationSelector.FarthestAndNewestPrioritySelector)SynthesizedAnnotationSelector.FARTHEST_AND_NEWEST_PRIORITY; + + TestSynthesizedAnnotation annotation1 = new TestSynthesizedAnnotation(0, 0); + TestSynthesizedAnnotation annotation2 = new TestSynthesizedAnnotation(0, 0); + Assert.assertEquals(annotation2, selector.choose(annotation1, annotation2)); + + annotation1 = new TestSynthesizedAnnotation(0, 1); + annotation2 = new TestSynthesizedAnnotation(0, 0); + Assert.assertEquals(annotation2, selector.choose(annotation1, annotation2)); + + annotation1 = new TestSynthesizedAnnotation(1, 0); + annotation2 = new TestSynthesizedAnnotation(0, 0); + Assert.assertEquals(annotation1, selector.choose(annotation1, annotation2)); + } + + static class TestSynthesizedAnnotation implements SynthesizedAnnotation { + + private final int verticalDistance; + private final int horizontalDistance; + + public TestSynthesizedAnnotation(int verticalDistance, int horizontalDistance) { + this.verticalDistance = verticalDistance; + this.horizontalDistance = horizontalDistance; + } + + @Override + public Object getRoot() { + return null; + } + + @Override + public Annotation getAnnotation() { + return null; + } + + @Override + public int getVerticalDistance() { + return this.verticalDistance; + } + + @Override + public int getHorizontalDistance() { + return this.horizontalDistance; + } + + @Override + public boolean hasAttribute(String attributeName, Class returnType) { + return false; + } + + @Override + public Map getAttributes() { + return null; + } + + @Override + public void setAttribute(String attributeName, AnnotationAttribute attribute) { + + } + + @Override + public void replaceAttribute(String attributeName, UnaryOperator operator) { + + } + + @Override + public Object getAttributeValue(String attributeName) { + return null; + } + + @Override + public Class annotationType() { + return null; + } + + @Override + public Object getAttributeValue(String attributeName, Class attributeType) { + return null; + } + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticMetaAnnotationTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticMetaAnnotationTest.java deleted file mode 100644 index 009f1e205..000000000 --- a/hutool-core/src/test/java/cn/hutool/core/annotation/SyntheticMetaAnnotationTest.java +++ /dev/null @@ -1,88 +0,0 @@ -package cn.hutool.core.annotation; - -import org.junit.Assert; -import org.junit.Test; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 合成注解{@link SyntheticMetaAnnotation}的测试用例 - * - * @author huangchengxing - */ -public class SyntheticMetaAnnotationTest { - - @Test - public void testSynthesisAnnotation() { - ChildAnnotation rootAnnotation = AnnotatedClass.class.getAnnotation(ChildAnnotation.class); - SyntheticMetaAnnotation syntheticMetaAnnotation = new SyntheticMetaAnnotation(rootAnnotation); - Assert.assertEquals(syntheticMetaAnnotation.getSource(), rootAnnotation); - Assert.assertEquals(syntheticMetaAnnotation.annotationType(), SyntheticMetaAnnotation.class); - Assert.assertEquals(1, syntheticMetaAnnotation.getDeclaredAnnotations().length); - Assert.assertEquals(syntheticMetaAnnotation.getDeclaredAnnotations()[0], rootAnnotation); - Assert.assertEquals(3, syntheticMetaAnnotation.getAnnotations().length); - - Assert.assertEquals("Child!", syntheticMetaAnnotation.getAttribute("childValue", String.class)); - Assert.assertEquals("Child!", syntheticMetaAnnotation.getAttribute("childValueAlias", String.class)); - Assert.assertEquals("Child's Parent!", syntheticMetaAnnotation.getAttribute("parentValue", String.class)); - Assert.assertEquals("Child's GrandParent!", syntheticMetaAnnotation.getAttribute("grandParentValue", String.class)); - - ChildAnnotation childAnnotation = syntheticMetaAnnotation.syntheticAnnotation(ChildAnnotation.class); - Assert.assertTrue(syntheticMetaAnnotation.isAnnotationPresent(ChildAnnotation.class)); - Assert.assertNotNull(childAnnotation); - Assert.assertEquals("Child!", childAnnotation.childValue()); - Assert.assertEquals("Child!", childAnnotation.childValueAlias()); - Assert.assertEquals(childAnnotation.grandParentType(), Integer.class); - Assert.assertThrows(IllegalArgumentException.class, () -> new SyntheticMetaAnnotation(childAnnotation)); - - ParentAnnotation parentAnnotation = syntheticMetaAnnotation.syntheticAnnotation(ParentAnnotation.class); - Assert.assertTrue(syntheticMetaAnnotation.isAnnotationPresent(ParentAnnotation.class)); - Assert.assertNotNull(parentAnnotation); - Assert.assertEquals("Child's Parent!", parentAnnotation.parentValue()); - Assert.assertEquals("java.lang.Void", parentAnnotation.grandParentType()); - Assert.assertThrows(IllegalArgumentException.class, () -> new SyntheticMetaAnnotation(parentAnnotation)); - - GrandParentAnnotation grandParentAnnotation = syntheticMetaAnnotation.syntheticAnnotation(GrandParentAnnotation.class); - Assert.assertTrue(syntheticMetaAnnotation.isAnnotationPresent(GrandParentAnnotation.class)); - Assert.assertNotNull(grandParentAnnotation); - Assert.assertEquals("Child's GrandParent!", grandParentAnnotation.grandParentValue()); - Assert.assertEquals(grandParentAnnotation.grandParentType(), Integer.class); - Assert.assertThrows(IllegalArgumentException.class, () -> new SyntheticMetaAnnotation(grandParentAnnotation)); - } - - // 注解结构如下: - // AnnotatedClass -> @ChildAnnotation -> @ParentAnnotation -> @GrandParentAnnotation - // -> @GrandParentAnnotation - @ChildAnnotation(childValueAlias = "Child!", grandParentType = Integer.class) - static class AnnotatedClass {} - - @Retention(RetentionPolicy.RUNTIME) - @Target({ ElementType.ANNOTATION_TYPE }) - @interface GrandParentAnnotation { - String grandParentValue() default ""; - Class grandParentType() default Void.class; - } - - @GrandParentAnnotation(grandParentValue = "Parent's GrandParent!") // 覆盖元注解@GrandParentAnnotation的属性 - @Retention(RetentionPolicy.RUNTIME) - @Target({ ElementType.TYPE }) - @interface ParentAnnotation { - String parentValue() default ""; - String grandParentType() default "java.lang.Void"; - } - - @GrandParentAnnotation(grandParentValue = "Child's GrandParent!") // 重复的元注解,靠近根注解的优先级高 - @ParentAnnotation(parentValue = "Child's Parent!") // 覆盖元注解@ParentAnnotation的属性 - @Retention(RetentionPolicy.RUNTIME) - @Target({ ElementType.METHOD, ElementType.TYPE }) - @interface ChildAnnotation { - String childValueAlias() default ""; - @Alias("childValueAlias") - String childValue() default ""; - Class grandParentType() default Void.class; - } - -} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/TypeAnnotationScannerTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/TypeAnnotationScannerTest.java index 0b3f31f81..c9eed2014 100644 --- a/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/TypeAnnotationScannerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/scanner/TypeAnnotationScannerTest.java @@ -36,7 +36,7 @@ public class TypeAnnotationScannerTest { annotations.forEach(a -> Assert.assertEquals(a.annotationType(), AnnotationForScannerTest.class)); // 不查找父类 - scanner = new TypeAnnotationScanner().setIncludeSupperClass(false); + scanner = new TypeAnnotationScanner().setIncludeSuperClass(false); annotations = scanner.getAnnotations(Example.class); Assert.assertEquals(1, annotations.size()); annotations.forEach(a -> Assert.assertEquals(a.annotationType(), AnnotationForScannerTest.class)); @@ -88,7 +88,7 @@ public class TypeAnnotationScannerTest { // 不查找父类 map.clear(); new TypeAnnotationScanner() - .setIncludeSupperClass(false) + .setIncludeSuperClass(false) .scan( (index, annotation) -> map.computeIfAbsent(index, i -> new ArrayList<>()).add(annotation), Example.class, null diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 64e8a5b2b..3215200b3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -73,12 +73,16 @@ public class ListUtilTest { lists = ListUtil.splitAvg(Arrays.asList(1, 2, 3, 4), 4); Assert.assertEquals("[[1], [2], [3], [4]]", lists.toString()); - lists = ListUtil.splitAvg(Arrays.asList(1, 2, 3), 5); - Assert.assertEquals("[[1], [2], [3], [], []]", lists.toString()); lists = ListUtil.splitAvg(Arrays.asList(1, 2, 3), 2); Assert.assertEquals("[[1, 2], [3]]", lists.toString()); } + @Test + public void splitAvgTest2() { + List> lists = ListUtil.splitAvg(Arrays.asList(1, 2, 3), 5); + Assert.assertEquals("[[1], [2], [3], [], []]", lists.toString()); + } + @Test(expected = IllegalArgumentException.class) public void splitAvgNotZero() { // limit不能小于等于0 diff --git a/hutool-core/src/test/java/cn/hutool/core/date/TemporalAccessorUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/TemporalAccessorUtilTest.java index 0a9f97c61..eca415c5b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/TemporalAccessorUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/TemporalAccessorUtilTest.java @@ -31,4 +31,16 @@ public class TemporalAccessorUtilTest { LocalDate.of(2021, 6, 26), "#SSS"); Assert.assertEquals("1624636800000", today2); } + + @Test + public void isInTest(){ + final String sourceStr = "2022-04-19 00:00:00"; + final String startTimeStr = "2022-04-19 00:00:00"; + final String endTimeStr = "2022-04-19 23:59:59"; + final boolean between = TemporalAccessorUtil.isIn( + LocalDateTimeUtil.parse(sourceStr, DatePattern.NORM_DATETIME_FORMATTER), + LocalDateTimeUtil.parse(startTimeStr, DatePattern.NORM_DATETIME_FORMATTER), + LocalDateTimeUtil.parse(endTimeStr, DatePattern.NORM_DATETIME_FORMATTER)); + Assert.assertTrue(between); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/map/LinkedForestMapTest.java b/hutool-core/src/test/java/cn/hutool/core/map/LinkedForestMapTest.java index 08ca22047..40adea923 100644 --- a/hutool-core/src/test/java/cn/hutool/core/map/LinkedForestMapTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/map/LinkedForestMapTest.java @@ -142,6 +142,14 @@ public class LinkedForestMapTest { Assert.assertFalse(c.hasChildren()); } + @Test + public void getNodeValueTest() { + final ForestMap map = new LinkedForestMap<>(false); + map.putNode("a", "aaa"); + Assert.assertEquals("aaa", map.getNodeValue("a")); + Assert.assertNull(map.getNodeValue("b")); + } + @Test public void putAllNodeTest() { final ForestMap> map = new LinkedForestMap<>(false); diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index f0dc8a029..ed624dfab 100755 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -534,4 +534,13 @@ public class ArrayUtilTest { String[] newArr = ArrayUtil.setOrAppend(arr, 0, "Good");// ClassCastException Assert.assertArrayEquals(new String[]{"Good"}, newArr); } + + @Test + public void getAnyTest() { + final String[] a = {"a", "b", "c", "d", "e"}; + final Object o = ArrayUtil.getAny(a, 3, 4); + final String[] resultO = (String[]) o; + final String[] c = {"d", "e"}; + Assert.assertTrue(ArrayUtil.containsAll(c, resultO[0], resultO[1])); + } } diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index dc75c6043..6e7b7ea26 100755 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -18,7 +18,7 @@ - 1.70 + 1.71 @@ -29,7 +29,7 @@ org.bouncycastle - bcprov-jdk15to18 + bcpkix-jdk15to18 ${bouncycastle.version} compile true diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/OpensslKeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/OpensslKeyUtil.java new file mode 100644 index 000000000..6748a79ea --- /dev/null +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/OpensslKeyUtil.java @@ -0,0 +1,187 @@ +package cn.hutool.crypto; + +import cn.hutool.core.io.IORuntimeException; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; + +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.openssl.PEMDecryptorProvider; +import org.bouncycastle.openssl.PEMEncryptedKeyPair; +import org.bouncycastle.openssl.PEMException; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.X509TrustedCertificateBlock; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder; +import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; +import org.bouncycastle.operator.InputDecryptorProvider; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; +import org.bouncycastle.pkcs.PKCSException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.security.Key; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * 基于bcpkix封装的Openssl相关工具,包括密钥转换、Pem密钥文件读取等
+ * 注意此工具需要引入org.bouncycastle:bcpkix-jdk15to18 + * + * @author changhr2013, looly + * @since 5.8.5 + */ +public class OpensslKeyUtil { + + private static final JcaPEMKeyConverter pemKeyConverter = new JcaPEMKeyConverter().setProvider(GlobalBouncyCastleProvider.INSTANCE.getProvider()); + + /** + * 转换{@link PrivateKeyInfo}为{@link PrivateKey} + * + * @param privateKeyInfo {@link PrivateKeyInfo} + * @return {@link PrivateKey} + * @throws CryptoException {@link PEMException}包装 + */ + public static PrivateKey getPrivateKey(final PrivateKeyInfo privateKeyInfo) throws CryptoException { + try { + return pemKeyConverter.getPrivateKey(privateKeyInfo); + } catch (final PEMException e) { + throw new CryptoException(e); + } + } + + /** + * 转换{@link SubjectPublicKeyInfo}为{@link PublicKey} + * + * @param publicKeyInfo {@link SubjectPublicKeyInfo} + * @return {@link PublicKey} + * @throws CryptoException {@link PEMException}包装 + */ + public static PublicKey getPublicKey(final SubjectPublicKeyInfo publicKeyInfo) throws CryptoException { + try { + return pemKeyConverter.getPublicKey(publicKeyInfo); + } catch (final PEMException e) { + throw new CryptoException(e); + } + } + + /** + * 转换{@link PEMKeyPair}为{@link KeyPair} + * + * @param keyPair {@link PEMKeyPair} + * @return {@link KeyPair} + * @throws CryptoException {@link PEMException}包装 + */ + public static KeyPair getKeyPair(final PEMKeyPair keyPair) throws CryptoException { + try { + return pemKeyConverter.getKeyPair(keyPair); + } catch (final PEMException e) { + throw new CryptoException(e); + } + } + + /** + * 从pem文件中读取公钥或私钥
+ * 根据类型返回 {@link PublicKey} 或者 {@link PrivateKey} + * + * @param keyStream pem 流 + * @param password 私钥密码 + * @return {@link Key},null 表示无法识别的密钥类型 + * @since 5.8.5 + */ + public static Key readPemKey(final InputStream keyStream, final char[] password) { + try (final PEMParser pemParser = new PEMParser(new InputStreamReader(keyStream))) { + return readPemKeyFromKeyObject(pemParser.readObject(), password); + } catch (final IOException e) { + throw new CryptoException(e); + } + } + + /** + * 解密{@link PKCS8EncryptedPrivateKeyInfo}为{@link PrivateKeyInfo} + * + * @param pkcs8Info {@link PKCS8EncryptedPrivateKeyInfo} + * @param password 密码 + * @return {@link PrivateKeyInfo} + * @throws CryptoException OperatorCreationException和PKCSException包装 + */ + public static PrivateKeyInfo decrypt(final PKCS8EncryptedPrivateKeyInfo pkcs8Info, final char[] password) throws CryptoException { + final InputDecryptorProvider decryptProvider; + try { + decryptProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider(GlobalBouncyCastleProvider.INSTANCE.getProvider()).build(password); + return pkcs8Info.decryptPrivateKeyInfo(decryptProvider); + } catch (final OperatorCreationException | PKCSException e) { + throw new CryptoException(e); + } + } + + /** + * 解密{@link PEMEncryptedKeyPair}为{@link PEMKeyPair} + * + * @param pemEncryptedKeyPair {@link PKCS8EncryptedPrivateKeyInfo} + * @param password 密码 + * @return {@link PEMKeyPair} + * @throws IORuntimeException IOException包装 + */ + public static PEMKeyPair decrypt(final PEMEncryptedKeyPair pemEncryptedKeyPair, final char[] password) throws IORuntimeException { + final PEMDecryptorProvider decryptProvider; + try { + decryptProvider = new JcePEMDecryptorProviderBuilder().setProvider(GlobalBouncyCastleProvider.INSTANCE.getProvider()).build(password); + return pemEncryptedKeyPair.decryptKeyPair(decryptProvider); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 读取Pem文件中的密钥,密钥支持包括:
+ *

    + *
  • {@link PrivateKeyInfo}
  • + *
  • {@link PEMKeyPair},默认读取私钥
  • + *
  • {@link PKCS8EncryptedPrivateKeyInfo}
  • + *
  • {@link PEMEncryptedKeyPair},默认读取私钥
  • + *
  • {@link X509CertificateHolder}
  • + *
  • {@link X509TrustedCertificateBlock}
  • + *
  • {@link PKCS10CertificationRequest}
  • + *
+ * + * @param keyObject 密钥内容对象 + * @param password 密码(部分加密的pem使用) + * @return {@link Key} + * @throws CryptoException 读取异常或不支持的类型 + */ + private static Key readPemKeyFromKeyObject(final Object keyObject, final char[] password) throws CryptoException { + if (keyObject instanceof PrivateKeyInfo) { + // PrivateKeyInfo + return getPrivateKey((PrivateKeyInfo) keyObject); + } else if (keyObject instanceof PEMKeyPair) { + // PemKeyPair + return getKeyPair((PEMKeyPair) keyObject).getPrivate(); + } else if (keyObject instanceof PKCS8EncryptedPrivateKeyInfo) { + // Encrypted PrivateKeyInfo + return getPrivateKey(decrypt((PKCS8EncryptedPrivateKeyInfo) keyObject, password)); + } else if (keyObject instanceof PEMEncryptedKeyPair) { + // Encrypted PemKeyPair + return getPrivateKey(decrypt((PEMEncryptedKeyPair) keyObject, password).getPrivateKeyInfo()); + } else if (keyObject instanceof SubjectPublicKeyInfo) { + // SubjectPublicKeyInfo + return getPublicKey((SubjectPublicKeyInfo) keyObject); + } else if (keyObject instanceof X509CertificateHolder) { + // X509 Certificate + return getPublicKey(((X509CertificateHolder) keyObject).getSubjectPublicKeyInfo()); + } else if (keyObject instanceof X509TrustedCertificateBlock) { + // X509 Trusted Certificate + return getPublicKey(((X509TrustedCertificateBlock) keyObject).getCertificateHolder().getSubjectPublicKeyInfo()); + } else if (keyObject instanceof PKCS10CertificationRequest) { + // PKCS#10 CSR + return getPublicKey(((PKCS10CertificationRequest) keyObject).getSubjectPublicKeyInfo()); + } else { + // 表示无法识别的密钥类型 + throw new CryptoException("Unsupported key object type: {}", keyObject.getClass()); + } + } +} diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/PemUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/PemUtil.java index c7ca4205b..d9b5edfaa 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/PemUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/PemUtil.java @@ -3,6 +3,7 @@ package cn.hutool.crypto; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; + import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemObjectGenerator; import org.bouncycastle.util.io.pem.PemReader; @@ -32,7 +33,7 @@ import java.security.PublicKey; public class PemUtil { /** - * 读取PEM格式的私钥 + * 读取PEM格式的私钥,支持PKCS#8和PKCS#1的ECC格式 * * @param pemStream pem流 * @return {@link PrivateKey} @@ -67,7 +68,13 @@ public class PemUtil { if (StrUtil.isNotBlank(type)) { //private if (type.endsWith("EC PRIVATE KEY")) { - return KeyUtil.generatePrivateKey("EC", object.getContent()); + try { + // 尝试PKCS#8 + return KeyUtil.generatePrivateKey("EC", object.getContent()); + } catch (final Exception e) { + // 尝试PKCS#1 + return KeyUtil.generatePrivateKey("EC", ECKeyUtil.createOpenSSHPrivateKeySpec(object.getContent())); + } } if (type.endsWith("PRIVATE KEY")) { return KeyUtil.generateRSAPrivateKey(object.getContent()); @@ -75,7 +82,13 @@ public class PemUtil { // public if (type.endsWith("EC PUBLIC KEY")) { - return KeyUtil.generatePublicKey("EC", object.getContent()); + try { + // 尝试DER + return KeyUtil.generatePublicKey("EC", object.getContent()); + } catch (Exception e) { + // 尝试PKCS#1 + return KeyUtil.generatePublicKey("EC", ECKeyUtil.createOpenSSHPublicKeySpec(object.getContent())); + } } else if (type.endsWith("PUBLIC KEY")) { return KeyUtil.generateRSAPublicKey(object.getContent()); } else if (type.endsWith("CERTIFICATE")) { @@ -95,7 +108,7 @@ public class PemUtil { * @since 5.1.6 */ public static byte[] readPem(InputStream keyStream) { - PemObject pemObject = readPemObject(keyStream); + final PemObject pemObject = readPemObject(keyStream); if (null != pemObject) { return pemObject.getContent(); } @@ -137,18 +150,17 @@ public class PemUtil { * * @param keyStream 私钥pem流 * @return {@link PrivateKey} + * @deprecated 请使用 {@link #readPemPrivateKey(InputStream)} */ + @Deprecated public static PrivateKey readSm2PemPrivateKey(InputStream keyStream) { - try{ - return KeyUtil.generatePrivateKey("sm2", ECKeyUtil.createOpenSSHPrivateKeySpec(readPem(keyStream))); - } finally { - IoUtil.close(keyStream); - } + return readPemPrivateKey(keyStream); } /** * 将私钥或公钥转换为PEM格式的字符串 - * @param type 密钥类型(私钥、公钥、证书) + * + * @param type 密钥类型(私钥、公钥、证书) * @param content 密钥内容 * @return PEM内容 * @since 5.5.9 @@ -175,7 +187,7 @@ public class PemUtil { * 写出pem密钥(私钥、公钥、证书) * * @param type 密钥类型(私钥、公钥、证书) - * @param content 密钥内容,需为PKCS#1格式 + * @param content 密钥内容,需为PKCS#1或PKCS#8格式 * @param writer pemWriter * @since 5.5.9 */ diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/BCUtilTest.java similarity index 96% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/BCUtilTest.java index 8a922cdef..68971d361 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/BCUtilTest.java @@ -1,4 +1,4 @@ -package cn.hutool.crypto.test; +package cn.hutool.crypto; import cn.hutool.crypto.BCUtil; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/KeyUtilTest.java similarity index 98% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/KeyUtilTest.java index 576a85cf8..2ef57eea2 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/KeyUtilTest.java @@ -1,4 +1,4 @@ -package cn.hutool.crypto.test; +package cn.hutool.crypto; import cn.hutool.crypto.CryptoException; import cn.hutool.crypto.GlobalBouncyCastleProvider; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/OpensslKeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/OpensslKeyUtilTest.java new file mode 100644 index 000000000..a6348ce6b --- /dev/null +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/OpensslKeyUtilTest.java @@ -0,0 +1,55 @@ +package cn.hutool.crypto; + +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.asymmetric.SM2; +import org.junit.Assert; +import org.junit.Test; + +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Arrays; +import java.util.List; + +public class OpensslKeyUtilTest { + @Test + public void verifyPemUtilReadKey() { + // 公钥 + // PKCS#10 文件读取公钥 + final PublicKey csrPublicKey = (PublicKey) OpensslKeyUtil.readPemKey(ResourceUtil.getStream("test_ec_certificate_request.csr"), null); + + // 证书读取公钥 + final PublicKey certPublicKey = (PublicKey) OpensslKeyUtil.readPemKey(ResourceUtil.getStream("test_ec_certificate.cer"), null); + + // PEM 公钥 + final PublicKey plainPublicKey = (PublicKey) OpensslKeyUtil.readPemKey(ResourceUtil.getStream("test_ec_public_key.pem"), null); + + // 私钥 + // 加密的 PEM 私钥 + final PrivateKey encPrivateKey = (PrivateKey) OpensslKeyUtil.readPemKey(ResourceUtil.getStream("test_ec_encrypted_private_key.key"), "123456".toCharArray()); + + // PKCS#8 私钥 + final PrivateKey pkcs8PrivateKey = (PrivateKey) OpensslKeyUtil.readPemKey(ResourceUtil.getStream("test_ec_pkcs8_private_key.key"), null); + + // SEC 1 私钥 + final PrivateKey sec1PrivateKey = (PrivateKey) OpensslKeyUtil.readPemKey(ResourceUtil.getStream("test_ec_sec1_private_key.pem"), null); + + // 组装还原后的公钥和私钥列表 + final List publicKeyList = Arrays.asList(csrPublicKey, certPublicKey, plainPublicKey); + final List privateKeyList = Arrays.asList(encPrivateKey, pkcs8PrivateKey, sec1PrivateKey); + + // 做笛卡尔积循环验证 + for (final PrivateKey privateKeyItem : privateKeyList) { + for (final PublicKey publicKeyItem : publicKeyList) { + // 校验公私钥 + final SM2 genSm2 = new SM2(privateKeyItem, publicKeyItem); + genSm2.usePlainEncoding(); + + final String content = "我是Hanley."; + final byte[] sign = genSm2.sign(StrUtil.utf8Bytes(content)); + final boolean verify = genSm2.verify(StrUtil.utf8Bytes(content), sign); + Assert.assertTrue(verify); + } + } + } +} diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/PemUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/PemUtilTest.java new file mode 100644 index 000000000..3585c20ea --- /dev/null +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/PemUtilTest.java @@ -0,0 +1,80 @@ +package cn.hutool.crypto; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.asymmetric.KeyType; +import cn.hutool.crypto.asymmetric.RSA; +import cn.hutool.crypto.asymmetric.SM2; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; +import java.security.PrivateKey; +import java.security.PublicKey; + +public class PemUtilTest { + + @Test + public void readPrivateKeyTest() { + final PrivateKey privateKey = PemUtil.readPemPrivateKey(ResourceUtil.getStream("test_private_key.pem")); + Assert.assertNotNull(privateKey); + } + + @Test + public void readPublicKeyTest() { + final PublicKey publicKey = PemUtil.readPemPublicKey(ResourceUtil.getStream("test_public_key.csr")); + Assert.assertNotNull(publicKey); + } + + @Test + public void readPemKeyTest() { + final PublicKey publicKey = (PublicKey) PemUtil.readPemKey(ResourceUtil.getStream("test_public_key.csr")); + Assert.assertNotNull(publicKey); + } + + @Test + public void validateKey() { + final PrivateKey privateKey = PemUtil.readPemPrivateKey(ResourceUtil.getStream("test_private_key.pem")); + final PublicKey publicKey = PemUtil.readPemPublicKey(ResourceUtil.getStream("test_public_key.csr")); + + final RSA rsa = new RSA(privateKey, publicKey); + final String str = "你好,Hutool";//测试字符串 + + final String encryptStr = rsa.encryptBase64(str, KeyType.PublicKey); + final String decryptStr = rsa.decryptStr(encryptStr, KeyType.PrivateKey); + Assert.assertEquals(str, decryptStr); + } + + @Test + public void readECPrivateKeyTest() { + final PrivateKey privateKey = PemUtil.readPemPrivateKey(ResourceUtil.getStream("test_ec_sec1_private_key.pem")); + final SM2 sm2 = new SM2(privateKey, null); + sm2.usePlainEncoding(); + + //需要签名的明文,得到明文对应的字节数组 + final byte[] dataBytes = "我是一段测试aaaa".getBytes(StandardCharsets.UTF_8); + + final byte[] sign = sm2.sign(dataBytes, null); + // 64位签名 + Assert.assertEquals(64, sign.length); + } + + @Test + @Ignore + public void readECPrivateKeyTest2() { + // https://gitee.com/dromara/hutool/issues/I37Z75 + final byte[] d = PemUtil.readPem(FileUtil.getInputStream("d:/test/keys/priv.key")); + final byte[] publicKey = PemUtil.readPem(FileUtil.getInputStream("d:/test/keys/pub.key")); + + final SM2 sm2 = new SM2(d, publicKey); + sm2.usePlainEncoding(); + + final String content = "我是Hanley."; + final byte[] sign = sm2.sign(StrUtil.utf8Bytes(content)); + final boolean verify = sm2.verify(StrUtil.utf8Bytes(content), sign); + Assert.assertTrue(verify); + } + +} diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SmTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/SmTest.java similarity index 98% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/SmTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/SmTest.java index 5888cdf08..fa6ea25b2 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SmTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/SmTest.java @@ -1,4 +1,4 @@ -package cn.hutool.crypto.test; +package cn.hutool.crypto; import cn.hutool.core.util.CharsetUtil; import cn.hutool.crypto.KeyUtil; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/ECIESTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/asymmetric/ECIESTest.java similarity index 85% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/ECIESTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/asymmetric/ECIESTest.java index 6e91f8a89..3ac2b222a 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/ECIESTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/asymmetric/ECIESTest.java @@ -1,9 +1,6 @@ -package cn.hutool.crypto.test.asymmetric; +package cn.hutool.crypto.asymmetric; import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.asymmetric.AsymmetricCrypto; -import cn.hutool.crypto.asymmetric.ECIES; -import cn.hutool.crypto.asymmetric.KeyType; import org.junit.Assert; import org.junit.Test; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/RSATest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/asymmetric/RSATest.java old mode 100755 new mode 100644 similarity index 96% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/RSATest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/asymmetric/RSATest.java index a8351c341..6f962c6c5 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/RSATest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/asymmetric/RSATest.java @@ -1,7 +1,6 @@ -package cn.hutool.crypto.test.asymmetric; +package cn.hutool.crypto.asymmetric; import cn.hutool.core.codec.Base64; -import cn.hutool.core.lang.Console; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.HexUtil; @@ -9,11 +8,6 @@ import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.KeyUtil; import cn.hutool.crypto.SecureUtil; -import cn.hutool.crypto.asymmetric.AsymmetricAlgorithm; -import cn.hutool.crypto.asymmetric.KeyType; -import cn.hutool.crypto.asymmetric.RSA; -import cn.hutool.crypto.asymmetric.Sign; -import cn.hutool.crypto.asymmetric.SignAlgorithm; import org.junit.Assert; import org.junit.Test; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SM2Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/asymmetric/SM2Test.java old mode 100755 new mode 100644 similarity index 98% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SM2Test.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/asymmetric/SM2Test.java index 3e852793d..60eea17d5 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SM2Test.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/asymmetric/SM2Test.java @@ -1,4 +1,4 @@ -package cn.hutool.crypto.test.asymmetric; +package cn.hutool.crypto.asymmetric; import cn.hutool.core.codec.Base64; import cn.hutool.core.util.CharsetUtil; @@ -8,8 +8,6 @@ import cn.hutool.crypto.ECKeyUtil; import cn.hutool.crypto.KeyUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.SmUtil; -import cn.hutool.crypto.asymmetric.KeyType; -import cn.hutool.crypto.asymmetric.SM2; import org.bouncycastle.crypto.engines.SM2Engine; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.jcajce.spec.OpenSSHPrivateKeySpec; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SignTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/asymmetric/SignTest.java similarity index 97% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SignTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/asymmetric/SignTest.java index 0bc8f268f..003d69604 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SignTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/asymmetric/SignTest.java @@ -1,10 +1,8 @@ -package cn.hutool.crypto.test.asymmetric; +package cn.hutool.crypto.asymmetric; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SecureUtil; -import cn.hutool.crypto.asymmetric.Sign; -import cn.hutool.crypto.asymmetric.SignAlgorithm; import org.junit.Assert; import org.junit.Test; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/BCryptTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/digest/BCryptTest.java similarity index 75% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/BCryptTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/digest/BCryptTest.java index 3450355ab..7ab03177b 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/BCryptTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/digest/BCryptTest.java @@ -1,6 +1,5 @@ -package cn.hutool.crypto.test.digest; +package cn.hutool.crypto.digest; -import cn.hutool.crypto.digest.BCrypt; import org.junit.Assert; import org.junit.Test; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/CBCBlockCipherMacEngineTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/digest/CBCBlockCipherMacEngineTest.java similarity index 97% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/CBCBlockCipherMacEngineTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/digest/CBCBlockCipherMacEngineTest.java index 8e4bdb6e3..85fbbbd55 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/CBCBlockCipherMacEngineTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/digest/CBCBlockCipherMacEngineTest.java @@ -1,4 +1,4 @@ -package cn.hutool.crypto.test.digest; +package cn.hutool.crypto.digest; import cn.hutool.crypto.KeyUtil; import cn.hutool.crypto.digest.mac.Mac; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/DigestTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/digest/DigestTest.java similarity index 90% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/DigestTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/digest/DigestTest.java index d555b4ec4..80acd8c4f 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/DigestTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/digest/DigestTest.java @@ -1,12 +1,9 @@ -package cn.hutool.crypto.test.digest; +package cn.hutool.crypto.digest; import org.junit.Assert; import org.junit.Test; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.CharsetUtil; -import cn.hutool.crypto.digest.DigestAlgorithm; -import cn.hutool.crypto.digest.DigestUtil; -import cn.hutool.crypto.digest.Digester; /** * 摘要算法单元测试 @@ -14,40 +11,40 @@ import cn.hutool.crypto.digest.Digester; * */ public class DigestTest { - + @Test public void digesterTest(){ String testStr = "test中文"; - + Digester md5 = new Digester(DigestAlgorithm.MD5); String digestHex = md5.digestHex(testStr); Assert.assertEquals("5393554e94bf0eb6436f240a4fd71282", digestHex); } - + @Test public void md5Test(){ String testStr = "test中文"; - + String md5Hex1 = DigestUtil.md5Hex(testStr); Assert.assertEquals("5393554e94bf0eb6436f240a4fd71282", md5Hex1); - + String md5Hex2 = DigestUtil.md5Hex(IoUtil.toStream(testStr, CharsetUtil.CHARSET_UTF_8)); Assert.assertEquals("5393554e94bf0eb6436f240a4fd71282", md5Hex2); } - + @Test public void md5WithSaltTest(){ String testStr = "test中文"; - + Digester md5 = new Digester(DigestAlgorithm.MD5); - + //加盐 md5.setSalt("saltTest".getBytes()); String md5Hex1 = md5.digestHex(testStr); Assert.assertEquals("762f7335200299dfa09bebbb601a5bc6", md5Hex1); String md5Hex2 = md5.digestHex(IoUtil.toUtf8Stream(testStr)); Assert.assertEquals("762f7335200299dfa09bebbb601a5bc6", md5Hex2); - + //重复2次 md5.setDigestCount(2); String md5Hex3 = md5.digestHex(testStr); @@ -55,18 +52,18 @@ public class DigestTest { String md5Hex4 = md5.digestHex(IoUtil.toUtf8Stream(testStr)); Assert.assertEquals("2b0616296f6755d25efc07f90afe9684", md5Hex4); } - + @Test public void sha1Test(){ String testStr = "test中文"; - + String sha1Hex1 = DigestUtil.sha1Hex(testStr); Assert.assertEquals("ecabf586cef0d3b11c56549433ad50b81110a836", sha1Hex1); - + String sha1Hex2 = DigestUtil.sha1Hex(IoUtil.toStream(testStr, CharsetUtil.CHARSET_UTF_8)); Assert.assertEquals("ecabf586cef0d3b11c56549433ad50b81110a836", sha1Hex2); } - + @Test public void hash256Test() { String testStr = "Test中文"; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/HmacTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/digest/HmacTest.java similarity index 95% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/HmacTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/digest/HmacTest.java index ccddaa43c..d7fb4d939 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/HmacTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/digest/HmacTest.java @@ -1,11 +1,9 @@ -package cn.hutool.crypto.test.digest; +package cn.hutool.crypto.digest; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.crypto.KeyUtil; import cn.hutool.crypto.SecureUtil; -import cn.hutool.crypto.digest.HMac; -import cn.hutool.crypto.digest.HmacAlgorithm; import cn.hutool.crypto.symmetric.ZUC; import org.junit.Assert; import org.junit.Test; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/Md5Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/digest/Md5Test.java similarity index 79% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/Md5Test.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/digest/Md5Test.java index 49cba7751..2c7cdb407 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/Md5Test.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/digest/Md5Test.java @@ -1,13 +1,11 @@ -package cn.hutool.crypto.test.digest; +package cn.hutool.crypto.digest; import org.junit.Assert; import org.junit.Test; -import cn.hutool.crypto.digest.MD5; - /** * MD5 单元测试 - * + * * @author Looly * */ diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/OTPTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/digest/OTPTest.java similarity index 98% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/OTPTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/digest/OTPTest.java index b073d9c4b..c63aea4c7 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/OTPTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/digest/OTPTest.java @@ -1,7 +1,6 @@ -package cn.hutool.crypto.test.digest; +package cn.hutool.crypto.digest; import cn.hutool.core.codec.Base32; -import cn.hutool.crypto.digest.HmacAlgorithm; import cn.hutool.crypto.digest.otp.HOTP; import cn.hutool.crypto.digest.otp.TOTP; import org.junit.Assert; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/AESTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/AESTest.java old mode 100755 new mode 100644 similarity index 98% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/AESTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/AESTest.java index b860854f2..3dd30e4b2 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/AESTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/AESTest.java @@ -1,4 +1,4 @@ -package cn.hutool.crypto.test.symmetric; +package cn.hutool.crypto.symmetric; import cn.hutool.core.codec.Base64; import cn.hutool.core.util.HexUtil; @@ -6,7 +6,6 @@ import cn.hutool.core.util.RandomUtil; import cn.hutool.crypto.KeyUtil; import cn.hutool.crypto.Mode; import cn.hutool.crypto.Padding; -import cn.hutool.crypto.symmetric.AES; import org.junit.Assert; import org.junit.Test; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/ChaCha20Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/ChaCha20Test.java similarity index 90% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/ChaCha20Test.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/ChaCha20Test.java index a30e28f0b..888720ca1 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/ChaCha20Test.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/ChaCha20Test.java @@ -1,8 +1,7 @@ -package cn.hutool.crypto.test.symmetric; +package cn.hutool.crypto.symmetric; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.RandomUtil; -import cn.hutool.crypto.symmetric.ChaCha20; import org.junit.Assert; import org.junit.Test; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/DesTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/DesTest.java similarity index 92% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/DesTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/DesTest.java index eb0130c4f..acd584cea 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/DesTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/DesTest.java @@ -1,10 +1,9 @@ -package cn.hutool.crypto.test.symmetric; +package cn.hutool.crypto.symmetric; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.Mode; import cn.hutool.crypto.Padding; import cn.hutool.crypto.SecureUtil; -import cn.hutool.crypto.symmetric.DES; import org.junit.Assert; import org.junit.Test; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/PBKDF2Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/PBKDF2Test.java similarity index 88% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/PBKDF2Test.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/PBKDF2Test.java index 780781ee2..7e6c2138a 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/PBKDF2Test.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/PBKDF2Test.java @@ -1,4 +1,4 @@ -package cn.hutool.crypto.test.symmetric; +package cn.hutool.crypto.symmetric; import cn.hutool.core.util.RandomUtil; import cn.hutool.crypto.SecureUtil; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/RC4Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/RC4Test.java similarity index 96% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/RC4Test.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/RC4Test.java index d630af7f2..7d4cec54c 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/RC4Test.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/RC4Test.java @@ -1,13 +1,11 @@ -package cn.hutool.crypto.test.symmetric; +package cn.hutool.crypto.symmetric; import cn.hutool.core.util.CharsetUtil; import org.junit.Assert; import org.junit.Test; -import cn.hutool.crypto.symmetric.RC4; - public class RC4Test { - + @Test public void testCryptMessage() { String key = "This is pretty long key"; @@ -16,7 +14,7 @@ public class RC4Test { byte[] crypt = rc4.encrypt(message); String msg = rc4.decrypt(crypt); Assert.assertEquals(message, msg); - + String message2 = "Hello, World, this is megssage 2"; byte[] crypt2 = rc4.encrypt(message2); String msg2 = rc4.decrypt(crypt2); @@ -31,7 +29,7 @@ public class RC4Test { byte[] crypt = rc4.encrypt(message); String msg = rc4.decrypt(crypt); Assert.assertEquals(message, msg); - + String message2 = "这是第二个中文消息!"; byte[] crypt2 = rc4.encrypt(message2); String msg2 = rc4.decrypt(crypt2); diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/Sm4StreamTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/Sm4StreamTest.java similarity index 93% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/Sm4StreamTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/Sm4StreamTest.java index 87ec2675c..e31a93841 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/Sm4StreamTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/Sm4StreamTest.java @@ -1,6 +1,5 @@ -package cn.hutool.crypto.test.symmetric; +package cn.hutool.crypto.symmetric; -import cn.hutool.crypto.symmetric.SM4; import org.junit.Ignore; import org.junit.Test; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/SymmetricTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/SymmetricTest.java similarity index 96% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/SymmetricTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/SymmetricTest.java index a1f4e04e6..eeee72a2f 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/SymmetricTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/SymmetricTest.java @@ -1,4 +1,4 @@ -package cn.hutool.crypto.test.symmetric; +package cn.hutool.crypto.symmetric; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.CharsetUtil; @@ -9,12 +9,6 @@ import cn.hutool.crypto.KeyUtil; import cn.hutool.crypto.Mode; import cn.hutool.crypto.Padding; import cn.hutool.crypto.SecureUtil; -import cn.hutool.crypto.symmetric.AES; -import cn.hutool.crypto.symmetric.DES; -import cn.hutool.crypto.symmetric.DESede; -import cn.hutool.crypto.symmetric.SymmetricAlgorithm; -import cn.hutool.crypto.symmetric.SymmetricCrypto; -import cn.hutool.crypto.symmetric.Vigenere; import org.junit.Assert; import org.junit.Test; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/TEATest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/TEATest.java similarity index 89% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/TEATest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/TEATest.java index 53916926a..7044db722 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/TEATest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/TEATest.java @@ -1,7 +1,5 @@ -package cn.hutool.crypto.test.symmetric; +package cn.hutool.crypto.symmetric; -import cn.hutool.crypto.symmetric.SymmetricCrypto; -import cn.hutool.crypto.symmetric.XXTEA; import org.junit.Assert; import org.junit.Test; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/ZucTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/ZucTest.java similarity index 92% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/ZucTest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/ZucTest.java index 81524d95c..1c384ffff 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/ZucTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/ZucTest.java @@ -1,8 +1,7 @@ -package cn.hutool.crypto.test.symmetric; +package cn.hutool.crypto.symmetric; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.RandomUtil; -import cn.hutool.crypto.symmetric.ZUC; import org.junit.Assert; import org.junit.Test; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/fpe/FPETest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/fpe/FPETest.java similarity index 94% rename from hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/fpe/FPETest.java rename to hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/fpe/FPETest.java index dd41cbf69..25c667886 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/fpe/FPETest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/symmetric/fpe/FPETest.java @@ -1,7 +1,6 @@ -package cn.hutool.crypto.test.symmetric.fpe; +package cn.hutool.crypto.symmetric.fpe; import cn.hutool.core.util.RandomUtil; -import cn.hutool.crypto.symmetric.fpe.FPE; import org.bouncycastle.crypto.util.BasicAlphabetMapper; import org.junit.Assert; import org.junit.Test; diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/PemUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/PemUtilTest.java deleted file mode 100644 index 0daea2e31..000000000 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/PemUtilTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package cn.hutool.crypto.test; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.resource.ResourceUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.PemUtil; -import cn.hutool.crypto.asymmetric.KeyType; -import cn.hutool.crypto.asymmetric.RSA; -import cn.hutool.crypto.asymmetric.SM2; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - -import java.nio.charset.StandardCharsets; -import java.security.PrivateKey; -import java.security.PublicKey; - -public class PemUtilTest { - - @Test - public void readPrivateKeyTest() { - PrivateKey privateKey = PemUtil.readPemPrivateKey(ResourceUtil.getStream("test_private_key.pem")); - Assert.assertNotNull(privateKey); - } - - @Test - public void readPublicKeyTest() { - PublicKey publicKey = PemUtil.readPemPublicKey(ResourceUtil.getStream("test_public_key.csr")); - Assert.assertNotNull(publicKey); - } - - @Test - public void readPemKeyTest() { - PublicKey publicKey = (PublicKey) PemUtil.readPemKey(ResourceUtil.getStream("test_public_key.csr")); - Assert.assertNotNull(publicKey); - } - - @Test - public void validateKey() { - PrivateKey privateKey = PemUtil.readPemPrivateKey(ResourceUtil.getStream("test_private_key.pem")); - PublicKey publicKey = PemUtil.readPemPublicKey(ResourceUtil.getStream("test_public_key.csr")); - - RSA rsa = new RSA(privateKey, publicKey); - String str = "你好,Hutool";//测试字符串 - - String encryptStr = rsa.encryptBase64(str, KeyType.PublicKey); - String decryptStr = rsa.decryptStr(encryptStr, KeyType.PrivateKey); - Assert.assertEquals(str, decryptStr); - } - - @Test - public void readECPrivateKeyTest() { - PrivateKey privateKey = PemUtil.readSm2PemPrivateKey(ResourceUtil.getStream("test_ec_private_key.pem")); - SM2 sm2 = new SM2(privateKey, null); - sm2.usePlainEncoding(); - - //需要签名的明文,得到明文对应的字节数组 - byte[] dataBytes = "我是一段测试aaaa".getBytes(StandardCharsets.UTF_8); - - byte[] sign = sm2.sign(dataBytes, null); - // 64位签名 - Assert.assertEquals(64, sign.length); - } - - @Test - @Ignore - public void readECPrivateKeyTest2() { - // https://gitee.com/dromara/hutool/issues/I37Z75 - byte[] d = PemUtil.readPem(FileUtil.getInputStream("d:/test/keys/priv.key")); - byte[] publicKey = PemUtil.readPem(FileUtil.getInputStream("d:/test/keys/pub.key")); - - SM2 sm2 = new SM2(d, publicKey); - sm2.usePlainEncoding(); - - String content = "我是Hanley."; - byte[] sign = sm2.sign(StrUtil.utf8Bytes(content)); - boolean verify = sm2.verify(StrUtil.utf8Bytes(content), sign); - Assert.assertTrue(verify); - } -} diff --git a/hutool-crypto/src/test/resources/test_ec_certificate.cer b/hutool-crypto/src/test/resources/test_ec_certificate.cer new file mode 100644 index 000000000..fcb41fa7a --- /dev/null +++ b/hutool-crypto/src/test/resources/test_ec_certificate.cer @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBozCCAUegAwIBAgIIJQJK8f5oQVQwDAYIKoEcz1UBg3UFADAjMQswCQYDVQQG +EwJDTjEUMBIGA1UEAwwLSHV0b29sIFRlc3QwHhcNMjIwNzE3MDcyMzU4WhcNMjMw +NzE3MDcyMzU4WjAjMQswCQYDVQQGEwJDTjEUMBIGA1UEAwwLSHV0b29sIFRlc3Qw +WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASXXjbO+6vY7vdD5aXoi2EMHUq0itI8 +kG6FN3cgLBFFoelyy3JxX94h7RpH4ylpNUXeRNuzv1VcPa06nsN1OjTWo2MwYTAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUFHvMM9CZ +VUxWKt1+CHI5uYLpLgQwHwYDVR0jBBgwFoAUFHvMM9CZVUxWKt1+CHI5uYLpLgQw +DAYIKoEcz1UBg3UFAANIADBFAiA5p0Gh7mvuuHMVG2SmbGj5HQpWcpwCaUF90BQ9 +/QEYZgIhAIXmeD2bDlOCPc3vxS4aNGxSd1wq/MT9bBJhKyiXWjx5 +-----END CERTIFICATE----- diff --git a/hutool-crypto/src/test/resources/test_ec_certificate_request.csr b/hutool-crypto/src/test/resources/test_ec_certificate_request.csr new file mode 100644 index 000000000..458056676 --- /dev/null +++ b/hutool-crypto/src/test/resources/test_ec_certificate_request.csr @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBODCB3wIBADBWMQswCQYDVQQGEwJDTjEQMA4GA1UECBMHQmVpamluZzEQMA4G +A1UEBxMHQmVpamluZzEPMA0GA1UECxMGSHV0b29sMRIwEAYDVQQDEwlodXRvb2wu +Y24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASXXjbO+6vY7vdD5aXoi2EMHUq0 +itI8kG6FN3cgLBFFoelyy3JxX94h7RpH4ylpNUXeRNuzv1VcPa06nsN1OjTWoCcw +JQYJKoZIhvcNAQkOMRgwFjAUBgNVHREEDTALgglodXRvb2wuY24wCgYIKoZIzj0E +AwIDSAAwRQIhAIH0w1XbGnRfbM1flsqRHzfur+pEZ3wiqpChxAI39lpIAiAsNAwn +o7PsXpTXLhq1ZgqurIjFeFuvY1hszUODmO5ySQ== +-----END CERTIFICATE REQUEST----- \ No newline at end of file diff --git a/hutool-crypto/src/test/resources/test_ec_encrypted_private_key.key b/hutool-crypto/src/test/resources/test_ec_encrypted_private_key.key new file mode 100644 index 000000000..dd3b2351f --- /dev/null +++ b/hutool-crypto/src/test/resources/test_ec_encrypted_private_key.key @@ -0,0 +1,8 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,04b396b78c1f1172857a8b76682b63ef + +NoaLaSy87gLE6C3yCi7JwiB86NSYipZmMVlLIcHaBL2ECRUcGDmXEZu6OqFyrbDc +XWXraEl3OieYduiVmuJ0GQ8oeWd5DNgHLBYTPnfgjBowbluAO9/R9AUh4R8Fz918 +/zsMZJckjSv3Gs6NWZW02v9OvhTDSJBNwu/M2WTWH10= +-----END EC PRIVATE KEY----- diff --git a/hutool-crypto/src/test/resources/test_ec_pkcs8_private_key.key b/hutool-crypto/src/test/resources/test_ec_pkcs8_private_key.key new file mode 100644 index 000000000..e4b404e93 --- /dev/null +++ b/hutool-crypto/src/test/resources/test_ec_pkcs8_private_key.key @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgEZmc6NdYu8UzpzhX +1bcRzfWBlcGgnPtqfVsRzXfXZ/6gCgYIKoZIzj0DAQehRANCAASXXjbO+6vY7vdD +5aXoi2EMHUq0itI8kG6FN3cgLBFFoelyy3JxX94h7RpH4ylpNUXeRNuzv1VcPa06 +nsN1OjTW +-----END PRIVATE KEY----- diff --git a/hutool-crypto/src/test/resources/test_ec_private_key.pem b/hutool-crypto/src/test/resources/test_ec_private_key.pem deleted file mode 100644 index 8ae4ed315..000000000 --- a/hutool-crypto/src/test/resources/test_ec_private_key.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIKB89IhhSy9WrtQS7TWO5Yqyv5a3DnogWYUhb3TbzjnWoAoGCCqBHM9V -AYItoUQDQgAE3LRuqCM697gL3jPhw98eGfTDcJsuJr6H1nE4VkgdtBdX3So2lC6m -UGEnWeRZuh8HnzCRobcu02Bgv7CVR5Iigg== ------END EC PRIVATE KEY----- diff --git a/hutool-crypto/src/test/resources/test_ec_public_key.pem b/hutool-crypto/src/test/resources/test_ec_public_key.pem new file mode 100644 index 000000000..692d47b9f --- /dev/null +++ b/hutool-crypto/src/test/resources/test_ec_public_key.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEl142zvur2O73Q+Wl6IthDB1KtIrS +PJBuhTd3ICwRRaHpcstycV/eIe0aR+MpaTVF3kTbs79VXD2tOp7DdTo01g== +-----END PUBLIC KEY----- diff --git a/hutool-crypto/src/test/resources/test_ec_sec1_private_key.pem b/hutool-crypto/src/test/resources/test_ec_sec1_private_key.pem new file mode 100644 index 000000000..8b0b28be9 --- /dev/null +++ b/hutool-crypto/src/test/resources/test_ec_sec1_private_key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIBGZnOjXWLvFM6c4V9W3Ec31gZXBoJz7an1bEc1312f+oAoGCCqGSM49 +AwEHoUQDQgAEl142zvur2O73Q+Wl6IthDB1KtIrSPJBuhTd3ICwRRaHpcstycV/e +Ie0aR+MpaTVF3kTbs79VXD2tOp7DdTo01g== +-----END EC PRIVATE KEY----- diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index 2ed832948..1cce58bd1 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -52,7 +52,7 @@ public class HttpUtil { * @return 是否https */ public static boolean isHttps(String url) { - return url.toLowerCase().startsWith("https:"); + return StrUtil.startWithIgnoreCase(url, "https:"); } /** @@ -63,7 +63,7 @@ public class HttpUtil { * @since 5.3.8 */ public static boolean isHttp(String url) { - return url.toLowerCase().startsWith("http:"); + return StrUtil.startWithIgnoreCase(url, "http:"); } /** diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 18dbb9682..fe9788dc9 100755 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -16,10 +16,26 @@ import java.util.Map; public class HttpUtilTest { + + @Test + public void isHttpTest(){ + Assert.assertTrue(HttpUtil.isHttp("Http://aaa.bbb")); + Assert.assertTrue(HttpUtil.isHttp("HTTP://aaa.bbb")); + Assert.assertFalse(HttpUtil.isHttp("FTP://aaa.bbb")); + } + + @Test + public void isHttpsTest(){ + Assert.assertTrue(HttpUtil.isHttps("Https://aaa.bbb")); + Assert.assertTrue(HttpUtil.isHttps("HTTPS://aaa.bbb")); + Assert.assertTrue(HttpUtil.isHttps("https://aaa.bbb")); + Assert.assertFalse(HttpUtil.isHttps("ftp://aaa.bbb")); + } + @Test @Ignore public void postTest() { - String result = HttpUtil.createPost("api.uhaozu.com/goods/description/1120448506") + final String result = HttpUtil.createPost("api.uhaozu.com/goods/description/1120448506") .charset(CharsetUtil.UTF_8) .execute().body(); Console.log(result); @@ -29,7 +45,7 @@ public class HttpUtilTest { @Ignore public void postTest2() { // 某些接口对Accept头有特殊要求,此处自定义头 - String result = HttpUtil + final String result = HttpUtil .createPost("http://cmp.ishanghome.com/cmp/v1/community/queryClusterCommunity") .header(Header.ACCEPT, "*/*") .execute() @@ -40,7 +56,7 @@ public class HttpUtilTest { @Test @Ignore public void getTest() { - String result1 = HttpUtil.get("http://photo.qzone.qq.com/fcgi-bin/fcg_list_album?uin=88888&outstyle=2", CharsetUtil.CHARSET_GBK); + final String result1 = HttpUtil.get("http://photo.qzone.qq.com/fcgi-bin/fcg_list_album?uin=88888&outstyle=2", CharsetUtil.CHARSET_GBK); Console.log(result1); } @@ -49,7 +65,7 @@ public class HttpUtilTest { public void getTest2() { // 此链接较为特殊,User-Agent去掉后进入一个JS跳转页面,如果设置了,需要开启302跳转 // 自定义的默认header无效 - String result = HttpRequest + final String result = HttpRequest .get("https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=101457313&redirect_uri=http%3A%2F%2Fwww.benmovip.com%2Fpay-cloud%2Fqqlogin%2FgetCode&state=ok") .removeHeader(Header.USER_AGENT).execute().body(); Console.log(result); @@ -59,7 +75,7 @@ public class HttpUtilTest { @Ignore public void getTest3() { // 测试url中带有空格的情况 - String result1 = HttpUtil.get("http://hutool.cn:5000/kf?abc= d"); + final String result1 = HttpUtil.get("http://hutool.cn:5000/kf?abc= d"); Console.log(result1); } @@ -67,7 +83,7 @@ public class HttpUtilTest { @Ignore public void getTest4() { // 测试url中带有空格的情况 - byte[] str = HttpRequest.get("http://img01.fs.yiban.cn/mobile/2D0Y71").execute().bodyBytes(); + final byte[] str = HttpRequest.get("http://img01.fs.yiban.cn/mobile/2D0Y71").execute().bodyBytes(); FileUtil.writeBytes(str, "f:/test/2D.jpg"); Console.log(str); @@ -77,7 +93,7 @@ public class HttpUtilTest { @Ignore public void getTest5() { String url2 = "http://storage.chancecloud.com.cn/20200413_%E7%B2%A4B12313_386.pdf"; - ByteArrayOutputStream os2 = new ByteArrayOutputStream(); + final ByteArrayOutputStream os2 = new ByteArrayOutputStream(); HttpUtil.download(url2, os2, false); url2 = "http://storage.chancecloud.com.cn/20200413_粤B12313_386.pdf"; @@ -95,9 +111,9 @@ public class HttpUtilTest { @Test @Ignore public void downloadStringTest() { - String url = "https://www.baidu.com"; + final String url = "https://www.baidu.com"; // 从远程直接读取字符串,需要自定义编码,直接调用JDK方法 - String content2 = HttpUtil.downloadString(url, CharsetUtil.UTF_8); + final String content2 = HttpUtil.downloadString(url, CharsetUtil.UTF_8); Console.log(content2); } @@ -107,8 +123,8 @@ public class HttpUtilTest { // 请求列表页 String listContent = HttpUtil.get("https://www.oschina.net/action/ajax/get_more_news_list?newsType=&p=2"); // 使用正则获取所有标题 - List titles = ReUtil.findAll("(.*?)", listContent, 1); - for (String title : titles) { + final List titles = ReUtil.findAll("(.*?)", listContent, 1); + for (final String title : titles) { // 打印标题 Console.log(title); } @@ -120,8 +136,8 @@ public class HttpUtilTest { @Test public void decodeParamsTest() { - String paramsStr = "uuuu=0&a=b&c=%3F%23%40!%24%25%5E%26%3Ddsssss555555"; - Map> map = HttpUtil.decodeParams(paramsStr, CharsetUtil.UTF_8); + final String paramsStr = "uuuu=0&a=b&c=%3F%23%40!%24%25%5E%26%3Ddsssss555555"; + final Map> map = HttpUtil.decodeParams(paramsStr, CharsetUtil.UTF_8); Assert.assertEquals("0", map.get("uuuu").get(0)); Assert.assertEquals("b", map.get("a").get(0)); Assert.assertEquals("?#@!$%^&=dsssss555555", map.get("c").get(0)); @@ -130,17 +146,17 @@ public class HttpUtilTest { @Test public void decodeParamMapTest() { // 参数值存在分界标记等号时 - Map paramMap = HttpUtil.decodeParamMap("https://www.xxx.com/api.action?aa=123&f_token=NzBkMjQxNDM1MDVlMDliZTk1OTU3ZDI1OTI0NTBiOWQ=", CharsetUtil.CHARSET_UTF_8); + final Map paramMap = HttpUtil.decodeParamMap("https://www.xxx.com/api.action?aa=123&f_token=NzBkMjQxNDM1MDVlMDliZTk1OTU3ZDI1OTI0NTBiOWQ=", CharsetUtil.CHARSET_UTF_8); Assert.assertEquals("123",paramMap.get("aa")); Assert.assertEquals("NzBkMjQxNDM1MDVlMDliZTk1OTU3ZDI1OTI0NTBiOWQ=",paramMap.get("f_token")); } @Test public void toParamsTest() { - String paramsStr = "uuuu=0&a=b&c=3Ddsssss555555"; - Map> map = HttpUtil.decodeParams(paramsStr, CharsetUtil.UTF_8); + final String paramsStr = "uuuu=0&a=b&c=3Ddsssss555555"; + final Map> map = HttpUtil.decodeParams(paramsStr, CharsetUtil.UTF_8); - String encodedParams = HttpUtil.toParams(map); + final String encodedParams = HttpUtil.toParams(map); Assert.assertEquals(paramsStr, encodedParams); } @@ -253,13 +269,13 @@ public class HttpUtilTest { @Ignore public void patchTest() { // 验证patch请求是否可用 - String body = HttpRequest.patch("https://www.baidu.com").execute().body(); + final String body = HttpRequest.patch("https://www.baidu.com").execute().body(); Console.log(body); } @Test public void urlWithFormTest() { - Map param = new LinkedHashMap<>(); + final Map param = new LinkedHashMap<>(); param.put("AccessKeyId", "123"); param.put("Action", "DescribeDomainRecords"); param.put("Format", "date"); @@ -298,19 +314,19 @@ public class HttpUtilTest { @Test public void normalizeParamsTest() { - String encodeResult = HttpUtil.normalizeParams("参数", CharsetUtil.CHARSET_UTF_8); + final String encodeResult = HttpUtil.normalizeParams("参数", CharsetUtil.CHARSET_UTF_8); Assert.assertEquals("%E5%8F%82%E6%95%B0", encodeResult); } @Test public void normalizeBlankParamsTest() { - String encodeResult = HttpUtil.normalizeParams("", CharsetUtil.CHARSET_UTF_8); + final String encodeResult = HttpUtil.normalizeParams("", CharsetUtil.CHARSET_UTF_8); Assert.assertEquals("", encodeResult); } @Test public void getMimeTypeTest() { - String mimeType = HttpUtil.getMimeType("aaa.aaa"); + final String mimeType = HttpUtil.getMimeType("aaa.aaa"); Assert.assertNull(mimeType); } @@ -318,7 +334,7 @@ public class HttpUtilTest { @Ignore public void getWeixinTest(){ // 测试特殊URL,即URL中有&情况是否请求正常 - String url = "https://mp.weixin.qq.com/s?__biz=MzI5NjkyNTIxMg==&mid=100000465&idx=1&sn=1044c0d19723f74f04f4c1da34eefa35&chksm=6cbda3a25bca2ab4516410db6ce6e125badaac2f8c5548ea6e18eab6dc3c5422cb8cbe1095f7"; + final String url = "https://mp.weixin.qq.com/s?__biz=MzI5NjkyNTIxMg==&mid=100000465&idx=1&sn=1044c0d19723f74f04f4c1da34eefa35&chksm=6cbda3a25bca2ab4516410db6ce6e125badaac2f8c5548ea6e18eab6dc3c5422cb8cbe1095f7"; final String s = HttpUtil.get(url); Console.log(s); } @@ -326,7 +342,7 @@ public class HttpUtilTest { @Test @Ignore public void getNocovTest(){ - String url = "https://qiniu.nocov.cn/medical-manage%2Ftest%2FBANNER_IMG%2F444004467954556928%2F1595215173047icon.png~imgReduce?e=1597081986&token=V2lJYVgQgAv_sbypfEZ0qpKs6TzD1q5JIDVr0Tw8:89cbBkLLwEc9JsMoCLkAEOu820E="; + final String url = "https://qiniu.nocov.cn/medical-manage%2Ftest%2FBANNER_IMG%2F444004467954556928%2F1595215173047icon.png~imgReduce?e=1597081986&token=V2lJYVgQgAv_sbypfEZ0qpKs6TzD1q5JIDVr0Tw8:89cbBkLLwEc9JsMoCLkAEOu820E="; final String s = HttpUtil.get(url); Console.log(s); } @@ -341,7 +357,7 @@ public class HttpUtilTest { @Test @Ignore public void gimg2Test(){ - byte[] bytes = HttpUtil.downloadBytes("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 byte[] bytes = HttpUtil.downloadBytes("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"); Console.log(Base64.encode(bytes)); } @@ -357,7 +373,7 @@ public class HttpUtilTest { @Ignore public void getPicTest(){ HttpGlobalConfig.setDecodeUrl(false); - String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + final String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; final HttpRequest request = HttpRequest.of(url).method(Method.GET); Console.log(request.execute().body()); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2447Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2447Test.java new file mode 100644 index 000000000..6452f0216 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2447Test.java @@ -0,0 +1,36 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +import java.time.LocalDateTime; + +public class Issue2447Test { + + @Test + public void addInteger() { + Time time = new Time(); + time.setTime(LocalDateTime.of(1970, 1, 2, 10, 0, 1, 0)); + String timeStr = JSONUtil.toJsonStr(time); + Assert.assertEquals(timeStr, "{\"time\":93601000}"); + Assert.assertEquals(JSONUtil.toBean(timeStr, Time.class).getTime(), time.getTime()); + } + + static class Time { + private LocalDateTime time; + + public LocalDateTime getTime() { + return time; + } + + public void setTime(LocalDateTime time) { + this.time = time; + } + + @Override + public String toString() { + return time.toString(); + } + } + +}