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等。 + * + *
当在注解中使用时,可为令多个属性互相关联,当对其中任意属性赋值时,
+ * 会将属性值一并同步到所有关联的属性中。 注解映射,用于包装并增强一个普通注解对象,
+ * 包装后的可以通过{@code getResolvedXXX}获得注解对象或属性值,
+ * 可以支持属性别名与属性覆写的属性解析机制。
+ *
+ * 父子注解
+ * 当实例创建时,可通过{@link #source}指定当前注解的子注解,多个实例通过该引用,
+ * 可以构成一条表示父子/元注解关系的单向链表。 属性别名
+ * 注解内的属性可以通过{@link Alias}互相关联,当解析时,
+ * 对绑定中的任意一个属性的赋值,会被同步给其他直接或者间接关联的属性。 属性覆写
+ * 当实例中{@link #source}不为{@code null},即当前注解存在至少一个或者多个子注解时,
+ * 若在子注解中的同名、同类型的属性,则获取值时将优先获取子注解的值,若该属性存在别名,则别名属性也如此。
+ * 该功能参考{@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
+ *
+ * 当{@link #isResolved()}为{@code false}时,则该方法应当被包装的原始注解对象,
+ * 即返回值应当与{@link #getAnnotation()}相同。
+ *
+ * @return 所需的注解,若{@link #isResolved()}为{@code false}则返回的是原始的注解对象
+ */
+ T getResolvedAnnotation();
+
+ /**
+ * 获取注解类型
+ *
+ * @return 注解类型
+ */
+ @Override
+ default Class extends Annotation> annotationType() {
+ return getAnnotation().annotationType();
+ }
+
+ /**
+ * 当前注解是否存在被解析的属性,当该值为{@code false}时,
+ * 通过{@code getResolvedAttributeValue}获得的值皆为注解的原始属性值,
+ * 通过{@link #getResolvedAnnotation()}获得注解对象为原始的注解对象。
+ *
+ * @return 是否
+ */
+ boolean isResolved();
+
+ /**
+ * 获取注解原始属性
+ *
+ * @return 注解属性
+ */
+ Method[] getAttributes();
+
+ /**
+ * 获取属性值
+ *
+ * @param attributeName 属性名称
+ * @param attributeType 属性类型
+ * @param
@@ -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 extends Annotation> annotationType) {
+ // TODO 改为通过带缓存的反射工具类完成
+ Objects.requireNonNull(annotationType);
+ return Stream.of(annotationType.getDeclaredMethods())
+ .filter(AnnotationUtil::isAnnotationAttribute)
+ .toArray(Method[]::new);
+ }
+
+ /**
+ * 该方法是否是注解属性,需要满足下述条件:
+ *
+ *
+ *
+ * @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
+ * 当{@link #source}为{@code null}时,认为当前注解即为根注解。
+ *
+ *
+ * eg: 若注解存在{@code a <=> b <=> c}的属性别名关系,则对a赋值,此时b、c也会被一并赋值。
+ *
+ *
+ * 属性覆写遵循如下机制:
+ *
+ *
+ *
+ * @author huangchengxing
+ * @see MetaAnnotatedElement
+ * @since 6.0.0
+ */
+public class ResolvedAnnotationMapping implements AnnotationMapping
+ * eg: 若注解存在{@code a <=> b <=> c}的属性别名关系,则覆写a,,属性b、c也会被覆写;
+ *
+ * eg:若从根注解a到元注解b有依赖关系{@code a => b => c},
+ * 此时若c中存在属性可同时被a、b覆写,则优先选择a;
+ *
+ * eg:若从根注解a到元注解b有依赖关系{@code a => b => c},
+ * 此时若b中存在属性被a覆写,而b中被a覆写的属性又覆写c中属性,
+ * 则等同于c中被覆写的属性直接被a覆写。
+ *
+ *
+ */
+ 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
+ *
+ *
+ */
+ 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 #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
+ *
+ */
+ 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 {};
+
+}