From a1a199513f22e88a51500463960c0dac9aa45b3e Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Tue, 13 Sep 2022 13:56:41 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E6=98=A0=E5=B0=84=E7=9A=84=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E6=B3=A8=E8=A7=A3=E5=8C=85=E8=A3=85=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/hutool/core/annotation/Alias.java | 12 +- .../core/annotation/AnnotationMapping.java | 89 +++ .../annotation/AnnotationMappingProxy.java | 177 +++++ .../core/annotation/AnnotationUtil.java | 58 +- .../annotation/GenericAnnotationMapping.java | 156 ++++ .../annotation/ResolvedAnnotationMapping.java | 682 ++++++++++++++++++ .../GenericAnnotationMappingTest.java | 120 +++ .../ResolvedAnnotationMappingTest.java | 300 ++++++++ 8 files changed, 1581 insertions(+), 13 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationMapping.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationMappingProxy.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/annotation/GenericAnnotationMapping.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/annotation/ResolvedAnnotationMapping.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/annotation/GenericAnnotationMappingTest.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/annotation/ResolvedAnnotationMappingTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/Alias.java b/hutool-core/src/main/java/cn/hutool/core/annotation/Alias.java index 3c1274e5f..142a4fc41 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/Alias.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/Alias.java @@ -1,13 +1,13 @@ package cn.hutool.core.annotation; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import java.lang.annotation.*; /** - * 别名注解,使用此注解的字段、方法、参数等会有一个别名,用于Bean拷贝、Bean转Map等 + *

别名注解,使用此注解的字段、方法、参数等会有一个别名,用于Bean拷贝、Bean转Map等。 + * + *

当在注解中使用时,可为令多个属性互相关联,当对其中任意属性赋值时, + * 会将属性值一并同步到所有关联的属性中。
+ * 该功能参考{@link AnnotatedElementUtil}。 * * @author Looly * @since 5.1.1 diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationMapping.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationMapping.java new file mode 100644 index 000000000..c177629f6 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationMapping.java @@ -0,0 +1,89 @@ +package cn.hutool.core.annotation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +/** + * 用于增强注解的包装器 + * + * @param 注解类型 + * @author huangchengxing + * @see AnnotationMappingProxy + * @since 6.0.0 + */ +public interface AnnotationMapping extends Annotation { + + /** + * 当前注解是否为根注解 + * + * @return 是否 + */ + boolean isRoot(); + + /** + * 获取注解对象 + * + * @return 注解对象 + */ + T getAnnotation(); + + /** + * 根据当前映射对象,通过动态代理生成一个类型与被包装注解对象一致的合成注解,该注解相对原生注解: + *

+ * 当{@link #isResolved()}为{@code false}时,则该方法应当被包装的原始注解对象, + * 即返回值应当与{@link #getAnnotation()}相同。 + * + * @return 所需的注解,若{@link #isResolved()}为{@code false}则返回的是原始的注解对象 + */ + T getResolvedAnnotation(); + + /** + * 获取注解类型 + * + * @return 注解类型 + */ + @Override + default Class annotationType() { + return getAnnotation().annotationType(); + } + + /** + * 当前注解是否存在被解析的属性,当该值为{@code false}时, + * 通过{@code getResolvedAttributeValue}获得的值皆为注解的原始属性值, + * 通过{@link #getResolvedAnnotation()}获得注解对象为原始的注解对象。 + * + * @return 是否 + */ + boolean isResolved(); + + /** + * 获取注解原始属性 + * + * @return 注解属性 + */ + Method[] getAttributes(); + + /** + * 获取属性值 + * + * @param attributeName 属性名称 + * @param attributeType 属性类型 + * @param 返回值类型 + * @return 属性值 + */ + R getAttributeValue(final String attributeName, final Class attributeType); + + /** + * 获取解析后的属性值 + * + * @param attributeName 属性名称 + * @param attributeType 属性类型 + * @param 返回值类型 + * @return 属性值 + */ + R getResolvedAttributeValue(final String attributeName, final Class attributeType); + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationMappingProxy.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationMappingProxy.java new file mode 100644 index 000000000..59dd3ea0d --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationMappingProxy.java @@ -0,0 +1,177 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.reflect.MethodUtil; +import cn.hutool.core.text.CharSequenceUtil; + +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.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiFunction; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 代理注解处理器,用于为{@link AnnotationMapping}生成代理对象,当从该代理对象上获取属性值时, + * 总是通过{@link AnnotationMapping#getResolvedAttributeValue(String, Class)}获取。 + * + * @param 注解类型 + * @author huangchengxing + * @see AnnotationMapping + * @since 6.0.0 + */ +public final class AnnotationMappingProxy implements InvocationHandler { + + /** + * 属性映射 + */ + private final AnnotationMapping mapping; + + /** + * 代理方法 + */ + private final Map> methods; + + /** + * 属性值缓存 + */ + private final Map valueCache; + + /** + * 创建一个代理对象 + * + * @param annotationType 注解类型 + * @param mapping 注解映射对象 + * @param 注解类型 + * @return 代理对象 + */ + @SuppressWarnings("unchecked") + public static A create(final Class annotationType, final AnnotationMapping mapping) { + Objects.requireNonNull(annotationType); + Objects.requireNonNull(mapping); + final AnnotationMappingProxy invocationHandler = new AnnotationMappingProxy<>(mapping); + return (A)Proxy.newProxyInstance( + annotationType.getClassLoader(), + new Class[]{ annotationType, Proxied.class }, + invocationHandler + ); + } + + /** + * 当前注解是否由当前代理类生成 + * + * @param annotation 注解对象 + * @return 是否 + */ + public static boolean isProxied(final Annotation annotation) { + return annotation instanceof Proxied; + } + + /** + * 创建一个代理方法处理器 + * + * @param annotation 属性映射 + */ + private AnnotationMappingProxy(final AnnotationMapping annotation) { + int methodCount = annotation.getAttributes().length; + this.methods = new HashMap<>(methodCount + 5); + this.valueCache = new ConcurrentHashMap<>(methodCount); + this.mapping = annotation; + loadMethods(); + } + + /** + * 调用被代理的方法 + * + * @param proxy 代理对象 + * @param method 方法 + * @param args 参数 + * @return 返回值 + */ + @Override + public Object invoke(final Object proxy, final Method method, final Object[] args) { + return Optional.ofNullable(methods.get(method.getName())) + .map(m -> m.apply(method, args)) + .orElseGet(() -> MethodUtil.invoke(this, method, args)); + } + + // ============================== 代理方法 ============================== + + /** + * 预加载需要代理的方法 + */ + private void loadMethods() { + methods.put("equals", (method, args) -> proxyEquals(args[0])); + methods.put("toString", (method, args) -> proxyToString()); + methods.put("hashCode", (method, args) -> proxyHashCode()); + methods.put("annotationType", (method, args) -> proxyAnnotationType()); + methods.put("getMapping", (method, args) -> proxyGetMapping()); + for (Method attribute : mapping.getAttributes()) { + methods.put(attribute.getName(), (method, args) -> getAttributeValue(method.getName(), method.getReturnType())); + } + } + + /** + * 代理{@link Annotation#toString()}方法 + */ + private String proxyToString() { + final String attributes = Stream.of(mapping.getAttributes()) + .map(attribute -> CharSequenceUtil.format("{}={}", attribute.getName(), getAttributeValue(attribute.getName(), attribute.getReturnType()))) + .collect(Collectors.joining(", ")); + return CharSequenceUtil.format("@{}({})", mapping.annotationType().getName(), attributes); + } + + /** + * 代理{@link Annotation#hashCode()}方法 + */ + private int proxyHashCode() { + return this.hashCode(); + } + + /** + * 代理{@link Annotation#equals(Object)}方法 + */ + private boolean proxyEquals(Object o) { + return Objects.equals(mapping, o); + } + + /** + * 代理{@link Annotation#annotationType()}方法 + */ + private Class proxyAnnotationType() { + return mapping.annotationType(); + } + + /** + * 代理{@link Proxied#getMapping()}方法 + */ + private AnnotationMapping proxyGetMapping() { + return mapping; + } + + /** + * 获取属性值 + */ + private Object getAttributeValue(final String attributeName, final Class attributeType) { + return valueCache.computeIfAbsent(attributeName, name -> mapping.getResolvedAttributeValue(attributeName, attributeType)); + } + + /** + * 表明注解是一个合成的注解 + */ + interface Proxied { + + /** + * 获取注解映射对象 + * + * @return 注解映射对象 + */ + AnnotationMapping getMapping(); + + } +} 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 632f4d20c..c9a7070cc 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 @@ -4,20 +4,18 @@ import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.reflect.FieldUtil; import cn.hutool.core.reflect.MethodUtil; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjUtil; -import java.lang.annotation.Annotation; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import java.lang.annotation.*; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.function.Predicate; +import java.util.stream.Stream; /** * 注解工具类
@@ -295,4 +293,50 @@ public class AnnotationUtil { final T annotation = getAnnotation(annotationEle, annotationType); return (T) Proxy.newProxyInstance(annotationType.getClassLoader(), new Class[]{annotationType}, new AnnotationProxy<>(annotation)); } + + /** + * 获取注解属性 + * + * @param annotationType 注解类型 + * @return 注解属性 + * @see 6.0.0 + */ + public static Method[] getAnnotationAttributes(final Class annotationType) { + // TODO 改为通过带缓存的反射工具类完成 + Objects.requireNonNull(annotationType); + return Stream.of(annotationType.getDeclaredMethods()) + .filter(AnnotationUtil::isAnnotationAttribute) + .toArray(Method[]::new); + } + + /** + * 该方法是否是注解属性,需要满足下述条件: + *
    + *
  • 不是{@link Object#equals(Object)};
  • + *
  • 不是{@link Object#hashCode()};
  • + *
  • 不是{@link Object#toString()};
  • + *
  • 不是桥接方法;
  • + *
  • 不是合成方法;
  • + *
  • 不是静态方法;
  • + *
  • 是公共方法;
  • + *
  • 方法必须没有参数;
  • + *
  • 方法必须有返回值(返回值类型不为{@link Void});
  • + *
+ * + * @param attribute 方法对象 + * @return 是否 + * @see 6.0.0 + */ + public static boolean isAnnotationAttribute(final Method attribute) { + return !MethodUtil.isEqualsMethod(attribute) + && !MethodUtil.isHashCodeMethod(attribute) + && !MethodUtil.isToStringMethod(attribute) + && ArrayUtil.isEmpty(attribute.getParameterTypes()) + && ObjUtil.notEquals(attribute.getReturnType(), Void.class) + && !Modifier.isStatic(attribute.getModifiers()) + && Modifier.isPublic(attribute.getModifiers()) + && !attribute.isBridge() + && !attribute.isSynthetic(); + } + } diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/GenericAnnotationMapping.java b/hutool-core/src/main/java/cn/hutool/core/annotation/GenericAnnotationMapping.java new file mode 100644 index 000000000..1d35b024c --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/GenericAnnotationMapping.java @@ -0,0 +1,156 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.reflect.ClassUtil; +import cn.hutool.core.reflect.MethodUtil; +import cn.hutool.core.text.CharSequenceUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Objects; +import java.util.stream.Stream; + +/** + * {@link AnnotationMapping}的基本实现,仅仅是简单包装了注解对象 + * + * @author huangchengxing + * @since 6.0.0 + */ +public class GenericAnnotationMapping implements AnnotationMapping { + + private final Annotation annotation; + private final boolean isRoot; + private final Method[] attributes; + + /** + * 创建一个通用注解包装类 + * + * @param annotation 注解对象 + * @param isRoot 是否根注解 + * @return {@link GenericAnnotationMapping}实例 + */ + public static GenericAnnotationMapping create(final Annotation annotation, final boolean isRoot) { + return new GenericAnnotationMapping(annotation, isRoot); + } + + /** + * 创建一个通用注解包装类 + * + * @param annotation 注解对象 + * @param isRoot 是否根注解 + */ + GenericAnnotationMapping(final Annotation annotation, final boolean isRoot) { + this.annotation = Objects.requireNonNull(annotation); + this.isRoot = isRoot; + this.attributes = AnnotationUtil.getAnnotationAttributes(annotation.annotationType()); + } + + /** + * 当前注解是否为根注解 + * + * @return 是否 + */ + @Override + public boolean isRoot() { + return isRoot; + } + + /** + * 获取注解对象 + * + * @return 注解对象 + */ + @Override + public Annotation getAnnotation() { + return annotation; + } + + /** + * 同{@link #getAnnotation()} + * + * @return 注解对象 + */ + @Override + public Annotation getResolvedAnnotation() { + return getAnnotation(); + } + + /** + * 总是返回{@code false} + * + * @return {@code false} + */ + @Override + public boolean isResolved() { + return false; + } + + /** + * 获取注解原始属性 + * + * @return 注解属性 + */ + @Override + public Method[] getAttributes() { + return attributes; + } + + /** + * 获取属性值 + * + * @param attributeName 属性名称 + * @param attributeType 属性类型 + * @param 返回值类型 + * @return 属性值 + */ + @Override + public R getAttributeValue(final String attributeName, final Class attributeType) { + return Stream.of(attributes) + .filter(attribute -> CharSequenceUtil.equals(attribute.getName(), attributeName)) + .filter(attribute -> ClassUtil.isAssignable(attributeType, attribute.getReturnType())) + .findFirst() + .map(method -> MethodUtil.invoke(annotation, method)) + .map(attributeType::cast) + .orElse(null); + } + + /** + * 获取解析后的属性值 + * + * @param attributeName 属性名称 + * @param attributeType 属性类型 + * @param 返回值类型 + * @return 属性值 + */ + @Override + public R getResolvedAttributeValue(final String attributeName, final Class attributeType) { + return getAttributeValue(attributeName, attributeType); + } + + /** + * 比较两个实例是否相等 + * + * @param o 对象 + * @return 是否 + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + GenericAnnotationMapping that = (GenericAnnotationMapping)o; + return isRoot == that.isRoot && annotation.equals(that.annotation); + } + + /** + * 获取实例哈希值 + * + * @return 哈希值 + */ + @Override + public int hashCode() { + return Objects.hash(annotation, isRoot); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/ResolvedAnnotationMapping.java b/hutool-core/src/main/java/cn/hutool/core/annotation/ResolvedAnnotationMapping.java new file mode 100644 index 000000000..26062a144 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/ResolvedAnnotationMapping.java @@ -0,0 +1,682 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.multi.MultiValueMap; +import cn.hutool.core.map.multi.SetValueMap; +import cn.hutool.core.reflect.ClassUtil; +import cn.hutool.core.reflect.MethodUtil; +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.ArrayUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.*; +import java.util.function.IntConsumer; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +/** + *

注解映射,用于包装并增强一个普通注解对象, + * 包装后的可以通过{@code getResolvedXXX}获得注解对象或属性值, + * 可以支持属性别名与属性覆写的属性解析机制。 + * + *

父子注解 + *

当实例创建时,可通过{@link #source}指定当前注解的子注解,多个实例通过该引用, + * 可以构成一条表示父子/元注解关系的单向链表。
+ * 当{@link #source}为{@code null}时,认为当前注解即为根注解。 + * + *

属性别名 + *

注解内的属性可以通过{@link Alias}互相关联,当解析时, + * 对绑定中的任意一个属性的赋值,会被同步给其他直接或者间接关联的属性。
+ * eg: 若注解存在{@code a <=> b <=> c}的属性别名关系,则对a赋值,此时bc也会被一并赋值。 + * + *

属性覆写 + *

当实例中{@link #source}不为{@code null},即当前注解存在至少一个或者多个子注解时, + * 若在子注解中的同名、同类型的属性,则获取值时将优先获取子注解的值,若该属性存在别名,则别名属性也如此。
+ * 属性覆写遵循如下机制: + *

    + *
  • + * 当覆写的属性存在别名属性时,别名属性也会一并被覆写;
    + * eg: 若注解存在{@code a <=> b <=> c}的属性别名关系,则覆写a,,属性bc也会被覆写; + *
  • + *
  • + * 当属性可被多个子注解覆写时,总是优先选择离根注解最近的子注解覆写该属性;
    + * eg:若从根注解a到元注解b有依赖关系{@code a => b => c}, + * 此时若c中存在属性可同时被ab覆写,则优先选择a; + *
  • + *
  • + * 当覆写属性的子注解属性也被其子注解覆写时,等同于该子注解的子注解直接覆写的当前注解的属性;
    + * eg:若从根注解a到元注解b有依赖关系{@code a => b => c}, + * 此时若b中存在属性被a覆写,而b中被a覆写的属性又覆写c中属性, + * 则等同于c中被覆写的属性直接被a覆写。 + *
  • + *
+ * + * @author huangchengxing + * @see MetaAnnotatedElement + * @since 6.0.0 + */ +public class ResolvedAnnotationMapping implements AnnotationMapping { + + /** + * 不存在的属性对应的默认下标 + */ + protected static final int NOT_FOUND_INDEX = -1; + + /** + * 注解属性,属性在该数组中的下标等同于属性本身 + */ + private final Method[] attributes; + + /** + * 别名属性设置 + */ + private final AliasSet[] aliasSets; + + /** + * 解析后的属性,下标都与{@link #attributes}相同下标的属性一一对应。 + * 当下标对应属性下标不为{@link #NOT_FOUND_INDEX}时,说明该属性存在解析: + *
    + *
  • 若在{@link #resolvedAttributeSources}找不到对应实例,则说明该属性是别名属性;
  • + *
  • 若在{@link #resolvedAttributeSources}找的到对应实例,则说明该属性是覆盖属性;
  • + *
+ */ + private final int[] resolvedAttributes; + + /** + * 解析后的属性对应的数据源
+ * 当属性被覆写时,该属性对应下标位置会指向覆写该属性的注解对象 + */ + private final ResolvedAnnotationMapping[] resolvedAttributeSources; + + /** + * 子注解的映射对象,当该项为{@code null}时,则认为当前注解为根注解 + */ + private final ResolvedAnnotationMapping source; + + /** + * 注解属性 + */ + private final Annotation annotation; + + /** + * 代理对象缓存 + */ + private volatile Annotation proxied; + + /** + * 该注解的属性是否发生了解析 + */ + private final boolean resolved; + + /** + * 构建一个注解映射对象 + * + * @param annotation 注解对象 + * @param resolveAnnotationAttribute 是否解析注解属性,为{@code true}时获得的注解皆支持属性覆盖与属性别名机制 + * @return 注解映射对象 + */ + public static ResolvedAnnotationMapping create(final Annotation annotation, final boolean resolveAnnotationAttribute) { + return create(null, annotation, resolveAnnotationAttribute); + } + + /** + * 构建一个注解映射对象,子注解及子注解的子注解们的属性会覆写注解对象的中的同名同名同类型属性, + * 当一个属性被多个子注解覆写时,优先选择离根注解最接近的注解中的属性用于覆写, + * + * @param source 子注解 + * @param annotation 注解对象 + * @param resolveAnnotationAttribute 是否解析注解属性,为{@code true}时获得的注解皆支持属性覆盖与属性别名机制 + * @return 注解映射对象 + */ + public static ResolvedAnnotationMapping create( + final ResolvedAnnotationMapping source, final Annotation annotation, final boolean resolveAnnotationAttribute) { + return new ResolvedAnnotationMapping(source, annotation, resolveAnnotationAttribute); + } + + /** + * 构建一个注解映射对象 + * + * @param source 当前注解的子注解 + * @param annotation 注解对象 + * @param resolveAttribute 是否需要解析属性 + * @throws NullPointerException {@code source}为{@code null}时抛出 + * @throws IllegalArgumentException + *
    + *
  • 当{@code annotation}已经被代理过时抛出;
  • + *
  • 当{@code source}包装的注解对象与{@code annotation}相同时抛出;
  • + *
  • 当{@code annotation}包装的注解对象类型为{@link ResolvedAnnotationMapping}时抛出;
  • + *
+ */ + ResolvedAnnotationMapping(final ResolvedAnnotationMapping source, final Annotation annotation, boolean resolveAttribute) { + Objects.requireNonNull(annotation); + Assert.isFalse(AnnotationMappingProxy.isProxied(annotation), "annotation has been proxied"); + Assert.isFalse(annotation instanceof ResolvedAnnotationMapping, "annotation has been wrapped"); + Assert.isFalse( + Objects.nonNull(source) && Objects.equals(source.annotation, annotation), + "source annotation can not same with target [{}]", annotation + ); + this.annotation = annotation; + this.attributes = AnnotationUtil.getAnnotationAttributes(annotation.annotationType()); + this.source = source; + + // 别名属性 + this.aliasSets = new AliasSet[this.attributes.length]; + + // 解析后的属性与数据源 + this.resolvedAttributeSources = new ResolvedAnnotationMapping[this.attributes.length]; + this.resolvedAttributes = new int[this.attributes.length]; + Arrays.fill(this.resolvedAttributes, NOT_FOUND_INDEX); + + // 若有必要,解析属性 + // TODO 可能的改进:flag改为枚举,使得可以自行选择:1.只支持属性别名,2.只支持属性覆盖,3.两个都支持,4.两个都不支持 + this.resolved = resolveAttribute && resolveAttributes(); + } + + /** + * 解析属性 + */ + private boolean resolveAttributes() { + // 解析同一注解中的别名 + resolveAliasAttributes(); + // 使用子注解覆写当前注解中的属性 + resolveOverwriteAttributes(); + // 注解的属性是否发生过解析 + return IntStream.of(resolvedAttributes) + .anyMatch(idx -> NOT_FOUND_INDEX != idx); + } + + // ================== 通用 ================== + + /** + * 当前注解是否为根注解 + * + * @return 是否 + */ + @Override + public boolean isRoot() { + return Objects.isNull(source); + } + + /** + * 获取根注解 + * + * @return 根注解的映射对象 + */ + public ResolvedAnnotationMapping getRoot() { + ResolvedAnnotationMapping mapping = this; + while (Objects.nonNull(mapping.source)) { + mapping = mapping.source; + } + return mapping; + } + + /** + * 获取注解属性 + * + * @return 注解属性 + */ + @Override + public Method[] getAttributes() { + return attributes; + } + + /** + * 获取注解对象 + * + * @return 注解对象 + */ + @Override + public Annotation getAnnotation() { + return annotation; + } + + /** + * 当前注解是否存在被解析的属性,当该值为{@code false}时, + * 通过{@code getResolvedAttributeValue}获得的值皆为注解的原始属性值, + * 通过{@link #getResolvedAnnotation()}获得注解对象为原始的注解对象。 + * + * @return 是否 + */ + @Override + public boolean isResolved() { + return resolved; + } + + /** + * 根据当前映射对象,通过动态代理生成一个类型与被包装注解对象一致的合成注解,该注解相对原生注解: + *
    + *
  • 支持同注解内通过{@link Alias}构建的别名机制;
  • + *
  • 支持子注解对元注解的同名同类型属性覆盖机制;
  • + *
+ * 当{@link #isResolved()}为{@code false}时,则该方法返回被包装的原始注解对象。 + * + * @return 所需的注解,若{@link ResolvedAnnotationMapping#isResolved()}为{@code false}则返回的是原始的注解对象 + */ + @SuppressWarnings("unchecked") + @Override + public Annotation getResolvedAnnotation() { + if (!isResolved()) { + return annotation; + } + // 双重检查保证线程安全的创建代理缓存 + if (Objects.isNull(proxied)) { + synchronized (this) { + if (Objects.isNull(proxied)) { + proxied = AnnotationMappingProxy.create(annotationType(), this); + } + } + } + return proxied; + } + + // ================== 属性搜索 ================== + + /** + * 注解是否存在指定属性 + * + * @param attributeName 属性名称 + * @param attributeType 属性类型 + * @return 是否 + */ + public boolean hasAttribute(final String attributeName, final Class attributeType) { + return getAttributeIndex(attributeName, attributeType) != NOT_FOUND_INDEX; + } + + /** + * 该属性下标是否在注解中存在对应属性 + * + * @param index 属性下标 + * @return 是否 + */ + public boolean hasAttribute(final int index) { + return index != NOT_FOUND_INDEX + && Objects.nonNull(ArrayUtil.get(attributes, index)); + } + + /** + * 获取注解属性的下标 + * + * @param attributeName 属性名称 + * @param attributeType 属性类型 + * @return 属性下标 + */ + public int getAttributeIndex(final String attributeName, final Class attributeType) { + for (int i = 0; i < attributes.length; i++) { + final Method attribute = attributes[i]; + if (CharSequenceUtil.equals(attribute.getName(), attributeName) + && ClassUtil.isAssignable(attributeType, attribute.getReturnType())) { + return i; + } + } + return NOT_FOUND_INDEX; + } + + /** + * 根据下标获取注解属性 + * + * @param index 属性下标 + * @return 属性对象 + */ + public Method getAttribute(final int index) { + return ArrayUtil.get(attributes, index); + } + + // ================== 属性取值 ================== + + /** + * 获取属性值 + * + * @param attributeName 属性名称 + * @param attributeType 属性类型 + * @param 返回值类型 + * @return 属性值 + */ + @Override + public R getAttributeValue(final String attributeName, final Class attributeType) { + return getAttributeValue(getAttributeIndex(attributeName, attributeType)); + } + + /** + * 获取属性值 + * + * @param index 属性下标 + * @param 返回值类型 + * @return 属性值 + */ + public R getAttributeValue(final int index) { + return hasAttribute(index) ? MethodUtil.invoke(annotation, attributes[index]) : null; + } + + /** + * 获取解析后的属性值 + * + * @param attributeName 属性名称 + * @param attributeType 属性类型 + * @param 返回值类型 + * @return 属性值 + */ + @Override + public R getResolvedAttributeValue(final String attributeName, final Class attributeType) { + return getResolvedAttributeValue(getAttributeIndex(attributeName, attributeType)); + } + + /** + * 获取解析后的属性值 + * + * @param index 属性下标 + * @param 返回值类型 + * @return 属性值 + */ + public R getResolvedAttributeValue(final int index) { + if (!hasAttribute(index)) { + return null; + } + // 如果该属性没有经过解析,则直接获得原始值 + final int resolvedIndex = resolvedAttributes[index]; + if (resolvedIndex == NOT_FOUND_INDEX) { + return getAttributeValue(index); + } + // 若该属性被解析过,但是仍然还在当前实例中,则从实际属性获得值 + final ResolvedAnnotationMapping attributeSource = resolvedAttributeSources[index]; + if (Objects.isNull(attributeSource)) { + return getAttributeValue(resolvedIndex); + } + // 若该属性被解析过,且不在本注解中,则从其元注解获得对应的值 + return attributeSource.getResolvedAttributeValue(resolvedIndex); + } + + // ================== 解析覆写属性 ================== + + /** + * 令{@code annotationAttributes}中属性覆写当前注解中同名同类型的属性, + * 该步骤必须在{@link #resolveAliasAttributes()}后进行 + */ + private void resolveOverwriteAttributes() { + if (Objects.isNull(source)) { + return; + } + // 获取除自己外的全部子注解 + final Deque sources = new LinkedList<>(); + Set> accessed = new HashSet<>(); + accessed.add(this.annotationType()); + ResolvedAnnotationMapping sourceMapping = this.source; + while (Objects.nonNull(sourceMapping)) { + // 检查循环依赖 + Assert.isFalse( + accessed.contains(sourceMapping.annotationType()), + "circular dependency between [{}] and [{}]", + annotationType(), sourceMapping.annotationType() + ); + sources.addFirst(sourceMapping); + accessed.add(source.annotationType()); + sourceMapping = sourceMapping.source; + } + // 从根注解开始,令子注解依次覆写当前注解中的值 + for (final ResolvedAnnotationMapping mapping : sources) { + updateResolvedAttributesByOverwrite(mapping); + } + } + + /** + * 令{@code annotationAttributes}中属性覆写当前注解中同名同类型且未被覆写的属性 + * @param overwriteMapping 注解属性聚合 + * + */ + private void updateResolvedAttributesByOverwrite(final ResolvedAnnotationMapping overwriteMapping) { + for (int overwriteIndex = 0; overwriteIndex < overwriteMapping.getAttributes().length; overwriteIndex++) { + final Method overwrite = overwriteMapping.getAttribute(overwriteIndex); + for (int targetIndex = 0; targetIndex < attributes.length; targetIndex++) { + final Method attribute = attributes[targetIndex]; + // 覆写的属性与被覆写的属性名称与类型必须一致 + if (!CharSequenceUtil.equals(attribute.getName(), overwrite.getName()) + || !ClassUtil.isAssignable(attribute.getReturnType(), overwrite.getReturnType())) { + continue; + } + // 若目标属性未被覆写,则覆写其属性 + overwriteAttribute(overwriteMapping, overwriteIndex, targetIndex, true); + } + } + } + + /** + * 更新需要覆写的属性的相关映射关系,若该属性存在别名,则将别名的映射关系一并覆写 + */ + private void overwriteAttribute( + final ResolvedAnnotationMapping overwriteMapping, final int overwriteIndex, final int targetIndex, boolean overwriteAliases) { + // 若目标属性已被覆写,则不允许再次覆写 + if (isOverwrittenAttribute(targetIndex)) { + return; + } + // 覆写属性 + resolvedAttributes[targetIndex] = overwriteIndex; + resolvedAttributeSources[targetIndex] = overwriteMapping; + // 若覆写的属性本身还存在别名,则将别名属性一并覆写 + if (overwriteAliases && Objects.nonNull(aliasSets[targetIndex])) { + aliasSets[targetIndex].forEach(aliasIndex -> overwriteAttribute( + overwriteMapping, overwriteIndex, aliasIndex, false + )); + } + } + + /** + * 判断该属性是否已被覆写 + */ + private boolean isOverwrittenAttribute(int index) { + // 若属性未发生过解析,则必然未被覆写 + return NOT_FOUND_INDEX != resolvedAttributes[index] + // 若属性发生过解析,且指向其他实例,则说明已被覆写 + && Objects.nonNull(resolvedAttributeSources[index]); + } + + // ================== 解析别名属性 ================== + + /** + * 解析当前注解属性中通过{@link Alias}构成别名的属性 + */ + private void resolveAliasAttributes() { + final Map attributeIndexes = new HashMap<>(attributes.length); + + // 解析被作为别名的关联属性,根据节点关系构建邻接表 + final MultiValueMap aliasedMethods = new SetValueMap<>(); + for (int i = 0; i < attributes.length; i++) { + // 获取属性上的@Alias注解 + final Method attribute = attributes[i]; + attributeIndexes.put(attribute, i); + final Alias attributeAnnotation = attribute.getAnnotation(Alias.class); + if (Objects.isNull(attributeAnnotation)) { + continue; + } + // 获取别名属性 + final Method aliasAttribute = getAliasAttribute(attribute, attributeAnnotation); + Objects.requireNonNull(aliasAttribute); + aliasedMethods.putValue(aliasAttribute, attribute); + aliasedMethods.putValue(attribute, aliasAttribute); + } + + // 按广度优先遍历邻接表,将属于同一张图上的节点分为一组,并为其建立AliasSet + final Set accessed = new HashSet<>(attributes.length); + final Set group = new LinkedHashSet<>(); + final Deque deque = new LinkedList<>(); + for (final Method target : aliasedMethods.keySet()) { + group.clear(); + deque.addLast(target); + while (!deque.isEmpty()) { + final Method curr = deque.removeFirst(); + // 已经访问过的节点不再访问 + if (accessed.contains(curr)) { + continue; + } + accessed.add(curr); + // 将其添加到关系组 + group.add(curr); + Collection aliases = aliasedMethods.get(curr); + if (CollUtil.isNotEmpty(aliases)) { + deque.addAll(aliases); + } + } + // 为同一关系组的节点构建关联关系 + final int[] groupIndexes = group.stream() + .mapToInt(attributeIndexes::get) + .toArray(); + updateAliasSetsForAliasGroup(groupIndexes); + } + + // 根据AliasSet更新关联的属性 + Stream.of(aliasSets).filter(Objects::nonNull).forEach(set -> { + final int resolvedIndex = set.resolve(); + set.forEach(index -> resolvedAttributes[index] = resolvedIndex); + }); + } + + /** + * 获取属性别名,并对其进行基本校验 + */ + private Method getAliasAttribute(final Method attribute, final Alias attributeAnnotation) { + // 获取别名属性下标,该属性必须在当前注解中存在 + final int aliasAttributeIndex = getAttributeIndex(attributeAnnotation.value(), attribute.getReturnType()); + Assert.isTrue(hasAttribute(aliasAttributeIndex), "can not find alias attribute [{}] in [{}]", attributeAnnotation.value(), this.annotation.annotationType()); + + // 获取具体的别名属性,该属性不能是其本身 + final Method aliasAttribute = getAttribute(aliasAttributeIndex); + Assert.notEquals(aliasAttribute, attribute, "attribute [{}] can not alias for itself", attribute); + + // 互为别名的属性类型必须一致 + Assert.isAssignable( + attribute.getReturnType(), aliasAttribute.getReturnType(), + "aliased attributes [{}] and [{}] must have same return type", + attribute, aliasAttribute + ); + return aliasAttribute; + } + + /** + * 为具有关联关系的别名属性构建{@link AliasSet} + */ + private void updateAliasSetsForAliasGroup(final int[] groupIndexes) { + final AliasSet set = new AliasSet(groupIndexes); + for (final int index : groupIndexes) { + aliasSets[index] = set; + } + } + + /** + * 比较两个实例是否相等 + * + * @param o 对象 + * @return 是否 + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ResolvedAnnotationMapping that = (ResolvedAnnotationMapping)o; + return resolved == that.resolved && annotation.equals(that.annotation); + } + + /** + * 获取实例哈希值 + * + * @return 哈希值 + */ + @Override + public int hashCode() { + return Objects.hash(annotation, resolved); + } + + /** + * 别名设置,一组具有别名关系的属性会共用同一实例 + */ + private class AliasSet { + + /** + * 关联的别名字段对应的属性在{@link #attributes}中的下标 + */ + final int[] indexes; + + /** + * 创建一个别名设置 + * + * @param indexes 互相关联的别名属性的下标 + */ + AliasSet(final int[] indexes) { + this.indexes = indexes; + } + + /** + * 从所有关联的别名属性中,选择出唯一个最终有效的属性: + *
    + *
  • 若所有属性都只有默认值,则要求所有的默认值都必须相等,若符合则返回首个属性,否则报错;
  • + *
  • 若有且仅有一个属性具有非默认值,则返回该属性;
  • + *
  • 若有多个属性具有非默认值,则要求所有的非默认值都必须相等,若符合并返回该首个具有非默认值的属性,否则报错;
  • + *
+ */ + private int resolve() { + int resolvedIndex = NOT_FOUND_INDEX; + boolean hasNotDef = false; + Object lastValue = null; + for (final int index : indexes) { + final Method attribute = attributes[index]; + + // 获取属性的值,并确认是否为默认值 + final Object def = attribute.getDefaultValue(); + final Object undef = MethodUtil.invoke(annotation, attribute); + final boolean isDefault = Objects.equals(def, undef); + + // 若是首个属性 + if (resolvedIndex == NOT_FOUND_INDEX) { + resolvedIndex = index; + lastValue = isDefault ? def : undef; + hasNotDef = !isDefault; + continue; + } + + // 不是首个属性,且已存在非默认值 + if (hasNotDef) { + // 如果当前也是非默认值,则要求两值必须相等 + if (!isDefault) { + Assert.isTrue( + Objects.equals(lastValue, undef), + "aliased attribute [{}] and [{}] must have same not default value, but is different: [{}] <==> [{}]", + attributes[resolvedIndex], attribute, lastValue, undef + ); + } + // 否则直接跳过,依然以上一非默认值为准 + continue; + } + + // 不是首个属性,但是还没有非默认值,而当前值恰好是非默认值,直接更新当前有效值与对应索引 + if (!isDefault) { + hasNotDef = true; + lastValue = undef; + resolvedIndex = index; + continue; + } + + // 不是首个属性,还没有非默认值,如果当前也是默认值,则要求两值必须相等 + Assert.isTrue( + Objects.equals(lastValue, def), + "aliased attribute [{}] and [{}] must have same default value, but is different: [{}] <==> [{}]", + attributes[resolvedIndex], attribute, lastValue, def + ); + } + Assert.isFalse(resolvedIndex == NOT_FOUND_INDEX, "can not resolve aliased attributes from [{}]", annotation); + return resolvedIndex; + } + + /** + * 遍历下标 + */ + void forEach(IntConsumer consumer) { + for (int index : indexes) { + consumer.accept(index); + } + } + + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/GenericAnnotationMappingTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/GenericAnnotationMappingTest.java new file mode 100644 index 000000000..011cf1419 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/GenericAnnotationMappingTest.java @@ -0,0 +1,120 @@ +package cn.hutool.core.annotation; + +import lombok.SneakyThrows; +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; + +/** + * test for {@link GenericAnnotationMapping} + * + * @author huangchengxing + */ +public class GenericAnnotationMappingTest { + + @Test + public void testEquals() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + GenericAnnotationMapping mapping = GenericAnnotationMapping.create(annotation, false); + Assert.assertEquals(mapping, mapping); + Assert.assertNotEquals(mapping, null); + Assert.assertEquals(mapping, GenericAnnotationMapping.create(annotation, false)); + Assert.assertNotEquals(mapping, GenericAnnotationMapping.create(annotation, true)); + } + + @Test + public void testHashCode() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + int hashCode = GenericAnnotationMapping.create(annotation, false).hashCode(); + Assert.assertEquals(hashCode, GenericAnnotationMapping.create(annotation, false).hashCode()); + Assert.assertNotEquals(hashCode, GenericAnnotationMapping.create(annotation, true).hashCode()); + } + + + @Test + public void testCreate() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + GenericAnnotationMapping mapping = GenericAnnotationMapping.create(annotation, false); + Assert.assertNotNull(mapping); + } + + @Test + public void testIsRoot() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + GenericAnnotationMapping mapping = GenericAnnotationMapping.create(annotation, true); + Assert.assertTrue(mapping.isRoot()); + + mapping = GenericAnnotationMapping.create(annotation, false); + Assert.assertFalse(mapping.isRoot()); + } + + @Test + public void testGetAnnotation() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + GenericAnnotationMapping mapping = GenericAnnotationMapping.create(annotation, false); + Assert.assertSame(annotation, mapping.getAnnotation()); + } + + @SneakyThrows + @Test + public void testGetAttributes() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + GenericAnnotationMapping mapping = GenericAnnotationMapping.create(annotation, false); + for (int i = 0; i < mapping.getAttributes().length; i++) { + Method method = mapping.getAttributes()[i]; + Assert.assertEquals(Annotation1.class.getDeclaredMethod(method.getName()), method); + } + } + + @Test + public void testAnnotationType() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + GenericAnnotationMapping mapping = GenericAnnotationMapping.create(annotation, false); + Assert.assertEquals(annotation.annotationType(), mapping.annotationType()); + } + + @Test + public void testIsResolved() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + GenericAnnotationMapping mapping = GenericAnnotationMapping.create(annotation, false); + Assert.assertFalse(mapping.isResolved()); + } + + @Test + public void testGetAttributeValue() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + GenericAnnotationMapping mapping = GenericAnnotationMapping.create(annotation, false); + Assert.assertEquals(annotation.value(), mapping.getAttributeValue("value", String.class)); + Assert.assertNull(mapping.getAttributeValue("value", Integer.class)); + } + + @Test + public void testGetResolvedAnnotation() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + GenericAnnotationMapping mapping = GenericAnnotationMapping.create(annotation, false); + Assert.assertSame(annotation, mapping.getResolvedAnnotation()); + } + + @Test + public void testGetResolvedAttributeValue() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + GenericAnnotationMapping mapping = GenericAnnotationMapping.create(annotation, false); + Assert.assertEquals(annotation.value(), mapping.getResolvedAttributeValue("value", String.class)); + Assert.assertNull(mapping.getResolvedAttributeValue("value", Integer.class)); + } + + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation1 { + String value() default ""; + } + + @Annotation1("foo") + private static class Foo {}; + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/ResolvedAnnotationMappingTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/ResolvedAnnotationMappingTest.java new file mode 100644 index 000000000..adb56f25e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/ResolvedAnnotationMappingTest.java @@ -0,0 +1,300 @@ +package cn.hutool.core.annotation; + +import lombok.SneakyThrows; +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; + +/** + * test for {@link ResolvedAnnotationMapping} + * + * @author huangchengxing + */ +public class ResolvedAnnotationMappingTest { + + @Test + public void testEquals() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + ResolvedAnnotationMapping mapping = ResolvedAnnotationMapping.create(annotation, false); + Assert.assertEquals(mapping, mapping); + Assert.assertNotEquals(null, mapping); + Assert.assertEquals(mapping, ResolvedAnnotationMapping.create(annotation, false)); + Assert.assertNotEquals(mapping, ResolvedAnnotationMapping.create(annotation, true)); + + // Annotation3没有需要解析的属性,因此即使在构造函数指定false也一样 + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + Assert.assertEquals( + ResolvedAnnotationMapping.create(annotation3, false), + ResolvedAnnotationMapping.create(annotation3, true) + ); + } + + @Test + public void testHashCode() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + int hashCode = ResolvedAnnotationMapping.create(annotation, false).hashCode(); + Assert.assertEquals(hashCode, ResolvedAnnotationMapping.create(annotation, false).hashCode()); + Assert.assertNotEquals(hashCode, ResolvedAnnotationMapping.create(annotation, true).hashCode()); + + // Annotation3没有需要解析的属性,因此即使在构造函数指定false也一样 + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + Assert.assertEquals( + ResolvedAnnotationMapping.create(annotation3, false).hashCode(), + ResolvedAnnotationMapping.create(annotation3, true).hashCode() + ); + } + + + @Test + public void testCreate() { + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + ResolvedAnnotationMapping mapping3 = ResolvedAnnotationMapping.create(annotation3, false); + Assert.assertNotNull(mapping3); + + Annotation2 annotation2 = Foo.class.getAnnotation(Annotation2.class); + ResolvedAnnotationMapping mapping2 = ResolvedAnnotationMapping.create(mapping3, annotation2, false); + Assert.assertNotNull(mapping2); + + Annotation1 annotation1 = Foo.class.getAnnotation(Annotation1.class); + ResolvedAnnotationMapping mapping1 = ResolvedAnnotationMapping.create(mapping2, annotation1, false); + Assert.assertNotNull(mapping1); + } + + @Test + public void testIsRoot() { + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + ResolvedAnnotationMapping mapping3 = ResolvedAnnotationMapping.create(annotation3, false); + Assert.assertTrue(mapping3.isRoot()); + + Annotation2 annotation2 = Foo.class.getAnnotation(Annotation2.class); + ResolvedAnnotationMapping mapping2 = ResolvedAnnotationMapping.create(mapping3, annotation2, false); + Assert.assertFalse(mapping2.isRoot()); + } + + @Test + public void testGetRoot() { + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + ResolvedAnnotationMapping mapping3 = ResolvedAnnotationMapping.create(annotation3, false); + Assert.assertSame(mapping3, mapping3.getRoot()); + + Annotation2 annotation2 = Foo.class.getAnnotation(Annotation2.class); + ResolvedAnnotationMapping mapping2 = ResolvedAnnotationMapping.create(mapping3, annotation2, false); + Assert.assertSame(mapping3, mapping2.getRoot()); + + Annotation1 annotation1 = Foo.class.getAnnotation(Annotation1.class); + ResolvedAnnotationMapping mapping1 = ResolvedAnnotationMapping.create(mapping2, annotation1, false); + Assert.assertSame(mapping3, mapping1.getRoot()); + } + + @Test + public void testGetAnnotation() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + ResolvedAnnotationMapping mapping = ResolvedAnnotationMapping.create(annotation, false); + Assert.assertSame(annotation, mapping.getAnnotation()); + } + + @SneakyThrows + @Test + public void testGetAttributes() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + ResolvedAnnotationMapping mapping = ResolvedAnnotationMapping.create(annotation, false); + for (int i = 0; i < mapping.getAttributes().length; i++) { + Method method = mapping.getAttributes()[i]; + Assert.assertEquals(Annotation1.class.getDeclaredMethod(method.getName()), method); + } + } + + @Test + public void testHasAttribute() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + ResolvedAnnotationMapping mapping = ResolvedAnnotationMapping.create(annotation, false); + + Assert.assertTrue(mapping.hasAttribute("value", String.class)); + Assert.assertFalse(mapping.hasAttribute("value", Integer.class)); + + int index = mapping.getAttributeIndex("value", String.class); + Assert.assertTrue(mapping.hasAttribute(index)); + Assert.assertFalse(mapping.hasAttribute(Integer.MIN_VALUE)); + } + + @Test + public void testAnnotationType() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + ResolvedAnnotationMapping mapping = ResolvedAnnotationMapping.create(annotation, false); + Assert.assertEquals(annotation.annotationType(), mapping.annotationType()); + } + + @Test + public void testIsResolved() { + Annotation1 annotation1 = Foo.class.getAnnotation(Annotation1.class); + + ResolvedAnnotationMapping mapping1 = ResolvedAnnotationMapping.create(annotation1, true); + Assert.assertTrue(mapping1.isResolved()); + Assert.assertFalse(ResolvedAnnotationMapping.create(annotation1, false).isResolved()); + + Annotation2 annotation2 = Foo.class.getAnnotation(Annotation2.class); + ResolvedAnnotationMapping mapping2 = ResolvedAnnotationMapping.create(annotation2, true); + Assert.assertFalse(mapping2.isResolved()); + + mapping2 = ResolvedAnnotationMapping.create(mapping1, annotation2, true); + Assert.assertTrue(mapping2.isResolved()); + } + + @Test + public void testGetAttributeIndex() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + ResolvedAnnotationMapping mapping = ResolvedAnnotationMapping.create(annotation, false); + for (int i = 0; i < mapping.getAttributes().length; i++) { + Method method = mapping.getAttributes()[i]; + Assert.assertEquals(i, mapping.getAttributeIndex(method.getName(), method.getReturnType())); + } + Assert.assertEquals(ResolvedAnnotationMapping.NOT_FOUND_INDEX, mapping.getAttributeIndex("value", Void.class)); + Assert.assertEquals(ResolvedAnnotationMapping.NOT_FOUND_INDEX, mapping.getAttributeIndex("nonexistent", Void.class)); + } + + @Test + public void testGetAttributeValue() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + ResolvedAnnotationMapping mapping = ResolvedAnnotationMapping.create(annotation, false); + + Assert.assertNull(mapping.getAttribute(Integer.MAX_VALUE)); + + int valueIdx = mapping.getAttributeIndex("value", String.class); + Assert.assertEquals(annotation.value(), mapping.getAttributeValue(valueIdx)); + Assert.assertEquals(annotation.value(), mapping.getAttributeValue("value", String.class)); + + int name1Idx = mapping.getAttributeIndex("value1", String.class); + Assert.assertEquals(annotation.value1(), mapping.getAttributeValue(name1Idx)); + Assert.assertEquals(annotation.value1(), mapping.getAttributeValue("value1", String.class)); + + int name2Idx = mapping.getAttributeIndex("value2", String.class); + Assert.assertEquals(annotation.value2(), mapping.getAttributeValue(name2Idx)); + Assert.assertEquals(annotation.value2(), mapping.getAttributeValue("value2", String.class)); + } + + @Test + public void testGetResolvedAnnotation() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + ResolvedAnnotationMapping mapping = ResolvedAnnotationMapping.create(annotation, true); + Annotation1 synthesis = (Annotation1)mapping.getResolvedAnnotation(); + + Assert.assertEquals(annotation.annotationType(), synthesis.annotationType()); + Assert.assertEquals(annotation.value(), synthesis.value()); + Assert.assertEquals(annotation.value(), synthesis.value1()); + Assert.assertEquals(annotation.value(), synthesis.value2()); + + Assert.assertTrue(AnnotationMappingProxy.isProxied(synthesis)); + Assert.assertSame(mapping, ((AnnotationMappingProxy.Proxied)synthesis).getMapping()); + + Assert.assertNotEquals(synthesis, annotation); + Assert.assertNotEquals(synthesis.hashCode(), annotation.hashCode()); + Assert.assertNotEquals(synthesis.toString(), annotation.toString()); + + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + Assert.assertSame(annotation3, ResolvedAnnotationMapping.create(annotation3, true).getResolvedAnnotation()); + } + + // ======================= resolved attribute value ======================= + + @Test + public void testGetResolvedAttributeValueWhenAliased() { + Annotation1 annotation = Foo.class.getAnnotation(Annotation1.class); + ResolvedAnnotationMapping mapping = ResolvedAnnotationMapping.create(annotation, true); + Assert.assertNull(mapping.getResolvedAttributeValue(Integer.MIN_VALUE)); + + // value = value1 = value2 + Assert.assertEquals(annotation.value(), mapping.getResolvedAttributeValue("value", String.class)); + Assert.assertEquals(annotation.value(), mapping.getResolvedAttributeValue("value1", String.class)); + Assert.assertEquals(annotation.value(), mapping.getResolvedAttributeValue("value2", String.class)); + + // alias == alias1 == alias2 + Assert.assertEquals(annotation.alias(), mapping.getResolvedAttributeValue("alias", String.class)); + Assert.assertEquals(annotation.alias(), mapping.getResolvedAttributeValue("alias1", String.class)); + Assert.assertEquals(annotation.alias(), mapping.getResolvedAttributeValue("alias2", String.class)); + + // defVal1 == defVal2 + Assert.assertEquals( + mapping.getResolvedAttributeValue("defVal", String.class), + mapping.getResolvedAttributeValue("defVal2", String.class) + ); + + // unDefVal1 == unDefVal2 + Assert.assertEquals( + mapping.getResolvedAttributeValue("unDefVal", String.class), + mapping.getResolvedAttributeValue("unDefVal2", String.class) + ); + } + + @Test + public void testGetResolvedAttributeWhenOverwritten() { + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + ResolvedAnnotationMapping mapping3 = ResolvedAnnotationMapping.create(annotation3, true); + Assert.assertEquals(annotation3.value(), mapping3.getResolvedAttributeValue("value", String.class)); + Assert.assertEquals((Integer)annotation3.alias(), mapping3.getResolvedAttributeValue("alias", Integer.class)); + + // annotation2中与annotation3同名同类型的属性value、alias被覆写 + Annotation2 annotation2 = Foo.class.getAnnotation(Annotation2.class); + ResolvedAnnotationMapping mapping2 = ResolvedAnnotationMapping.create(mapping3, annotation2, true); + Assert.assertEquals(annotation3.value(), mapping2.getResolvedAttributeValue("value", String.class)); + Assert.assertEquals((Integer)annotation3.alias(), mapping2.getResolvedAttributeValue("alias", Integer.class)); + + // annotation1中与annotation3同名同类型的属性value被覆写,由于value存在别名value1,value2因此也一并被覆写 + Annotation1 annotation1 = Foo.class.getAnnotation(Annotation1.class); + ResolvedAnnotationMapping mapping1 = ResolvedAnnotationMapping.create(mapping2, annotation1, true); + Assert.assertEquals(annotation3.value(), mapping1.getResolvedAttributeValue("value", String.class)); + Assert.assertEquals(annotation3.value(), mapping1.getResolvedAttributeValue("value1", String.class)); + Assert.assertEquals(annotation3.value(), mapping1.getResolvedAttributeValue("value2", String.class)); + // 而alias由于类型不同不会被覆写 + Assert.assertEquals(annotation1.alias(), mapping1.getResolvedAttributeValue("alias", String.class)); + } + + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation1 { + @Alias("value1") + String value() default ""; + String value1() default ""; + @Alias("value") + String value2() default ""; + + @Alias("alias2") + String alias() default ""; + @Alias("alias2") + String alias1() default ""; + @Alias("alias1") + String alias2() default ""; + + @Alias("defVal2") + String defVal() default ""; + String defVal2() default ""; + + @Alias("unDefVal2") + String unDefVal() default ""; + String unDefVal2() default ""; + } + + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation2 { + String value() default ""; + int alias() default 123; + } + + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation3 { + String value() default ""; + int alias() default 123; + } + + @Annotation3(value = "Annotation3", alias = 312) + @Annotation2(value = "Annotation2") + @Annotation1(value = "Annotation1", alias = "goo", unDefVal = "foo", unDefVal2 = "foo") + private static class Foo {}; + +} From 77e065f302480d56fcc63f0917c411d8d2a4a4ad Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Tue, 13 Sep 2022 13:57:46 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=A4=84=E7=90=86=E5=85=83=E6=B3=A8=E8=A7=A3=E7=9A=84=E5=A2=9E?= =?UTF-8?q?=E5=BC=BAAnnotatedElement=E5=8C=85=E8=A3=85=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/annotation/MetaAnnotatedElement.java | 322 ++++++++++++++++++ .../annotation/MetaAnnotatedElementTest.java | 208 +++++++++++ 2 files changed, 530 insertions(+) create mode 100644 hutool-core/src/main/java/cn/hutool/core/annotation/MetaAnnotatedElement.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/annotation/MetaAnnotatedElementTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/MetaAnnotatedElement.java b/hutool-core/src/main/java/cn/hutool/core/annotation/MetaAnnotatedElement.java new file mode 100644 index 000000000..26e0567cc --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/MetaAnnotatedElement.java @@ -0,0 +1,322 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.stream.EasyStream; +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjUtil; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Inherited; +import java.lang.reflect.AnnotatedElement; +import java.util.*; +import java.util.function.BiFunction; +import java.util.stream.Stream; + +/** + *

注解元素映射,用于包装一个{@link AnnotatedElement},然后将被包装的元素上, + * 直接声明的注解以及这些注解的元组全部解析为{@link ResolvedAnnotationMapping}。 + * 从而用于支持对元注解的访问操作。
+ * + *

默认情况下,总是不扫描{@link java.lang}包下的注解, + * 并且在当前实例中,{@link Inherited}注解将不生效, + * 即通过directly方法将无法获得父类上带有{@link Inherited}的注解。 + * + *

当通过静态工厂方法创建时,该实例与关联的{@link ResolvedAnnotationMapping}都会针对{@link ResolvedAnnotationMapping}进行缓存, + * 从而避免频繁的反射与代理造成不必要的性能损耗。 + * + * @author huangchengxing + * @see ResolvedAnnotationMapping + * @since 6.0.0 + */ +public class MetaAnnotatedElement> implements AnnotatedElement, Iterable { + + /** + * 注解对象 + */ + private final AnnotatedElement element; + + /** + * 创建{@link AnnotationMapping}的工厂方法,返回值为{@code null}时将忽略该注解 + */ + private final BiFunction mappingFactory; + + /** + * 注解映射,此处为懒加载,默认为{@code null},获取该属性必须通过{@link #getAnnotationMappings()}触发初始化 + */ + private volatile Map, T> annotationMappings; + + /** + * 获取{@link AnnotatedElement}上的注解结构,该方法会针对相同的{@link AnnotatedElement}缓存映射对象 + * + * @param element 被注解元素 + * @param mappingFactory 创建{@link AnnotationMapping}的工厂方法,返回值为{@code null}时将忽略该注解 + * @param {@link AnnotationMapping}类型 + * @return {@link AnnotatedElement}上的注解结构 + */ + public static > MetaAnnotatedElement create( + final AnnotatedElement element, final BiFunction mappingFactory) { + return new MetaAnnotatedElement<>(element, mappingFactory); + } + + /** + * 解析注解属性 + * + * @param element 被注解元素 + * @param mappingFactory 创建{@link AnnotationMapping}的工厂方法,返回值为{@code null}时将忽略该注解 + */ + MetaAnnotatedElement(final AnnotatedElement element, final BiFunction mappingFactory) { + this.element = Objects.requireNonNull(element); + this.mappingFactory = Objects.requireNonNull(mappingFactory); + // 等待懒加载 + this.annotationMappings = null; + } + + /** + * 从{@link AnnotatedElement}直接声明的注解的层级结构中获得注解映射对象 + * + * @param annotationType 注解类型 + * @return 注解映射对象 + */ + public Optional getMapping(final Class annotationType) { + return Optional.ofNullable(annotationType) + .map(getAnnotationMappings()::get); + } + + /** + * 获取被包装的{@link AnnotatedElement} + * + * @return 被包装的{@link AnnotatedElement} + */ + public AnnotatedElement getElement() { + return element; + } + + /** + * 从{@link AnnotatedElement}直接声明的注解中获得注解映射对象 + * + * @param annotationType 注解类型 + * @return 注解映射对象 + */ + public Optional getDeclaredMapping(final Class annotationType) { + return EasyStream.of(getAnnotationMappings().values()) + .filter(T::isRoot) + .findFirst(mapping -> ObjUtil.equals(annotationType, mapping.annotationType())); + } + + /** + * 注解是否是{@link AnnotatedElement}直接声明的注解,或者在这些注解的层级结构中存在 + * + * @param annotationType 注解元素 + * @return 是否 + */ + @Override + public boolean isAnnotationPresent(Class annotationType) { + return getMapping(annotationType) + .isPresent(); + } + + /** + * 从{@link AnnotatedElement}直接声明的注解的层级结构中获得注解对象 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + @Override + public A getAnnotation(final Class annotationType) { + return getMapping(annotationType) + .map(T::getResolvedAnnotation) + .map(annotationType::cast) + .orElse(null); + } + + /** + * 从{@link AnnotatedElement}直接声明的注解中获得注解对象 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + @Override + public A getDeclaredAnnotation(final Class annotationType) { + return getDeclaredMapping(annotationType) + .map(T::getResolvedAnnotation) + .map(annotationType::cast) + .orElse(null); + } + + /** + * 获取{@link AnnotatedElement}直接的指定类型注解 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return {@link AnnotatedElement}直接声明的指定类型注解 + */ + @SuppressWarnings("unchecked") + @Override + public A[] getAnnotationsByType(final Class annotationType) { + final A result = getAnnotation(annotationType); + if (Objects.nonNull(result)) { + return (A[])new Annotation[]{ result }; + } + return ArrayUtil.newArray(annotationType, 0); + } + + /** + * 获取{@link AnnotatedElement}直接声明的指定类型注解 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return {@link AnnotatedElement}直接声明的指定类型注解 + */ + @SuppressWarnings("unchecked") + @Override + public A[] getDeclaredAnnotationsByType(final Class annotationType) { + final A result = getDeclaredAnnotation(annotationType); + if (Objects.nonNull(result)) { + return (A[])new Annotation[]{ result }; + } + return ArrayUtil.newArray(annotationType, 0); + } + + /** + * 获取{@link AnnotatedElement}直接声明的注解的映射对象 + * + * @return {@link AnnotatedElement}直接声明的注解的映射对象 + */ + @Override + public Annotation[] getDeclaredAnnotations() { + return getAnnotationMappings().values().stream() + .filter(T::isRoot) + .map(T::getResolvedAnnotation) + .toArray(Annotation[]::new); + } + + /** + * 获取所有注解 + * + * @return 所有注解 + */ + @Override + public Annotation[] getAnnotations() { + return getAnnotationMappings().values().stream() + .map(T::getResolvedAnnotation) + .toArray(Annotation[]::new); + } + + /** + * 获取注解映射对象集合的迭代器 + * + * @return 迭代器 + */ + @Override + public Iterator iterator() { + return getAnnotationMappings().values().iterator(); + } + + // ========================= protected ========================= + + /** + * 获取注解映射,若当前实例未完成初始化则先进行初始化 + * + * @return 不可变的注解映射集合 + */ + protected final Map, T> getAnnotationMappings() { + initAnnotationMappingsIfNecessary(); + return annotationMappings; + } + + /** + * 该注解是否需要映射
+ * 默认情况下,已经处理过、或在{@link java.lang}包下的注解不会被处理 + * + * @param mappings 当前已处理的注解 + * @param annotation 注解对象 + * @return 是否 + */ + protected boolean isNeedMapping(final Map, T> mappings, final Annotation annotation) { + return !CharSequenceUtil.startWith(annotation.annotationType().getName(), "java.lang.") + && !mappings.containsKey(annotation.annotationType()); + } + + // ========================= private ========================= + + /** + * 创建注解映射 + */ + private T createMapping(final T source, final Annotation annotation) { + return mappingFactory.apply(source, annotation); + } + + /** + * 扫描{@link AnnotatedElement}上直接声明的注解,然后按广度优先扫描这些注解的元注解, + * 直到将所有类型的注解对象皆加入{@link #annotationMappings}为止 + */ + private void initAnnotationMappingsIfNecessary() { + // 双重检查保证初始化过程线程安全 + if (Objects.isNull(annotationMappings)) { + synchronized (this) { + if (Objects.isNull(annotationMappings)) { + Map, T> mappings = new LinkedHashMap<>(8); + initAnnotationMappings(mappings); + this.annotationMappings = Collections.unmodifiableMap(mappings); + } + } + } + } + + /** + * 初始化 + */ + private void initAnnotationMappings(final Map, T> mappings) { + final Deque deque = new LinkedList<>(); + Arrays.stream(element.getDeclaredAnnotations()) + .filter(m -> isNeedMapping(mappings, m)) + .map(annotation -> createMapping(null, annotation)) + .filter(Objects::nonNull) + .forEach(deque::addLast); + while (!deque.isEmpty()) { + // 若已有该类型的注解,则不再进行扫描 + T mapping = deque.removeFirst(); + if (!isNeedMapping(mappings, mapping)) { + continue; + } + // 保存该注解,并将其需要处理的元注解也加入队列 + mappings.put(mapping.annotationType(), mapping); + Stream.of(mapping.annotationType().getDeclaredAnnotations()) + .map(annotation -> createMapping(mapping, annotation)) + .filter(Objects::nonNull) + .filter(m -> isNeedMapping(mappings, m)) + .forEach(deque::addLast); + } + } + + /** + * 比较两个实例是否相等 + * + * @param o 对象 + * @return 是否 + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MetaAnnotatedElement that = (MetaAnnotatedElement)o; + return element.equals(that.element) && mappingFactory.equals(that.mappingFactory); + } + + /** + * 获取实例的哈希值 + * + * @return 哈希值 + */ + + @Override + public int hashCode() { + return Objects.hash(element, mappingFactory); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/MetaAnnotatedElementTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/MetaAnnotatedElementTest.java new file mode 100644 index 000000000..fd8a2f3cc --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/MetaAnnotatedElementTest.java @@ -0,0 +1,208 @@ +package cn.hutool.core.annotation; + +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.*; +import java.lang.reflect.AnnotatedElement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.function.BiFunction; + +/** + * test for {@link MetaAnnotatedElement} + * + * @author huangchengxing + */ +public class MetaAnnotatedElementTest { + + private static final BiFunction RESOLVED_MAPPING_FACTORY = + (source, annotation) -> new ResolvedAnnotationMapping(source, annotation, true); + + private static final BiFunction MAPPING_FACTORY = + (source, annotation) -> new ResolvedAnnotationMapping(source, annotation, false); + + @Test + public void testEquals() { + AnnotatedElement element = new MetaAnnotatedElement<>(Foo.class, RESOLVED_MAPPING_FACTORY); + Assert.assertEquals(element, element); + Assert.assertNotEquals(element, null); + Assert.assertEquals(element, new MetaAnnotatedElement<>(Foo.class, RESOLVED_MAPPING_FACTORY)); + Assert.assertNotEquals(element, new MetaAnnotatedElement<>(Foo.class, MAPPING_FACTORY)); + Assert.assertNotEquals(element, new MetaAnnotatedElement<>(Annotation1.class, MAPPING_FACTORY)); + } + + @Test + public void testHashCode() { + int hashCode = new MetaAnnotatedElement<>(Foo.class, RESOLVED_MAPPING_FACTORY).hashCode(); + Assert.assertEquals(hashCode, new MetaAnnotatedElement<>(Foo.class, RESOLVED_MAPPING_FACTORY).hashCode()); + Assert.assertNotEquals(hashCode, new MetaAnnotatedElement<>(Foo.class, MAPPING_FACTORY).hashCode()); + } + + @Test + public void testCreate() { + // 第二次创建时优先从缓存中获取 + AnnotatedElement resolvedElement = MetaAnnotatedElement.create(Foo.class, RESOLVED_MAPPING_FACTORY); + Assert.assertEquals(resolvedElement, MetaAnnotatedElement.create(Foo.class, RESOLVED_MAPPING_FACTORY)); + AnnotatedElement element = MetaAnnotatedElement.create(Foo.class, MAPPING_FACTORY); + Assert.assertEquals(element, MetaAnnotatedElement.create(Foo.class, MAPPING_FACTORY)); + } + + @Test + public void testGetMapping() { + MetaAnnotatedElement element = MetaAnnotatedElement.create(Foo.class, MAPPING_FACTORY); + Assert.assertTrue(element.getMapping(Annotation1.class).isPresent()); + Assert.assertTrue(element.getMapping(Annotation2.class).isPresent()); + Assert.assertTrue(element.getMapping(Annotation3.class).isPresent()); + Assert.assertTrue(element.getMapping(Annotation4.class).isPresent()); + } + + @Test + public void testGetDeclaredMapping() { + MetaAnnotatedElement element = MetaAnnotatedElement.create(Foo.class, MAPPING_FACTORY); + Assert.assertFalse(element.getDeclaredMapping(Annotation1.class).isPresent()); + Assert.assertFalse(element.getDeclaredMapping(Annotation2.class).isPresent()); + Assert.assertTrue(element.getDeclaredMapping(Annotation3.class).isPresent()); + Assert.assertTrue(element.getDeclaredMapping(Annotation4.class).isPresent()); + } + + @Test + public void testIsAnnotationPresent() { + MetaAnnotatedElement element = MetaAnnotatedElement.create(Foo.class, MAPPING_FACTORY); + Assert.assertTrue(element.isAnnotationPresent(Annotation1.class)); + Assert.assertTrue(element.isAnnotationPresent(Annotation2.class)); + Assert.assertTrue(element.isAnnotationPresent(Annotation3.class)); + Assert.assertTrue(element.isAnnotationPresent(Annotation4.class)); + } + + @Test + public void testGetAnnotation() { + MetaAnnotatedElement element = MetaAnnotatedElement.create(Foo.class, MAPPING_FACTORY); + Annotation4 annotation4 = Foo.class.getAnnotation(Annotation4.class); + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + Annotation2 annotation2 = Annotation3.class.getAnnotation(Annotation2.class); + Annotation1 annotation1 = Annotation2.class.getAnnotation(Annotation1.class); + + Assert.assertEquals(annotation1, element.getAnnotation(Annotation1.class)); + Assert.assertEquals(annotation2, element.getAnnotation(Annotation2.class)); + Assert.assertEquals(annotation3, element.getAnnotation(Annotation3.class)); + Assert.assertEquals(annotation4, element.getAnnotation(Annotation4.class)); + } + + @Test + public void testGetDeclaredAnnotation() { + MetaAnnotatedElement element = MetaAnnotatedElement.create(Foo.class, MAPPING_FACTORY); + Annotation4 annotation4 = Foo.class.getAnnotation(Annotation4.class); + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + + Assert.assertNull(element.getDeclaredAnnotation(Annotation1.class)); + Assert.assertNull(element.getDeclaredAnnotation(Annotation2.class)); + Assert.assertEquals(annotation3, element.getDeclaredAnnotation(Annotation3.class)); + Assert.assertEquals(annotation4, element.getDeclaredAnnotation(Annotation4.class)); + } + + @Test + public void testGetAnnotationByType() { + MetaAnnotatedElement element = MetaAnnotatedElement.create(Foo.class, MAPPING_FACTORY); + Annotation4 annotation4 = Foo.class.getAnnotation(Annotation4.class); + Assert.assertArrayEquals( + new Annotation[]{ annotation4 }, + element.getAnnotationsByType(Annotation4.class) + ); + } + + @Test + public void testGetDeclaredAnnotationByType() { + MetaAnnotatedElement element = MetaAnnotatedElement.create(Foo.class, MAPPING_FACTORY); + Annotation4 annotation4 = Foo.class.getAnnotation(Annotation4.class); + Assert.assertArrayEquals( + new Annotation[]{ annotation4 }, + element.getDeclaredAnnotationsByType(Annotation4.class) + ); + } + + @Test + public void testGetAnnotations() { + MetaAnnotatedElement element = MetaAnnotatedElement.create(Foo.class, MAPPING_FACTORY); + Annotation4 annotation4 = Foo.class.getAnnotation(Annotation4.class); + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + Annotation2 annotation2 = Annotation3.class.getAnnotation(Annotation2.class); + Annotation1 annotation1 = Annotation2.class.getAnnotation(Annotation1.class); + Annotation[] annotations = new Annotation[]{ annotation3, annotation4, annotation2, annotation1 }; + + Assert.assertArrayEquals(annotations, element.getAnnotations()); + } + + @Test + public void testGetDeclaredAnnotations() { + MetaAnnotatedElement element = MetaAnnotatedElement.create(Foo.class, MAPPING_FACTORY); + Annotation4 annotation4 = Foo.class.getAnnotation(Annotation4.class); + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + Annotation[] annotations = new Annotation[]{ annotation3, annotation4 }; + + Assert.assertArrayEquals(annotations, element.getDeclaredAnnotations()); + } + + @Test + public void testIterator() { + MetaAnnotatedElement element = MetaAnnotatedElement.create(Foo.class, MAPPING_FACTORY); + Annotation4 annotation4 = Foo.class.getAnnotation(Annotation4.class); + Annotation3 annotation3 = Foo.class.getAnnotation(Annotation3.class); + Annotation2 annotation2 = Annotation3.class.getAnnotation(Annotation2.class); + Annotation1 annotation1 = Annotation2.class.getAnnotation(Annotation1.class); + Annotation[] annotations = new Annotation[]{ annotation3, annotation4, annotation2, annotation1 }; + + Iterator iterator = element.iterator(); + List mappingList = new ArrayList<>(); + iterator.forEachRemaining(mapping -> mappingList.add(mapping.getAnnotation())); + + Assert.assertEquals(Arrays.asList(annotations), mappingList); + } + + @Test + public void testGetElement() { + AnnotatedElement source = Foo.class; + MetaAnnotatedElement element = MetaAnnotatedElement.create(source, MAPPING_FACTORY); + Assert.assertSame(source, element.getElement()); + } + + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation1 { + @Alias("name") + String value() default ""; + @Alias("value") + String name() default ""; + } + + @Annotation1("Annotation2") + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation2 { + @Alias("name") + String value() default ""; + @Alias("value") + String name() default ""; + } + + @Annotation2("Annotation3") + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation3 { + String name() default ""; + } + + @Annotation2("Annotation4") + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation4 { + String value() default ""; + } + + @Annotation3(name = "foo") + @Annotation4("foo") + private static class Foo {}; + +} From 4b38bc31d2568ebf83ecc10b9a753f0cfc38b48f Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Tue, 13 Sep 2022 13:58:24 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=A4=84=E7=90=86=E5=B1=82=E7=BA=A7=E7=BB=93=E6=9E=84=E7=9A=84?= =?UTF-8?q?=E5=A2=9E=E5=BC=BAAnnotatedElement=E5=8C=85=E8=A3=85=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HierarchicalAnnotatedElements.java | 388 ++++++++++++++++++ .../HierarchicalAnnotatedElementTest.java | 208 ++++++++++ 2 files changed, 596 insertions(+) create mode 100644 hutool-core/src/main/java/cn/hutool/core/annotation/HierarchicalAnnotatedElements.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/annotation/HierarchicalAnnotatedElementTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/HierarchicalAnnotatedElements.java b/hutool-core/src/main/java/cn/hutool/core/annotation/HierarchicalAnnotatedElements.java new file mode 100644 index 000000000..7959164bc --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/HierarchicalAnnotatedElements.java @@ -0,0 +1,388 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.reflect.ClassUtil; +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.ArrayUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.function.BiFunction; +import java.util.stream.Stream; + +/** + *

表示一组处于在层级结构中具有关联关系的{@link AnnotatedElement},创建实例时, + * 将扫描指定{@link AnnotatedElement}的层级结构中的所有{@link AnnotatedElement}, + * 并将其包装为{@link MetaAnnotatedElement}。
+ * eg:
+ * 若存在元素A有对应父类与父接口BC, + * 则根据A生成的{@link HierarchicalAnnotatedElements}实例将同时包含ABC, + * 该实例同时支持对这三个实例上直接声明的注解,以及这些注解的元注解进行访问。 + * + *

注解搜索范围 + *

在当前实例中,针对带有和不带declared关键字的方法定义如下: + *

+ * + *

扫描顺序 + *

当{@link AnnotatedElement}具有层级结构式,会按照广度优先扫描其本身(元素是{@link Class})、 + * 或其声明类(元素是{@link Method})的层级结构。
+ * 在该过程中,总是先扫描父类,再扫描父接口, + * 若存在多个父接口,则其扫描顺序遵循从{@link Class#getInterfaces()}获得该接口的顺序。 + * + * @author huangchengxing + * @since 6.0.0 + */ +public class HierarchicalAnnotatedElements implements AnnotatedElement, Iterable { + + /** + * 创建{@link AnnotatedElement}的工厂方法,当返回{@code null}时将忽略该元素 + */ + protected final BiFunction, AnnotatedElement, AnnotatedElement> elementFactory; + + /** + * 层级中的全部{@link AnnotatedElement}对象,默认为懒加载,需要通过{@link #getElementMappings()}触发初始化
+ * 该集合中的元素按照其与被包装的{@link AnnotatedElement}的距离和被按广度优先扫描的顺序排序 + */ + private volatile Set elementMappings; + + /** + * 被包装的{@link AnnotatedElement}对象 + */ + protected final AnnotatedElement source; + + /** + * 创建一个分层注解元素 + * + * @param element 被包装的元素,若元素已是{@link HierarchicalAnnotatedElements},则返回其本身 + * @return {@link HierarchicalAnnotatedElements}实例, + * 当{@code element}也是一个{@link HierarchicalAnnotatedElements}时,返回{@code element}本身 + */ + public static HierarchicalAnnotatedElements create(final AnnotatedElement element) { + return create(element, (es, e) -> e); + } + + /** + * 创建一个分层注解元素 + * + * @param element 被包装的元素,若元素已是{@link HierarchicalAnnotatedElements},则返回其本身 + * @param elementFactory 创建{@link AnnotatedElement}的工厂方法,当返回{@code null}时将忽略该元素 + * @return {@link HierarchicalAnnotatedElements}实例, + * 当{@code element}也是一个{@link HierarchicalAnnotatedElements}时,返回{@code element}本身 + */ + public static HierarchicalAnnotatedElements create( + final AnnotatedElement element, + final BiFunction, AnnotatedElement, AnnotatedElement> elementFactory) { + return element instanceof HierarchicalAnnotatedElements ? + (HierarchicalAnnotatedElements)element : new HierarchicalAnnotatedElements(element, elementFactory); + } + + /** + * 构造 + * + * @param element 被包装的元素 + * @param elementFactory 创建{@link AnnotatedElement}的工厂方法,当返回{@code null}时将忽略该元素 + */ + HierarchicalAnnotatedElements( + final AnnotatedElement element, + final BiFunction, AnnotatedElement, AnnotatedElement> elementFactory) { + this.source = Objects.requireNonNull(element); + // 懒加载 + this.elementMappings = null; + this.elementFactory = Objects.requireNonNull(elementFactory); + } + + /** + * 注解是否在层级结构中所有{@link AnnotatedElement}上的注解和元注解中存在 + * + * @param annotationType 注解类型 + * @return 是否 + */ + @Override + public boolean isAnnotationPresent(final Class annotationType) { + return getElementMappings().stream() + .anyMatch(element -> element.isAnnotationPresent(annotationType)); + } + + /** + * 从层级结构中所有{@link AnnotatedElement}上的注解和元注解中获取指定类型的注解 + * + * @return 注解对象 + */ + @Override + public Annotation[] getAnnotations() { + return getElementMappings().stream() + .map(AnnotatedElement::getAnnotations) + .filter(ArrayUtil::isNotEmpty) + .flatMap(Stream::of) + .toArray(Annotation[]::new); + } + + /** + * 从层级结构中所有{@link AnnotatedElement}上的注解和元注解中获取指定类型的注解 + * + * @param annotationType 注解类型 + * @param
注解类型 + * @return 注解对象 + */ + @Override + public A getAnnotation(final Class annotationType) { + return getElementMappings().stream() + .map(e -> e.getAnnotation(annotationType)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + + /** + * 从层级结构中所有{@link AnnotatedElement}上的注解和元注解中获取指定类型的注解 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + @SuppressWarnings("unchecked") + @Override + public A[] getAnnotationsByType(final Class annotationType) { + return getElementMappings().stream() + .map(e -> e.getAnnotationsByType(annotationType)) + .filter(ArrayUtil::isNotEmpty) + .flatMap(Stream::of) + .toArray(size -> ArrayUtil.newArray(annotationType, size)); + } + + /** + * 获取层级结构中所有{@link AnnotatedElement}上直接声明的注解 + * + * @return 注解对象 + */ + @Override + public Annotation[] getDeclaredAnnotations() { + return getElementMappings().stream() + .map(AnnotatedElement::getDeclaredAnnotations) + .filter(ArrayUtil::isNotEmpty) + .flatMap(Stream::of) + .toArray(Annotation[]::new); + } + + /** + * 获取层级结构中所有{@link AnnotatedElement}上直接声明的指定类型注解 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + @Override + public A getDeclaredAnnotation(final Class annotationType) { + return getElementMappings().stream() + .map(element -> element.getDeclaredAnnotation(annotationType)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + + /** + * 获取层级结构中所有{@link AnnotatedElement}上直接声明的指定类型注解 + * + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + @SuppressWarnings("unchecked") + @Override + public A[] getDeclaredAnnotationsByType(final Class annotationType) { + return (A[]) getElementMappings().stream() + .map(element -> element.getDeclaredAnnotationsByType(annotationType)) + .filter(ArrayUtil::isNotEmpty) + .flatMap(Stream::of) + .toArray(size -> ArrayUtil.newArray(annotationType, size)); + } + + /** + * 获取注解元素映射集合的迭代器 + * + * @return 迭代器 + */ + @Override + public Iterator iterator() { + return getElementMappings().iterator(); + } + + /** + * 获取被包装的原始{@link AnnotatedElement}对象 + * + * @return 注解对象 + */ + public AnnotatedElement getElement() { + return source; + } + + /** + * 比较两个实例是否相等 + * + * @param o 对象 + * @return 是否 + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + HierarchicalAnnotatedElements that = (HierarchicalAnnotatedElements)o; + return elementFactory.equals(that.elementFactory) && source.equals(that.source); + } + + /** + * 获取实例的哈希值 + * + * @return 哈希值 + */ + @Override + public int hashCode() { + return Objects.hash(elementFactory, source); + } + + // ========================= protected ========================= + + /** + * 获取当前元素及层级结构中的关联元素的映射对象 + * + * @return 元素映射对象 + */ + protected final Set getElementMappings() { + initElementMappingsIfNecessary(); + return elementMappings; + } + + /** + * 检验方法的签名是否与原始方法匹配 + * + * @param source 原始的方法 + * @param target 比较的方法 + * @return 是否 + */ + protected boolean isMatchMethod(final Method source, final Method target) { + return CharSequenceUtil.equals(source.getName(), target.getName()) + // 不可为桥接方法或者合成方法 + && !target.isBridge() && !target.isSynthetic() + // 返回值需可通过原始方法的返回值转换得到 + && ClassUtil.isAssignable(target.getReturnType(), source.getReturnType()) + // 参数数量必须一致,且类型也必须严格一致,但不检验泛型 + && Arrays.equals(source.getParameterTypes(), target.getParameterTypes()); + } + + // ========================= private ========================= + + /** + * 将元素转为{@link MetaAnnotatedElement}后添加至{@code mappings} + */ + private void collectElement(Set elements, final AnnotatedElement element) { + AnnotatedElement target = elementFactory.apply(elements, element); + if (Objects.nonNull(target)) { + elements.add(target); + } + } + + /** + * 遍历层级结构,获取层级结构中所有关联的{@link AnnotatedElement},并添加到{@link #elementMappings} + */ + private void initElementMappingsIfNecessary() { + // 双重检查保证初始化过程线程安全 + if (Objects.isNull(elementMappings)) { + synchronized (this) { + if (Objects.isNull(elementMappings)) { + Set mappings = initElementMappings(); + elementMappings = Collections.unmodifiableSet(mappings); + } + } + } + } + + /** + * 遍历层级结构,获取层级结构中所有关联的{@link AnnotatedElement},并添加到{@link #elementMappings} + */ + private Set initElementMappings() { + Set mappings = new LinkedHashSet<>(); + // 原始元素是类 + if (source instanceof Class) { + scanHierarchy(mappings, (Class)source, false, source); + } + // 原始元素是方法 + else if (source instanceof Method) { + final Method methodSource = (Method)source; + // 静态、私有与被final关键字修饰方法无法被子类重写,因此不可能具有层级结构 + if (Modifier.isPrivate(methodSource.getModifiers()) + || Modifier.isFinal(methodSource.getModifiers()) + || Modifier.isStatic(methodSource.getModifiers())) { + collectElement(mappings, methodSource); + } else { + scanHierarchy(mappings, methodSource.getDeclaringClass(), true, methodSource); + } + } + return mappings; + } + + /** + * 按广度优先,遍历{@code type}的父类以及父接口,并从类上/类中指定方法上获得所需的注解 + */ + private void scanHierarchy( + Set mappings, Class type, final boolean isMethod, final AnnotatedElement source) { + Method methodSource = isMethod ? (Method)source : null; + final Deque> deque = new LinkedList<>(); + deque.addLast(type); + final Set> accessed = new HashSet<>(); + while (!deque.isEmpty()) { + type = deque.removeFirst(); + // 已访问过的类不再处理 + if (!isNeedMapping(type, accessed)) { + continue; + } + // 收集元素 + if (!isMethod) { + collectElement(mappings, type); + } else { + // TODO 改为通过带缓存的反射工具类完成 + Stream.of(type.getDeclaredMethods()) + .filter(method -> isMatchMethod(methodSource, method)) + .forEach(method -> collectElement(mappings, method)); + } + // 获取父类与父接口 + accessed.add(type); + deque.addLast(type.getSuperclass()); + CollUtil.addAll(deque, type.getInterfaces()); + } + } + + /** + * 是否需要处理该类,不符合任意一点则不处理: + *

+ */ + private boolean isNeedMapping(Class type, Set> accessedTypes) { + return Objects.nonNull(type) + && !accessedTypes.contains(type) + && !Objects.equals(type, Object.class); + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/HierarchicalAnnotatedElementTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/HierarchicalAnnotatedElementTest.java new file mode 100644 index 000000000..f4e8a0034 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/HierarchicalAnnotatedElementTest.java @@ -0,0 +1,208 @@ +package cn.hutool.core.annotation; + +import lombok.SneakyThrows; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.*; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.*; +import java.util.function.BiFunction; + +/** + * test for {@link HierarchicalAnnotatedElements} + * + * @author huangchengxing + */ +public class HierarchicalAnnotatedElementTest { + + private static final BiFunction, AnnotatedElement, AnnotatedElement> ELEMENT_MAPPING_FACTORY = (es, e) -> e; + + @SneakyThrows + @Test + public void testCreateFromMethod() { + Method method1 = Foo.class.getDeclaredMethod("method"); + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(method1); + Assert.assertEquals(3, elements.getElementMappings().size()); + + Method method2 = Foo.class.getDeclaredMethod("method2"); + elements = HierarchicalAnnotatedElements.create(method2); + Assert.assertEquals(1, elements.getElementMappings().size()); + } + + @Test + public void testCreate() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class); + Assert.assertNotNull(elements); + Assert.assertEquals(3, elements.getElementMappings().size()); + + elements = HierarchicalAnnotatedElements.create(Foo.class, ELEMENT_MAPPING_FACTORY); + Assert.assertNotNull(elements); + Assert.assertEquals(3, elements.getElementMappings().size()); + + Assert.assertEquals(elements, HierarchicalAnnotatedElements.create(elements, ELEMENT_MAPPING_FACTORY)); + } + + @Test + public void testEquals() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class, ELEMENT_MAPPING_FACTORY); + Assert.assertEquals(elements, elements); + Assert.assertEquals(elements, HierarchicalAnnotatedElements.create(Foo.class, ELEMENT_MAPPING_FACTORY)); + Assert.assertNotEquals(elements, HierarchicalAnnotatedElements.create(Super.class, ELEMENT_MAPPING_FACTORY)); + Assert.assertNotEquals(elements, HierarchicalAnnotatedElements.create(Foo.class, (es, e) -> e)); + Assert.assertNotEquals(elements, null); + } + + @Test + public void testHashCode() { + int hashCode = HierarchicalAnnotatedElements.create(Foo.class, ELEMENT_MAPPING_FACTORY).hashCode(); + Assert.assertEquals(hashCode, HierarchicalAnnotatedElements.create(Foo.class, ELEMENT_MAPPING_FACTORY).hashCode()); + Assert.assertNotEquals(hashCode, HierarchicalAnnotatedElements.create(Super.class, ELEMENT_MAPPING_FACTORY).hashCode()); + Assert.assertNotEquals(hashCode, HierarchicalAnnotatedElements.create(Foo.class, (es, e) -> e).hashCode()); + } + + @Test + public void testGetElement() { + AnnotatedElement element = Foo.class; + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(element, ELEMENT_MAPPING_FACTORY); + Assert.assertSame(element, elements.getElement()); + } + + @Test + public void testIsAnnotationPresent() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class); + Assert.assertTrue(elements.isAnnotationPresent(Annotation1.class)); + Assert.assertTrue(elements.isAnnotationPresent(Annotation2.class)); + Assert.assertTrue(elements.isAnnotationPresent(Annotation3.class)); + } + + @Test + public void testGetAnnotations() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class); + + Annotation1 annotation1 = Foo.class.getAnnotation(Annotation1.class); + Annotation2 annotation2 = Super.class.getAnnotation(Annotation2.class); + Annotation3 annotation3 = Interface.class.getAnnotation(Annotation3.class); + Annotation[] annotations = new Annotation[]{ annotation1, annotation2, annotation3 }; + + Assert.assertArrayEquals(annotations, elements.getAnnotations()); + } + + @Test + public void testGetAnnotation() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class); + + Annotation1 annotation1 = Foo.class.getAnnotation(Annotation1.class); + Assert.assertEquals(annotation1, elements.getAnnotation(Annotation1.class)); + + Annotation2 annotation2 = Super.class.getAnnotation(Annotation2.class); + Assert.assertEquals(annotation2, elements.getAnnotation(Annotation2.class)); + + Annotation3 annotation3 = Interface.class.getAnnotation(Annotation3.class); + Assert.assertEquals(annotation3, elements.getAnnotation(Annotation3.class)); + } + + @Test + public void testGetAnnotationsByType() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class); + + Annotation1 annotation1 = Foo.class.getAnnotation(Annotation1.class); + Assert.assertArrayEquals(new Annotation[]{ annotation1 }, elements.getAnnotationsByType(Annotation1.class)); + + Annotation2 annotation2 = Super.class.getAnnotation(Annotation2.class); + Assert.assertArrayEquals(new Annotation[]{ annotation2 }, elements.getAnnotationsByType(Annotation2.class)); + + Annotation3 annotation3 = Interface.class.getAnnotation(Annotation3.class); + Assert.assertArrayEquals(new Annotation[]{ annotation3 }, elements.getAnnotationsByType(Annotation3.class)); + } + + @Test + public void testGetDeclaredAnnotationsByType() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class); + + Annotation1 annotation1 = Foo.class.getAnnotation(Annotation1.class); + Assert.assertArrayEquals(new Annotation[]{ annotation1 }, elements.getDeclaredAnnotationsByType(Annotation1.class)); + + Annotation2 annotation2 = Super.class.getAnnotation(Annotation2.class); + Assert.assertArrayEquals(new Annotation[]{ annotation2 }, elements.getDeclaredAnnotationsByType(Annotation2.class)); + + Annotation3 annotation3 = Interface.class.getAnnotation(Annotation3.class); + Assert.assertArrayEquals(new Annotation[]{ annotation3 }, elements.getDeclaredAnnotationsByType(Annotation3.class)); + } + + @Test + public void testGetDeclaredAnnotation() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class); + + Annotation1 annotation1 = Foo.class.getAnnotation(Annotation1.class); + Assert.assertEquals(annotation1, elements.getDeclaredAnnotation(Annotation1.class)); + + Annotation2 annotation2 = Super.class.getAnnotation(Annotation2.class); + Assert.assertEquals(annotation2, elements.getDeclaredAnnotation(Annotation2.class)); + + Annotation3 annotation3 = Interface.class.getAnnotation(Annotation3.class); + Assert.assertEquals(annotation3, elements.getDeclaredAnnotation(Annotation3.class)); + } + + @Test + public void testGetDeclaredAnnotations() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class); + + Annotation1 annotation1 = Foo.class.getAnnotation(Annotation1.class); + Annotation2 annotation2 = Super.class.getAnnotation(Annotation2.class); + Annotation3 annotation3 = Interface.class.getAnnotation(Annotation3.class); + Annotation[] annotations = new Annotation[]{ annotation1, annotation2, annotation3 }; + + Assert.assertArrayEquals(annotations, elements.getDeclaredAnnotations()); + } + + @Test + public void testIterator() { + HierarchicalAnnotatedElements elements = HierarchicalAnnotatedElements.create(Foo.class); + Iterator iterator = elements.iterator(); + Assert.assertNotNull(iterator); + + List elementList = new ArrayList<>(); + iterator.forEachRemaining(elementList::add); + Assert.assertEquals(Arrays.asList(Foo.class, Super.class, Interface.class), elementList); + } + + @Target({ElementType.TYPE_USE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation3 { } + + @Target({ElementType.TYPE_USE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation2 { } + + @Target({ElementType.TYPE_USE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation1 { } + + @Annotation3 + private interface Interface { + @Annotation3 + String method(); + @Annotation3 + static String method2() { return null; } + } + + @Annotation2 + private static class Super { + @Annotation2 + public String method() { return null; } + @Annotation2 + public static String method2() { return null; } + } + + @Annotation1 + private static class Foo extends Super implements Interface { + @Annotation1 + @Override + public String method() { return null; } + @Annotation1 + public static String method2() { return null; } + }; + +} From 6c6eeb49d79fd3e0ece2d6fe2138e82e2f247c8c Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Tue, 13 Sep 2022 13:59:01 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E6=B7=BB=E5=8A=A0AnnotatedElementUtil?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/annotation/AnnotatedElementUtil.java | 615 ++++++++++++++++++ .../annotation/AnnotatedElementUtilTest.java | 480 ++++++++++++++ 2 files changed, 1095 insertions(+) create mode 100644 hutool-core/src/main/java/cn/hutool/core/annotation/AnnotatedElementUtil.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/annotation/AnnotatedElementUtilTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotatedElementUtil.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotatedElementUtil.java new file mode 100644 index 000000000..2670c99bb --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotatedElementUtil.java @@ -0,0 +1,615 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.map.WeakConcurrentMap; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjUtil; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Inherited; +import java.lang.reflect.AnnotatedElement; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +/** + *

{@link AnnotatedElement}工具类,提供对层级结构中{@link AnnotatedElement}上注解及元注解的访问支持, + * 并提供诸如基于{@link Alias}的属性别名、基于父子注解间的属性值覆盖等特殊的属性映射机制支持。 + * + *

搜索层级结构 + *

参考 Spring 中AnnotatedElementUtils, + * 工具类提供get以及find两种语义的搜索: + *

    + *
  • get:表示搜索范围仅限于指定的{@link AnnotatedElement}本身;
  • + *
  • + * find:表示搜索范围除了指定的{@link AnnotatedElement}本身外, + * 若{@link AnnotatedElement}是类,则还会搜索其所有关联的父类和父接口; + * 若{@link AnnotatedElement}是方法,则还会搜索其声明类关联的所有父类和父接口中,与该方法具有相同方法签名的方法对象;
    + *
  • + *
+ * eg:
+ * 若类A分别有父类和父接口BC, + * 则通过getXXX方法将只能获得A上的注解, + * 而通过getXXX方法将能获得ABC上的注解。 + * + *

搜索元注解 + *

工具类支持搜索注解的元注解。在所有格式为getXXXfindXXX的静态方法中, + * 若不带有directly关键字,则该方法支持搜索元注解,否则皆支持搜索元注解。
+ * eg:
+ * 若类A分别有父类和父接口BC,上面分别有注解X与其元注解Y, + * 则此时基于A有: + *

    + *
  • getDirectlyXXX:能够获得A上的注解X
  • + *
  • getXXX:能够获得A上的注解X及元注解Y
  • + *
  • findDirectlyXXX:能够分别获得ABC上的注解X
  • + *
  • findXXX:能够分别获得ABC上的注解X及元注解Y
  • + *
+ * 注意:在当前实例中将无视{@link Inherited}的效果,即通过directly方法将无法获得父类上带有{@link Inherited}的注解。 + * + *

注解属性映射 + *

工具类支持注解对象属性上的一些属性映射机制,即当注解被扫描时, + * 将根据一些属性映射机制“解析”为其他类型的属性,这里支持的机制包括: + *

    + *
  • + * 基于{@link Alias}的属性别名:若注解属性通过{@link Alias}互相关联,则对其中任意属性赋值,则等同于对所有关联属性赋值;
    + * eg: + *
    
    + *          // set aliased attributes
    + *         {@literal @}interface FooAnnotation {
    + *             {@literal @}Alias("alias")
    + *              default String value() default "";
    + *             {@literal @}Alias("value")
    + *              default String alias() default "";
    + *          }
    + *         {@literal @}FooAnnotation("foo")
    + *          class Foo { }
    + *
    + *          // get resolved annotation
    + *          FooAnnotation annotation = getResolvedAnnotation(Foo.class, FooAnnotation.class);
    + *          annotation.value(); // = "foo"
    + *          annotation.alias(); // = "foo"
    + *         }
    + *
  • + *
  • + * 基于父子注解的属性覆写:若子注解中存在属性,与其元注解的属性名称、类型皆相同,则子注解的属性值将会覆写其元注解的属性值, + * 若被覆写的属性值存在关联别名,则关联别名也会被一并覆写。
    + * eg: + *
    
    + *         {@literal @}interface Meta {
    + *              default String value() default "";
    + *          }
    + *         {@literal @}Meta("meta")
    + *          {@literal @}interface Root {
    + *              default String value() default ""; // overwrite for @Meta.value
    + *          }
    + *         {@literal @}Root("foo")
    + *          class Foo { }
    + *
    + *          // get resolved annotation
    + *          Meta meta = getResolvedAnnotation(Foo.class, Meta.class);
    + *          meta.value(); // = "foo"
    + *          Root root = getResolvedAnnotation(Foo.class, Root.class);
    + *          root.value(); // = "foo"
    + *         
    + *
  • + *
+ * + * @author huangchengxing + * @see ResolvedAnnotationMapping + * @see GenericAnnotationMapping + * @see HierarchicalAnnotatedElements + * @see MetaAnnotatedElement + * @since 6.0.0 + */ +public class AnnotatedElementUtil { + + /** + * 支持属性解析的{@link MetaAnnotatedElement}缓存 + */ + private static final Map> RESOLVED_ELEMENT_CACHE = new WeakConcurrentMap<>(); + + /** + * 不支持属性解析的{@link MetaAnnotatedElement}缓存 + */ + private static final Map> ELEMENT_CACHE = new WeakConcurrentMap<>(); + + // region ========== find ========== + + /** + * 在{@code element}所处层级结构的所有{@link AnnotatedElement}上,是否存在该类型的注解或元注解 + * + * @param element {@link AnnotatedElement} + * @param annotationType 注解类型 + * @return 是否 + */ + public static boolean isAnnotated(final AnnotatedElement element, final Class annotationType) { + return toHierarchyMetaElement(element, false) + .isAnnotationPresent(annotationType); + } + + /** + * 从{@code element}所处层级结构的所有{@link AnnotatedElement}上,获取该类型的注解或元注解 + * + * @param element {@link AnnotatedElement} + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + public static T findAnnotation(final AnnotatedElement element, final Class annotationType) { + return toHierarchyMetaElement(element, false) + .getAnnotation(annotationType); + } + + /** + * 从{@code element}所处层级结构的所有{@link AnnotatedElement}上,获取所有该类型的注解或元注解 + * + * @param element {@link AnnotatedElement} + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + public static T[] findAllAnnotations(final AnnotatedElement element, final Class annotationType) { + return toHierarchyMetaElement(element, false) + .getAnnotationsByType(annotationType); + } + + /** + * 从{@code element}所处层级结构的所有{@link AnnotatedElement}上,获取所有的注解或元注解 + * + * @param element {@link AnnotatedElement} + * @return 注解对象 + */ + public static Annotation[] findAnnotations(final AnnotatedElement element) { + return toHierarchyMetaElement(element, false) + .getAnnotations(); + } + + /** + * 从{@code element}所处层级结构的所有{@link AnnotatedElement}上,获取所有的注解或元注解。
+ * 得到的注解支持基于{@link Alias}的别名、及子注解对元注解中同名同类型属性进行覆写的特殊机制。 + * + * @param element {@link AnnotatedElement} + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + public static T findResolvedAnnotation(final AnnotatedElement element, final Class annotationType) { + return toHierarchyMetaElement(element, true) + .getAnnotation(annotationType); + } + + /** + * 从{@code element}所处层级结构的所有{@link AnnotatedElement}上,获取所有的注解或元注解。
+ * 得到的注解支持基于{@link Alias}的别名、及子注解对元注解中同名同类型属性进行覆写的特殊机制。 + * + * @param element {@link AnnotatedElement} + * @return 注解对象 + */ + public static Annotation[] findResolvedAnnotations(final AnnotatedElement element) { + return toHierarchyMetaElement(element, true) + .getAnnotations(); + } + + /** + * 从{@code element}所处层级结构的所有{@link AnnotatedElement}上,获取所有该类型的注解或元注解。
+ * 得到的注解支持基于{@link Alias}的别名、及子注解对元注解中同名同类型属性进行覆写的特殊机制。 + * + * @param element {@link AnnotatedElement} + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + public static T[] findAllResolvedAnnotations(final AnnotatedElement element, final Class annotationType) { + return toHierarchyMetaElement(element, true) + .getAnnotationsByType(annotationType); + } + + // endregion + + // region ========== find & direct ========== + + /** + * 从{@code element}所处层级结构的所有{@link AnnotatedElement}上获取该类型的注解 + * + * @param element {@link AnnotatedElement} + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + public static T findDirectlyAnnotation(final AnnotatedElement element, final Class annotationType) { + return toHierarchyMetaElement(element, false) + .getDeclaredAnnotation(annotationType); + } + + /** + * 从{@code element}所处层级结构的所有{@link AnnotatedElement}上获取所有该类型的注解 + * + * @param element {@link AnnotatedElement} + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + public static T[] findAllDirectlyAnnotations(final AnnotatedElement element, final Class annotationType) { + return toHierarchyMetaElement(element, false) + .getDeclaredAnnotationsByType(annotationType); + } + + /** + * 从{@code element}所处层级结构的所有{@link AnnotatedElement}上获取所有的注解 + * + * @param element {@link AnnotatedElement} + * @return 注解对象 + */ + public static Annotation[] findDirectlyAnnotations(final AnnotatedElement element) { + return toHierarchyMetaElement(element, false) + .getDeclaredAnnotations(); + } + + /** + * 从{@code element}所处层级结构的所有{@link AnnotatedElement}上,获取所有的注解。
+ * 得到的注解支持基于{@link Alias}的别名、及子注解对元注解中同名同类型属性进行覆写的特殊机制。 + * + * @param element {@link AnnotatedElement} + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + public static T findDirectlyResolvedAnnotation(final AnnotatedElement element, final Class annotationType) { + return toHierarchyMetaElement(element, true) + .getDeclaredAnnotation(annotationType); + } + + /** + * 从{@code element}所处层级结构的所有{@link AnnotatedElement}上获取所有的注解。
+ * 得到的注解支持基于{@link Alias}的别名、及子注解对元注解中同名同类型属性进行覆写的特殊机制。 + * + * @param element {@link AnnotatedElement} + * @return 注解对象 + */ + public static Annotation[] findDirectlyResolvedAnnotations(final AnnotatedElement element) { + return toHierarchyMetaElement(element, true) + .getDeclaredAnnotations(); + } + + /** + * 从{@code element}所处层级结构的所有{@link AnnotatedElement}上获取所有该类型的注解。
+ * 得到的注解支持基于{@link Alias}的别名、及子注解对元注解中同名同类型属性进行覆写的特殊机制。 + * + * @param element {@link AnnotatedElement} + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + public static T[] findAllDirectlyResolvedAnnotations(final AnnotatedElement element, final Class annotationType) { + return toHierarchyMetaElement(element, true) + .getDeclaredAnnotationsByType(annotationType); + } + + // endregion + + // region ========== get ========== + + /** + * 在{@code element}上,是否存在该类型的注解或元注解 + * + * @param element {@link AnnotatedElement} + * @param annotationType 注解类型 + * @return 是否 + */ + public static boolean isAnnotationPresent(final AnnotatedElement element, final Class annotationType) { + return toMetaElement(element, false) + .isAnnotationPresent(annotationType); + } + + /** + * 从{@code element}上,获取该类型的注解或元注解 + * + * @param element {@link AnnotatedElement} + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + public static T getAnnotation(final AnnotatedElement element, final Class annotationType) { + return toMetaElement(element, false) + .getAnnotation(annotationType); + } + + /** + * 从{@code element}上,获取所有的注解或元注解 + * + * @param element {@link AnnotatedElement} + * @return 注解对象 + */ + public static Annotation[] getAnnotations(final AnnotatedElement element) { + return toMetaElement(element, false) + .getAnnotations(); + } + + /** + * 从{@code element}上,获取所有的注解或元注解。
+ * 得到的注解支持基于{@link Alias}的别名机制。 + * + * @param element {@link AnnotatedElement} + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + public static T getResolvedAnnotation(final AnnotatedElement element, final Class annotationType) { + return toMetaElement(element, true) + .getAnnotation(annotationType); + } + + /** + * 从{@code element}上,获取所有的注解或元注解。
+ * 得到的注解支持基于{@link Alias}的别名机制。 + * + * @param element {@link AnnotatedElement} + * @return 注解对象 + */ + public static Annotation[] getResolvedAnnotations(final AnnotatedElement element) { + return toMetaElement(element, true) + .getAnnotations(); + } + + // endregion + + // region ========== get & direct ========== + + /** + * 从{@code element}上获取该类型的注解 + * + * @param element {@link AnnotatedElement} + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + public static T getDirectlyAnnotation(final AnnotatedElement element, final Class annotationType) { + return toMetaElement(element, false) + .getDeclaredAnnotation(annotationType); + } + + /** + * 从{@code element}上获取所有的注解 + * + * @param element {@link AnnotatedElement} + * @return 注解对象 + */ + public static Annotation[] getDirectlyAnnotations(final AnnotatedElement element) { + return toMetaElement(element, false) + .getDeclaredAnnotations(); + } + + /** + * 从{@code element}上,获取所有的注解。
+ * 得到的注解支持基于{@link Alias}的别名机制。 + * + * @param element {@link AnnotatedElement} + * @param annotationType 注解类型 + * @param 注解类型 + * @return 注解对象 + */ + public static T getDirectlyResolvedAnnotation(final AnnotatedElement element, final Class annotationType) { + return toMetaElement(element, true) + .getDeclaredAnnotation(annotationType); + } + + /** + * 从{@code element}上,获取所有的注解。
+ * 得到的注解支持基于{@link Alias}的别名机制。 + * + * @param element {@link AnnotatedElement} + * @return 注解对象 + */ + public static Annotation[] getDirectlyResolvedAnnotations(final AnnotatedElement element) { + return toMetaElement(element, true) + .getDeclaredAnnotations(); + } + + // endregion + + // region ========== to element ========== + + /** + *

扫描{@code element}所处层级结构中的{@link AnnotatedElement}, + * 并将其全部转为{@link MetaAnnotatedElement}后, + * 再把所有对象合并为{@link HierarchicalAnnotatedElements}。
+ * 得到的对象可访问{@code element}所处层级结构中所有{@link AnnotatedElement}上的注解及元注解。 + * + * @param element 元素 + * @param resolved 是否解析注解属性,若为{@code true}则获得的注解将支持属性别名以及属性覆盖机制 + * @return {@link HierarchicalAnnotatedElements}实例 + * @see #getMetaElementCache(AnnotatedElement) + * @see #getResolvedMetaElementCache(AnnotatedElement) + */ + public static AnnotatedElement toHierarchyMetaElement(final AnnotatedElement element, final boolean resolved) { + if (Objects.isNull(element)) { + return emptyElement(); + } + if (resolved) { + return HierarchicalAnnotatedElements.create(element, (es, e) -> getResolvedMetaElementCache(e)); + } + return HierarchicalAnnotatedElements.create(element, (es, e) -> getMetaElementCache(e)); + } + + /** + *

扫描{@code element}所处层级结构中的{@link AnnotatedElement}, + * 再把所有对象合并为{@link HierarchicalAnnotatedElements} + * 得到的对象可访问{@code element}所处层级结构中所有{@link AnnotatedElement}上的注解。 + * + * @param element 元素 + * @return {@link AnnotatedElement}实例 + */ + public static AnnotatedElement toHierarchyElement(final AnnotatedElement element) { + return ObjUtil.defaultIfNull( + element, ele -> HierarchicalAnnotatedElements.create(ele, (es, e) -> e), emptyElement() + ); + } + + /** + * 将{@link AnnotatedElement}转为{@link MetaAnnotatedElement}, + * 得到的对象可访问{@code element}上所有的注解及元注解。 + * + * @param element 元素 + * @param resolved 是否解析注解属性,若为{@code true}则获得的注解将支持属性别名以及属性覆盖机制 + * @return {@link AnnotatedElement}实例 + * @see #getMetaElementCache(AnnotatedElement) + * @see #getResolvedMetaElementCache(AnnotatedElement) + */ + public static AnnotatedElement toMetaElement(final AnnotatedElement element, final boolean resolved) { + return ObjUtil.defaultIfNull( + element, e -> resolved ? getResolvedMetaElementCache(e) : getMetaElementCache(e), emptyElement() + ); + } + + /** + * 将一组注解中的非{@code null}注解对象合并为一个{@link AnnotatedElement} + * + * @param annotations 注解 + * @return {@link AnnotatedElement}实例 + * @see ConstantElement + */ + public static AnnotatedElement asElement(Annotation... annotations) { + annotations = ArrayUtil.filter(annotations, Objects::nonNull); + return ArrayUtil.isEmpty(annotations) ? + emptyElement() : new ConstantElement(annotations); + } + + /** + * 获取一个不包含任何注解的{@link AnnotatedElement} + * + * @return {@link AnnotatedElement}实例 + * @see EmptyElement + */ + public static AnnotatedElement emptyElement() { + return EmptyElement.INSTANCE; + } + + // endregion + + // region ========== private ========== + + /** + * 创建一个支持注解解析的{@link MetaAnnotatedElement} + * + * @param element {@link AnnotatedElement} + * @return {@link MetaAnnotatedElement}实例 + */ + private static MetaAnnotatedElement getResolvedMetaElementCache(final AnnotatedElement element) { + return RESOLVED_ELEMENT_CACHE.computeIfAbsent(element, ele -> MetaAnnotatedElement.create( + element, (source, annotation) -> ResolvedAnnotationMapping.create(source, annotation, true) + )); + } + + /** + * 创建一个支持注解解析的{@link MetaAnnotatedElement} + * + * @param element {@link AnnotatedElement} + * @return {@link MetaAnnotatedElement}实例 + */ + private static MetaAnnotatedElement getMetaElementCache(final AnnotatedElement element) { + return ELEMENT_CACHE.computeIfAbsent(element, ele -> MetaAnnotatedElement.create( + element, (source, annotation) -> GenericAnnotationMapping.create(annotation, Objects.isNull(source)) + )); + } + + // endregion + + /** + * 由一组注解聚合来的{@link AnnotatedElement} + */ + private static class ConstantElement implements AnnotatedElement { + + /** + * 注解对象 + */ + private final Annotation[] annotations; + + /** + * 构造 + * + * @param annotations 注解 + */ + ConstantElement(final Annotation[] annotations) { + this.annotations = Objects.requireNonNull(annotations); + } + + /** + * 获取指定类型的注解对象 + * + * @param annotationClass 注解类型 + * @param 注解类型 + * @return 注解 + */ + @Override + public T getAnnotation(final Class annotationClass) { + return Stream.of(annotations) + .filter(annotation -> Objects.equals(annotation.annotationType(), annotationClass)) + .findFirst() + .map(annotationClass::cast) + .orElse(null); + } + + /** + * 获取指定直接所有的注解对象 + * + * @return 注解 + */ + @Override + public Annotation[] getAnnotations() { + return annotations.clone(); + } + + /** + * 获取指定直接声明的注解对象 + * + * @return 注解 + */ + @Override + public Annotation[] getDeclaredAnnotations() { + return annotations.clone(); + } + } + + /** + * 不包含任何注解的{@link AnnotatedElement} + */ + private static class EmptyElement implements AnnotatedElement { + + /** + * 默认的空实例 + */ + static final EmptyElement INSTANCE = new EmptyElement(); + + /** + * 固定返回{@code null} + * + * @param annotationClass 注解类型 + * @param 注解类型 + * @return {@code null} + */ + @Override + public T getAnnotation(final Class annotationClass) { + return null; + } + + /** + * 固定返回空数组 + * + * @return 空数组 + */ + @Override + public Annotation[] getAnnotations() { + return new Annotation[0]; + } + + /** + * 固定返回空数组 + * + * @return 空数组 + */ + @Override + public Annotation[] getDeclaredAnnotations() { + return new Annotation[0]; + } + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotatedElementUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotatedElementUtilTest.java new file mode 100644 index 000000000..03ed704a6 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotatedElementUtilTest.java @@ -0,0 +1,480 @@ +package cn.hutool.core.annotation; + +import org.junit.Assert; +import org.junit.Test; + +import java.lang.annotation.*; +import java.lang.reflect.AnnotatedElement; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * test for {@link AnnotatedElementUtil} + * + * @author huangchengxing + */ +public class AnnotatedElementUtilTest { + + private final static Annotation3 ANNOTATION3 = Foo.class.getAnnotation(Annotation3.class); // Foo.class's annotations + private final static Annotation2 ANNOTATION2 = Annotation3.class.getAnnotation(Annotation2.class); + private final static Annotation1 ANNOTATION1 = Annotation2.class.getAnnotation(Annotation1.class); + + private final static Annotation4 ANNOTATION4 = Super.class.getAnnotation(Annotation4.class); // Super.class's annotations + + private final static Annotation6 ANNOTATION6 = Interface.class.getAnnotation(Annotation6.class); // Interface.class's annotations + private final static Annotation5 ANNOTATION5 = Annotation6.class.getAnnotation(Annotation5.class); + + private final static Annotation[] DECLARED_ANNOTATIONS = new Annotation[]{ + ANNOTATION3, // Foo.class's annotations + ANNOTATION4, // Super.class's annotations + ANNOTATION6 // Interface.class's annotations + }; + private final static Annotation[] ANNOTATIONS = new Annotation[]{ + ANNOTATION3, ANNOTATION2, ANNOTATION1, // Foo.class's annotations + ANNOTATION4, // Super.class's annotations + ANNOTATION6, ANNOTATION5 // Interface.class's annotations + }; + + @Test + public void testIsAnnotated() { + Assert.assertTrue(AnnotatedElementUtil.isAnnotated(Foo.class, Annotation1.class)); + Assert.assertTrue(AnnotatedElementUtil.isAnnotated(Foo.class, Annotation2.class)); + Assert.assertTrue(AnnotatedElementUtil.isAnnotated(Foo.class, Annotation3.class)); + Assert.assertTrue(AnnotatedElementUtil.isAnnotated(Foo.class, Annotation4.class)); + Assert.assertTrue(AnnotatedElementUtil.isAnnotated(Foo.class, Annotation5.class)); + Assert.assertTrue(AnnotatedElementUtil.isAnnotated(Foo.class, Annotation6.class)); + } + + @Test + public void testFindAnnotation() { + Assert.assertEquals(ANNOTATION1, AnnotatedElementUtil.findAnnotation(Foo.class, Annotation1.class)); + Assert.assertEquals(ANNOTATION2, AnnotatedElementUtil.findAnnotation(Foo.class, Annotation2.class)); + Assert.assertEquals(ANNOTATION3, AnnotatedElementUtil.findAnnotation(Foo.class, Annotation3.class)); + Assert.assertEquals(ANNOTATION4, AnnotatedElementUtil.findAnnotation(Foo.class, Annotation4.class)); + Assert.assertEquals(ANNOTATION5, AnnotatedElementUtil.findAnnotation(Foo.class, Annotation5.class)); + Assert.assertEquals(ANNOTATION6, AnnotatedElementUtil.findAnnotation(Foo.class, Annotation6.class)); + } + + @Test + public void testFindAllAnnotations() { + Assert.assertArrayEquals(new Annotation[]{ANNOTATION1}, AnnotatedElementUtil.findAllAnnotations(Foo.class, Annotation1.class)); + Assert.assertArrayEquals(new Annotation[]{ANNOTATION2}, AnnotatedElementUtil.findAllAnnotations(Foo.class, Annotation2.class)); + Assert.assertArrayEquals(new Annotation[]{ANNOTATION3}, AnnotatedElementUtil.findAllAnnotations(Foo.class, Annotation3.class)); + Assert.assertArrayEquals(new Annotation[]{ANNOTATION4}, AnnotatedElementUtil.findAllAnnotations(Foo.class, Annotation4.class)); + Assert.assertArrayEquals(new Annotation[]{ANNOTATION5}, AnnotatedElementUtil.findAllAnnotations(Foo.class, Annotation5.class)); + Assert.assertArrayEquals(new Annotation[]{ANNOTATION6}, AnnotatedElementUtil.findAllAnnotations(Foo.class, Annotation6.class)); + } + + @Test + public void testFindAnnotations() { + Annotation[] annotations = AnnotatedElementUtil.findAnnotations(Foo.class); + Assert.assertArrayEquals(ANNOTATIONS, annotations); + } + + @Test + public void testFindResolvedAnnotation() { + Annotation3 resolvedAnnotation3 = AnnotatedElementUtil.findResolvedAnnotation(Foo.class, Annotation3.class); + Assert.assertNotNull(resolvedAnnotation3); + Assert.assertEquals(resolvedAnnotation3.alias(), ANNOTATION3.value()); + Assert.assertEquals(resolvedAnnotation3.alias(), resolvedAnnotation3.value()); // value与alias互为别名 + + Annotation2 resolvedAnnotation2 = AnnotatedElementUtil.findResolvedAnnotation(Foo.class, Annotation2.class); + Assert.assertNotNull(resolvedAnnotation2); + Assert.assertEquals(resolvedAnnotation2.num(), ANNOTATION3.num()); // num属性被Annotation3.num覆盖 + + Annotation1 resolvedAnnotation1 = AnnotatedElementUtil.findResolvedAnnotation(Foo.class, Annotation1.class); + Assert.assertNotNull(resolvedAnnotation1); + Assert.assertEquals(ANNOTATION3.value(), resolvedAnnotation1.value()); // value属性被Annotation3.value覆盖 + Assert.assertEquals(resolvedAnnotation1.value(), resolvedAnnotation1.alias()); // value与alias互为别名 + + Assert.assertEquals(ANNOTATION4, AnnotatedElementUtil.findResolvedAnnotation(Foo.class, Annotation4.class)); + Assert.assertEquals(ANNOTATION6, AnnotatedElementUtil.findResolvedAnnotation(Foo.class, Annotation6.class)); + Assert.assertEquals(ANNOTATION5, AnnotatedElementUtil.findResolvedAnnotation(Foo.class, Annotation5.class)); + } + + @Test + public void testFindResolvedAnnotations() { + Annotation[] resolvedAnnotations = AnnotatedElementUtil.findResolvedAnnotations(Foo.class); + Map, Annotation> annotationMap = Stream.of(resolvedAnnotations).collect(Collectors.toMap(Annotation::annotationType, Function.identity())); + + Annotation3 resolvedAnnotation3 = (Annotation3)annotationMap.get(Annotation3.class); + Assert.assertNotNull(resolvedAnnotation3); + Assert.assertEquals(resolvedAnnotation3.alias(), ANNOTATION3.value()); + Assert.assertEquals(resolvedAnnotation3.alias(), resolvedAnnotation3.value()); // value与alias互为别名 + + Annotation2 resolvedAnnotation2 = (Annotation2)annotationMap.get(Annotation2.class); + Assert.assertNotNull(resolvedAnnotation2); + Assert.assertEquals(resolvedAnnotation2.num(), ANNOTATION3.num()); // num属性被Annotation3.num覆盖 + + Annotation1 resolvedAnnotation1 = (Annotation1)annotationMap.get(Annotation1.class); + Assert.assertNotNull(resolvedAnnotation1); + Assert.assertEquals(ANNOTATION3.value(), resolvedAnnotation1.value()); // value属性被Annotation3.value覆盖 + Assert.assertEquals(resolvedAnnotation1.value(), resolvedAnnotation1.alias()); // value与alias互为别名 + + Assert.assertEquals(ANNOTATION4, annotationMap.get(Annotation4.class)); + Assert.assertEquals(ANNOTATION6, annotationMap.get(Annotation6.class)); + Assert.assertEquals(ANNOTATION5, annotationMap.get(Annotation5.class)); + } + + @Test + public void testFindAllResolvedAnnotations() { + Annotation3 resolvedAnnotation3 = AnnotatedElementUtil.findAllResolvedAnnotations(Foo.class, Annotation3.class)[0]; + Assert.assertNotNull(resolvedAnnotation3); + Assert.assertEquals(resolvedAnnotation3.alias(), ANNOTATION3.value()); + Assert.assertEquals(resolvedAnnotation3.alias(), resolvedAnnotation3.value()); // value与alias互为别名 + + Annotation2 resolvedAnnotation2 = AnnotatedElementUtil.findAllResolvedAnnotations(Foo.class, Annotation2.class)[0]; + Assert.assertNotNull(resolvedAnnotation2); + Assert.assertEquals(resolvedAnnotation2.num(), ANNOTATION3.num()); // num属性被Annotation3.num覆盖 + + Annotation1 resolvedAnnotation1 = AnnotatedElementUtil.findAllResolvedAnnotations(Foo.class, Annotation1.class)[0]; + Assert.assertNotNull(resolvedAnnotation1); + Assert.assertEquals(ANNOTATION3.value(), resolvedAnnotation1.value()); // value属性被Annotation3.value覆盖 + Assert.assertEquals(resolvedAnnotation1.value(), resolvedAnnotation1.alias()); // value与alias互为别名 + + Assert.assertEquals(ANNOTATION4, AnnotatedElementUtil.findAllResolvedAnnotations(Foo.class, Annotation4.class)[0]); + Assert.assertEquals(ANNOTATION6, AnnotatedElementUtil.findAllResolvedAnnotations(Foo.class, Annotation6.class)[0]); + Assert.assertEquals(ANNOTATION5, AnnotatedElementUtil.findAllResolvedAnnotations(Foo.class, Annotation5.class)[0]); + } + + @Test + public void testFindDirectlyAnnotation() { + Assert.assertNull(AnnotatedElementUtil.findDirectlyAnnotation(Foo.class, Annotation1.class)); + Assert.assertNull(AnnotatedElementUtil.findDirectlyAnnotation(Foo.class, Annotation2.class)); + Assert.assertEquals(ANNOTATION3, AnnotatedElementUtil.findDirectlyAnnotation(Foo.class, Annotation3.class)); + Assert.assertEquals(ANNOTATION4, AnnotatedElementUtil.findDirectlyAnnotation(Foo.class, Annotation4.class)); + Assert.assertNull(AnnotatedElementUtil.findDirectlyAnnotation(Foo.class, Annotation5.class)); + Assert.assertEquals(ANNOTATION6, AnnotatedElementUtil.findDirectlyAnnotation(Foo.class, Annotation6.class)); + } + + @Test + public void testFindAllDirectlyAnnotations() { + Assert.assertEquals(0, AnnotatedElementUtil.findAllDirectlyAnnotations(Foo.class, Annotation1.class).length); + Assert.assertEquals(0, AnnotatedElementUtil.findAllDirectlyAnnotations(Foo.class, Annotation2.class).length); + Assert.assertArrayEquals(new Annotation[]{ANNOTATION3}, AnnotatedElementUtil.findAllDirectlyAnnotations(Foo.class, Annotation3.class)); + Assert.assertArrayEquals(new Annotation[]{ANNOTATION4}, AnnotatedElementUtil.findAllDirectlyAnnotations(Foo.class, Annotation4.class)); + Assert.assertEquals(0, AnnotatedElementUtil.findAllDirectlyAnnotations(Foo.class, Annotation5.class).length); + Assert.assertArrayEquals(new Annotation[]{ANNOTATION6}, AnnotatedElementUtil.findAllDirectlyAnnotations(Foo.class, Annotation6.class)); + } + + @Test + public void testFindDirectlyAnnotations() { + Assert.assertArrayEquals( + DECLARED_ANNOTATIONS, AnnotatedElementUtil.findDirectlyAnnotations(Foo.class) + ); + } + + @Test + public void testFindDirectlyResolvedAnnotation() { + Assert.assertEquals(ANNOTATION4, AnnotatedElementUtil.findDirectlyResolvedAnnotation(Foo.class, Annotation4.class)); + Assert.assertEquals(ANNOTATION6, AnnotatedElementUtil.findDirectlyResolvedAnnotation(Foo.class, Annotation6.class)); + Annotation3 resolvedAnnotation3 = AnnotatedElementUtil.findDirectlyResolvedAnnotation(Foo.class, Annotation3.class); + Assert.assertNotNull(resolvedAnnotation3); + Assert.assertEquals(resolvedAnnotation3.alias(), ANNOTATION3.value()); + Assert.assertEquals(resolvedAnnotation3.alias(), resolvedAnnotation3.value()); // value与alias互为别名 + + Assert.assertNull(AnnotatedElementUtil.findDirectlyResolvedAnnotation(Foo.class, Annotation1.class)); + Assert.assertNull(AnnotatedElementUtil.findDirectlyResolvedAnnotation(Foo.class, Annotation2.class)); + Assert.assertNull(AnnotatedElementUtil.findDirectlyResolvedAnnotation(Foo.class, Annotation5.class)); + } + + @Test + public void testFindDirectlyResolvedAnnotations() { + Annotation[] resolvedAnnotations = AnnotatedElementUtil.findDirectlyResolvedAnnotations(Foo.class); + Map, Annotation> annotationMap = Stream.of(resolvedAnnotations).collect(Collectors.toMap(Annotation::annotationType, Function.identity())); + + Assert.assertEquals(ANNOTATION4, annotationMap.get(Annotation4.class)); + Assert.assertEquals(ANNOTATION6, annotationMap.get(Annotation6.class)); + Annotation3 resolvedAnnotation3 = (Annotation3)annotationMap.get(Annotation3.class); + Assert.assertNotNull(resolvedAnnotation3); + Assert.assertEquals(resolvedAnnotation3.alias(), ANNOTATION3.value()); + Assert.assertEquals(resolvedAnnotation3.alias(), resolvedAnnotation3.value()); // value与alias互为别名 + + Assert.assertNull(annotationMap.get(Annotation1.class)); + Assert.assertNull(annotationMap.get(Annotation2.class)); + Assert.assertNull(annotationMap.get(Annotation5.class)); + } + + @Test + public void testFindAllDirectlyResolvedAnnotations() { + + Assert.assertEquals(ANNOTATION4, AnnotatedElementUtil.findAllDirectlyResolvedAnnotations(Foo.class, Annotation4.class)[0]); + Assert.assertEquals(ANNOTATION6, AnnotatedElementUtil.findAllDirectlyResolvedAnnotations(Foo.class, Annotation6.class)[0]); + Annotation3 resolvedAnnotation3 = AnnotatedElementUtil.findAllDirectlyResolvedAnnotations(Foo.class, Annotation3.class)[0]; + Assert.assertNotNull(resolvedAnnotation3); + Assert.assertEquals(resolvedAnnotation3.alias(), ANNOTATION3.value()); + Assert.assertEquals(resolvedAnnotation3.alias(), resolvedAnnotation3.value()); // value与alias互为别名 + + Assert.assertEquals(0, AnnotatedElementUtil.findAllDirectlyResolvedAnnotations(Foo.class, Annotation1.class).length); + Assert.assertEquals(0, AnnotatedElementUtil.findAllDirectlyResolvedAnnotations(Foo.class, Annotation2.class).length); + Assert.assertEquals(0, AnnotatedElementUtil.findAllDirectlyResolvedAnnotations(Foo.class, Annotation5.class).length); + } + + @Test + public void testIsAnnotationPresent() { + Assert.assertTrue(AnnotatedElementUtil.isAnnotationPresent(Foo.class, Annotation1.class)); + Assert.assertTrue(AnnotatedElementUtil.isAnnotationPresent(Foo.class, Annotation2.class)); + Assert.assertTrue(AnnotatedElementUtil.isAnnotationPresent(Foo.class, Annotation3.class)); + + Assert.assertFalse(AnnotatedElementUtil.isAnnotationPresent(Foo.class, Annotation4.class)); + Assert.assertFalse(AnnotatedElementUtil.isAnnotationPresent(Foo.class, Annotation5.class)); + Assert.assertFalse(AnnotatedElementUtil.isAnnotationPresent(Foo.class, Annotation6.class)); + } + + @Test + public void testGetAnnotation() { + Assert.assertEquals(ANNOTATION1, AnnotatedElementUtil.getAnnotation(Foo.class, Annotation1.class)); + Assert.assertEquals(ANNOTATION2, AnnotatedElementUtil.getAnnotation(Foo.class, Annotation2.class)); + Assert.assertEquals(ANNOTATION3, AnnotatedElementUtil.getAnnotation(Foo.class, Annotation3.class)); + + Assert.assertNull(AnnotatedElementUtil.getAnnotation(Foo.class, Annotation4.class)); + Assert.assertNull(AnnotatedElementUtil.getAnnotation(Foo.class, Annotation5.class)); + Assert.assertNull(AnnotatedElementUtil.getAnnotation(Foo.class, Annotation6.class)); + } + + @Test + public void testGetAnnotations() { + Annotation[] annotations = AnnotatedElementUtil.getAnnotations(Foo.class); + Assert.assertArrayEquals( + new Annotation[]{ ANNOTATION3, ANNOTATION2, ANNOTATION1 }, + annotations + ); + } + + @Test + public void testGetResolvedAnnotation() { + Annotation3 resolvedAnnotation3 = AnnotatedElementUtil.getResolvedAnnotation(Foo.class, Annotation3.class); + Assert.assertNotNull(resolvedAnnotation3); + Assert.assertEquals(resolvedAnnotation3.alias(), ANNOTATION3.value()); + Assert.assertEquals(resolvedAnnotation3.alias(), resolvedAnnotation3.value()); // value与alias互为别名 + + Annotation2 resolvedAnnotation2 = AnnotatedElementUtil.getResolvedAnnotation(Foo.class, Annotation2.class); + Assert.assertNotNull(resolvedAnnotation2); + Assert.assertEquals(resolvedAnnotation2.num(), ANNOTATION3.num()); // num属性被Annotation3.num覆盖 + + Annotation1 resolvedAnnotation1 = AnnotatedElementUtil.getResolvedAnnotation(Foo.class, Annotation1.class); + Assert.assertNotNull(resolvedAnnotation1); + Assert.assertEquals(ANNOTATION3.value(), resolvedAnnotation1.value()); // value属性被Annotation3.value覆盖 + Assert.assertEquals(resolvedAnnotation1.value(), resolvedAnnotation1.alias()); // value与alias互为别名 + + Assert.assertNull(AnnotatedElementUtil.getResolvedAnnotation(Foo.class, Annotation4.class)); + Assert.assertNull(AnnotatedElementUtil.getResolvedAnnotation(Foo.class, Annotation5.class)); + Assert.assertNull(AnnotatedElementUtil.getResolvedAnnotation(Foo.class, Annotation6.class)); + } + + @Test + public void testGetResolvedAnnotations() { + Map, Annotation> annotationMap = Stream.of(AnnotatedElementUtil.getResolvedAnnotations(Foo.class)) + .collect(Collectors.toMap(Annotation::annotationType, Function.identity())); + + Annotation3 resolvedAnnotation3 = (Annotation3)annotationMap.get(Annotation3.class); + Assert.assertNotNull(resolvedAnnotation3); + Assert.assertEquals(resolvedAnnotation3.alias(), ANNOTATION3.value()); + Assert.assertEquals(resolvedAnnotation3.alias(), resolvedAnnotation3.value()); // value与alias互为别名 + + Annotation2 resolvedAnnotation2 = (Annotation2)annotationMap.get(Annotation2.class); + Assert.assertNotNull(resolvedAnnotation2); + Assert.assertEquals(resolvedAnnotation2.num(), ANNOTATION3.num()); // num属性被Annotation3.num覆盖 + + Annotation1 resolvedAnnotation1 = (Annotation1)annotationMap.get(Annotation1.class); + Assert.assertNotNull(resolvedAnnotation1); + Assert.assertEquals(ANNOTATION3.value(), resolvedAnnotation1.value()); // value属性被Annotation3.value覆盖 + Assert.assertEquals(resolvedAnnotation1.value(), resolvedAnnotation1.alias()); // value与alias互为别名 + + Assert.assertFalse(annotationMap.containsKey(Annotation4.class)); + Assert.assertFalse(annotationMap.containsKey(Annotation5.class)); + Assert.assertFalse(annotationMap.containsKey(Annotation6.class)); + } + + @Test + public void testGetDirectlyAnnotation() { + Assert.assertEquals(ANNOTATION3, AnnotatedElementUtil.getDirectlyAnnotation(Foo.class, Annotation3.class)); + + Assert.assertNull(AnnotatedElementUtil.getDirectlyAnnotation(Foo.class, Annotation2.class)); + Assert.assertNull(AnnotatedElementUtil.getDirectlyAnnotation(Foo.class, Annotation1.class)); + Assert.assertNull(AnnotatedElementUtil.getDirectlyAnnotation(Foo.class, Annotation4.class)); + Assert.assertNull(AnnotatedElementUtil.getDirectlyAnnotation(Foo.class, Annotation5.class)); + Assert.assertNull(AnnotatedElementUtil.getDirectlyAnnotation(Foo.class, Annotation6.class)); + } + + @Test + public void testGetDirectlyAnnotations() { + Annotation[] annotations = AnnotatedElementUtil.getDirectlyAnnotations(Foo.class); + Assert.assertEquals(1, annotations.length); + Assert.assertEquals(ANNOTATION3, annotations[0]); + } + + @Test + public void testGetDirectlyResolvedAnnotation() { + Annotation3 resolvedAnnotation3 = AnnotatedElementUtil.getDirectlyResolvedAnnotation(Foo.class, Annotation3.class); + Assert.assertNotNull(resolvedAnnotation3); + Assert.assertEquals(resolvedAnnotation3.alias(), ANNOTATION3.value()); + Assert.assertEquals(resolvedAnnotation3.alias(), resolvedAnnotation3.value()); // value与alias互为别名 + + Assert.assertNull(AnnotatedElementUtil.getDirectlyResolvedAnnotation(Foo.class, Annotation2.class)); + Assert.assertNull(AnnotatedElementUtil.getDirectlyResolvedAnnotation(Foo.class, Annotation1.class)); + Assert.assertNull(AnnotatedElementUtil.getDirectlyResolvedAnnotation(Foo.class, Annotation4.class)); + Assert.assertNull(AnnotatedElementUtil.getDirectlyResolvedAnnotation(Foo.class, Annotation5.class)); + Assert.assertNull(AnnotatedElementUtil.getDirectlyResolvedAnnotation(Foo.class, Annotation6.class)); + } + + @Test + public void testGetDirectlyResolvedAnnotations() { + Annotation[] annotations = AnnotatedElementUtil.getDirectlyResolvedAnnotations(Foo.class); + Assert.assertEquals(1, annotations.length); + + Annotation3 resolvedAnnotation3 = (Annotation3)annotations[0]; + Assert.assertNotNull(resolvedAnnotation3); + Assert.assertEquals(resolvedAnnotation3.alias(), ANNOTATION3.value()); + Assert.assertEquals(resolvedAnnotation3.alias(), resolvedAnnotation3.value()); // value与alias互为别名 + } + + @Test + public void testToHierarchyMetaElement() { + Assert.assertNotNull(AnnotatedElementUtil.toHierarchyMetaElement(null, false)); + Assert.assertNotNull(AnnotatedElementUtil.toHierarchyMetaElement(null, true)); + AnnotatedElement element = AnnotatedElementUtil.toHierarchyMetaElement(Foo.class, false); + + // 带有元注解 + Assert.assertArrayEquals(ANNOTATIONS, element.getAnnotations()); + + // 不带元注解 + Assert.assertArrayEquals(DECLARED_ANNOTATIONS, element.getDeclaredAnnotations()); + + // 解析注解属性 + AnnotatedElement resolvedElement = AnnotatedElementUtil.toHierarchyMetaElement(Foo.class, true); + Annotation3 resolvedAnnotation3 = resolvedElement.getAnnotation(Annotation3.class); + Assert.assertNotNull(resolvedAnnotation3); + Assert.assertEquals(resolvedAnnotation3.alias(), ANNOTATION3.value()); + Assert.assertEquals(resolvedAnnotation3.alias(), resolvedAnnotation3.value()); // value与alias互为别名 + + Annotation2 resolvedAnnotation2 = resolvedElement.getAnnotation(Annotation2.class); + Assert.assertNotNull(resolvedAnnotation2); + Assert.assertEquals(resolvedAnnotation2.num(), ANNOTATION3.num()); // num属性被Annotation3.num覆盖 + + Annotation1 resolvedAnnotation1 = resolvedElement.getAnnotation(Annotation1.class); + Assert.assertNotNull(resolvedAnnotation1); + Assert.assertEquals(ANNOTATION3.value(), resolvedAnnotation1.value()); // value属性被Annotation3.value覆盖 + Assert.assertEquals(resolvedAnnotation1.value(), resolvedAnnotation1.alias()); // value与alias互为别名 + + Assert.assertEquals(ANNOTATION4, resolvedElement.getAnnotation(Annotation4.class)); + Assert.assertEquals(ANNOTATION6, resolvedElement.getAnnotation(Annotation6.class)); + Assert.assertEquals(ANNOTATION5, resolvedElement.getAnnotation(Annotation5.class)); + } + + @Test + public void testToHierarchyElement() { + Assert.assertNotNull(AnnotatedElementUtil.toHierarchyElement(Foo.class)); + AnnotatedElement element = AnnotatedElementUtil.toHierarchyElement(Foo.class); + Assert.assertArrayEquals(new Annotation[]{ANNOTATION3, ANNOTATION4, ANNOTATION6}, element.getAnnotations()); + } + + @Test + public void testToMetaElement() { + Assert.assertNotNull(AnnotatedElementUtil.toMetaElement(null, false)); + Assert.assertNotNull(AnnotatedElementUtil.toMetaElement(null, true)); + + // 不解析注解属性 + AnnotatedElement element = AnnotatedElementUtil.toMetaElement(Foo.class, false); + Assert.assertSame(element, AnnotatedElementUtil.toMetaElement(Foo.class, false)); // 第二次获取时从缓存中获取 + Assert.assertArrayEquals(new Annotation[]{ANNOTATION3, ANNOTATION2, ANNOTATION1}, element.getAnnotations()); + + // 解析注解属性 + element = AnnotatedElementUtil.toMetaElement(Foo.class, true); + Assert.assertSame(element, AnnotatedElementUtil.toMetaElement(Foo.class, true)); // 第二次获取时从缓存中获取 + Assert.assertEquals(3, element.getAnnotations().length); + + Annotation3 resolvedAnnotation3 = element.getAnnotation(Annotation3.class); + Assert.assertNotNull(resolvedAnnotation3); + Assert.assertEquals(resolvedAnnotation3.alias(), ANNOTATION3.value()); + Assert.assertEquals(resolvedAnnotation3.alias(), resolvedAnnotation3.value()); // value与alias互为别名 + + Annotation2 resolvedAnnotation2 = element.getAnnotation(Annotation2.class); + Assert.assertNotNull(resolvedAnnotation2); + Assert.assertEquals(resolvedAnnotation2.num(), ANNOTATION3.num()); // num属性被Annotation3.num覆盖 + + Annotation1 resolvedAnnotation1 = element.getAnnotation(Annotation1.class); + Assert.assertNotNull(resolvedAnnotation1); + Assert.assertEquals(ANNOTATION3.value(), resolvedAnnotation1.value()); // value属性被Annotation3.value覆盖 + Assert.assertEquals(resolvedAnnotation1.value(), resolvedAnnotation1.alias()); // value与alias互为别名 + } + + @Test + public void testAsElement() { + Annotation[] annotations = new Annotation[]{ANNOTATION1, ANNOTATION2}; + Assert.assertNotNull(AnnotatedElementUtil.asElement()); + + AnnotatedElement element = AnnotatedElementUtil.asElement(ANNOTATION1, null, ANNOTATION2); + Assert.assertArrayEquals(annotations, element.getAnnotations()); + Assert.assertArrayEquals(annotations, element.getDeclaredAnnotations()); + Assert.assertEquals(ANNOTATION1, element.getAnnotation(Annotation1.class)); + Assert.assertNull(element.getAnnotation(Annotation3.class)); + } + + @Test + public void testEmptyElement() { + AnnotatedElement element = AnnotatedElementUtil.emptyElement(); + Assert.assertSame(element, AnnotatedElementUtil.emptyElement()); + Assert.assertNull(element.getAnnotation(Annotation1.class)); + Assert.assertEquals(0, element.getAnnotations().length); + Assert.assertEquals(0, element.getDeclaredAnnotations().length); + } + + // ================= super ================= + + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation4 {} + + @Annotation4 + private static class Super {}; + + // ================= interface ================= + + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation5 {} + + @Annotation5 + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation6 {} + + @Annotation6 + private interface Interface {}; + + // ================= foo ================= + + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation1 { + @Alias("alias") + String value() default ""; + @Alias("value") + String alias() default ""; + } + + @Annotation1 + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation2 { + int num() default Integer.MIN_VALUE; + } + + @Annotation2 + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.RUNTIME) + private @interface Annotation3 { + @Alias("alias") + String value() default ""; + @Alias("value") + String alias() default ""; + int num() default Integer.MIN_VALUE; + } + + @Annotation3(value = "foo", num = Integer.MAX_VALUE) + private static class Foo extends Super implements Interface {} + +}