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/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赋值,此时b、c也会被一并赋值。
+ *
+ *
属性覆写
+ *
当实例中{@link #source}不为{@code null},即当前注解存在至少一个或者多个子注解时,
+ * 若在子注解中的同名、同类型的属性,则获取值时将优先获取子注解的值,若该属性存在别名,则别名属性也如此。
+ * 属性覆写遵循如下机制:
+ *
+ * -
+ * 当覆写的属性存在别名属性时,别名属性也会一并被覆写;
+ * 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覆写。
+ *
+ *
+ *
+ * @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/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 {}
+
+}
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/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; }
+ };
+
+}
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 {};
+
+}
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 {};
+
+}