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 {}; + +}