提交 0ace0be2 编写于 作者: P Phillip Webb

Fix annotation value mapping regression

Refine `TypeMappedAnnotation.getValueFromMetaAnnotation` to correctly
account for aliases when finding values from meta-annotations. Prior
to this commit, only convention based mappings were considered when
searching for values on meta-annotations. This meant that declared
meta-annotations that used aliases could return the "default value"
rather than the merged value.

Closes gh-22654
上级 88a2729f
......@@ -67,6 +67,10 @@ final class AnnotationTypeMapping {
private final int[] conventionMappings;
private final int[] annotationValueMappings;
private final AnnotationTypeMapping[] annotationValueSource;
private final Map<Method, List<Method>> aliasedBy;
private final Set<Method> claimedAliases = new HashSet<>();
......@@ -92,9 +96,12 @@ final class AnnotationTypeMapping {
this.mirrorSets = new MirrorSets();
this.aliasMappings = filledIntArray(this.attributes.size(), -1);
this.conventionMappings = filledIntArray(this.attributes.size(), -1);
this.annotationValueMappings = filledIntArray(this.attributes.size(), -1);
this.annotationValueSource = new AnnotationTypeMapping[this.attributes.size()];
this.aliasedBy = resolveAliasedForTargets();
processAliases();
addConventionMappings();
addConventionAnnotationValues();
}
......@@ -200,7 +207,7 @@ final class AnnotationTypeMapping {
aliases.add(this.attributes.get(i));
collectAliases(aliases);
if (aliases.size() > 1) {
processAliases(aliases);
processAliases(i, aliases);
}
}
}
......@@ -219,7 +226,7 @@ final class AnnotationTypeMapping {
}
}
private void processAliases(List<Method> aliases) {
private void processAliases(int attributeIndex, List<Method> aliases) {
int rootAttributeIndex = getFirstRootAttributeIndex(aliases);
AnnotationTypeMapping mapping = this;
while (mapping != null) {
......@@ -232,6 +239,16 @@ final class AnnotationTypeMapping {
}
mapping.mirrorSets.updateFrom(aliases);
mapping.claimedAliases.addAll(aliases);
if (mapping.annotation != null) {
int[] resolvedMirrors = mapping.mirrorSets.resolve(null,
mapping.annotation, ReflectionUtils::invokeMethod);
for (int i = 0; i < mapping.attributes.size(); i++) {
if (aliases.contains(mapping.attributes.get(i))) {
this.annotationValueMappings[attributeIndex] = resolvedMirrors[i];
this.annotationValueSource[attributeIndex] = mapping;
}
}
}
mapping = mapping.parent;
}
}
......@@ -267,6 +284,22 @@ final class AnnotationTypeMapping {
}
}
private void addConventionAnnotationValues() {
for (int i = 0; i < this.attributes.size(); i++) {
Method attribute = this.attributes.get(i);
AnnotationTypeMapping mapping = this;
while (mapping.depth > 0) {
int mapped = mapping.getAttributes().indexOf(attribute.getName());
if (mapped != -1 && (this.annotationValueMappings[i] == -1
|| this.annotationValueSource[i].depth > mapping.depth)) {
this.annotationValueMappings[i] = mapped;
this.annotationValueSource[i] = mapping;
}
mapping = mapping.parent;
}
}
}
/**
* Method called after all mappings have been set. At this point no further
* lookups from child mappings will occur.
......@@ -389,6 +422,26 @@ final class AnnotationTypeMapping {
return this.conventionMappings[attributeIndex];
}
/**
* Return a mapped attribute value from the most suitable
* {@link #getAnnotation() meta-annotation}. The resulting value is obtained
* from the closest meta-annotation, taking into consideration both
* convention and alias based mapping rules. For root mappings, this method
* will always return {@code null}.
* @param attributeIndex the attribute index of the source attribute
* @return the mapped annotation value, or {@code null}
*/
@Nullable
Object getMappedAnnotationValue(int attributeIndex) {
int mapped = this.annotationValueMappings[attributeIndex];
if (mapped == -1) {
return null;
}
AnnotationTypeMapping source = this.annotationValueSource[attributeIndex];
return ReflectionUtils.invokeMethod(source.attributes.get(mapped),
source.annotation);
}
/**
* Return if the specified value is equivalent to the default value of the
* attribute at the given index.
......@@ -615,5 +668,4 @@ final class AnnotationTypeMapping {
}
}
}
......@@ -140,7 +140,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
private Object getValueForMirrorResolution(Method attribute, Object annotation) {
int attributeIndex = this.mapping.getAttributes().indexOf(attribute);
boolean valueAttribute = VALUE.equals(attribute.getName());
return getValue(attributeIndex, !valueAttribute, false);
return getValue(attributeIndex, !valueAttribute, true);
}
@Override
......@@ -183,7 +183,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
@Override
public boolean hasDefaultValue(String attributeName) {
int attributeIndex = getAttributeIndex(attributeName, true);
Object value = getValue(attributeIndex, true, true);
Object value = getValue(attributeIndex, true, false);
return value == null || this.mapping.isEquivalentToDefaultValue(attributeIndex, value,
this.valueExtractor);
}
......@@ -371,7 +371,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
@Nullable
private <T> T getValue(int attributeIndex, Class<T> type) {
Method attribute = this.mapping.getAttributes().get(attributeIndex);
Object value = getValue(attributeIndex, true, true);
Object value = getValue(attributeIndex, true, false);
if (value == null) {
value = attribute.getDefaultValue();
}
......@@ -380,7 +380,8 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
@Nullable
private Object getValue(int attributeIndex, boolean useConventionMapping,
boolean resolveMirrors) {
boolean forMirrorResolution) {
AnnotationTypeMapping mapping = this.mapping;
if (this.useMergedValues) {
int mappedIndex = this.mapping.getAliasMapping(attributeIndex);
......@@ -392,7 +393,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
attributeIndex = mappedIndex;
}
}
if (resolveMirrors) {
if (!forMirrorResolution) {
attributeIndex = (mapping.getDepth() != 0 ?
this.resolvedMirrors :
this.resolvedRootMirrors)[attributeIndex];
......@@ -400,31 +401,22 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
if (attributeIndex == -1) {
return null;
}
Method attribute = mapping.getAttributes().get(attributeIndex);
if (mapping.getDepth() == 0) {
Method attribute = mapping.getAttributes().get(attributeIndex);
return this.valueExtractor.apply(attribute, this.rootAttributes);
}
return getValueFromMetaAnnotation(attribute);
return getValueFromMetaAnnotation(attributeIndex, forMirrorResolution);
}
@Nullable
private Object getValueFromMetaAnnotation(Method attribute) {
AnnotationTypeMapping mapping = this.mapping;
if (this.useMergedValues && !VALUE.equals(attribute.getName())) {
AnnotationTypeMapping candidate = mapping;
while (candidate != null && candidate.getDepth() > 0) {
int attributeIndex = candidate.getAttributes().indexOf(attribute.getName());
if (attributeIndex != -1) {
Method candidateAttribute = candidate.getAttributes().get(attributeIndex);
if (candidateAttribute.getReturnType().equals(attribute.getReturnType())) {
mapping = candidate;
attribute = candidateAttribute;
}
}
candidate = candidate.getParent();
}
private Object getValueFromMetaAnnotation(int attributeIndex,
boolean forMirrorResolution) {
if (this.useMergedValues && !forMirrorResolution) {
return this.mapping.getMappedAnnotationValue(attributeIndex);
}
return ReflectionUtils.invokeMethod(attribute, mapping.getAnnotation());
Method attribute = this.mapping.getAttributes().get(attributeIndex);
return ReflectionUtils.invokeMethod(attribute, this.mapping.getAnnotation());
}
@SuppressWarnings("unchecked")
......
......@@ -2036,6 +2036,25 @@ public class MergedAnnotationsTests {
assertThat(annotation.getString("text")).isEqualTo("metameta");
}
@Test // gh-22654
public void getValueWhenHasDefaultOverrideWithImplicitAlias() {
MergedAnnotation<?> annotation1 = MergedAnnotations.from(
DefaultOverrideImplicitAliasMetaClass1.class).get(DefaultOverrideRoot.class);
assertThat(annotation1.getString("text")).isEqualTo("alias-meta-1");
MergedAnnotation<?> annotation2 = MergedAnnotations.from(
DefaultOverrideImplicitAliasMetaClass2.class).get(DefaultOverrideRoot.class);
assertThat(annotation2.getString("text")).isEqualTo("alias-meta-2");
}
@Test // gh-22654
public void getValueWhenHasDefaultOverrideWithExplicitAlias() {
MergedAnnotation<?> annotation = MergedAnnotations.from(
DefaultOverrideExplicitAliasRootMetaMetaClass.class).get(
DefaultOverrideExplicitAliasRoot.class);
assertThat(annotation.getString("text")).isEqualTo("meta");
assertThat(annotation.getString("value")).isEqualTo("meta");
}
// @formatter:off
@Retention(RetentionPolicy.RUNTIME)
......@@ -3359,6 +3378,68 @@ public class MergedAnnotationsTests {
}
@Retention(RetentionPolicy.RUNTIME)
@DefaultOverrideRoot
@interface DefaultOverrideImplicitAlias {
@AliasFor(annotation=DefaultOverrideRoot.class, attribute="text")
String text1() default "alias";
@AliasFor(annotation=DefaultOverrideRoot.class, attribute="text")
String text2() default "alias";
}
@Retention(RetentionPolicy.RUNTIME)
@DefaultOverrideImplicitAlias(text1="alias-meta-1")
@interface DefaultOverrideAliasImplicitMeta1 {
}
@Retention(RetentionPolicy.RUNTIME)
@DefaultOverrideImplicitAlias(text2="alias-meta-2")
@interface DefaultOverrideImplicitAliasMeta2 {
}
@DefaultOverrideAliasImplicitMeta1
static class DefaultOverrideImplicitAliasMetaClass1 {
}
@DefaultOverrideImplicitAliasMeta2
static class DefaultOverrideImplicitAliasMetaClass2 {
}
@Retention(RetentionPolicy.RUNTIME)
@interface DefaultOverrideExplicitAliasRoot {
@AliasFor("value")
String text() default "";
@AliasFor("text")
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@DefaultOverrideExplicitAliasRoot("meta")
@interface DefaultOverrideExplicitAliasRootMeta {
}
@Retention(RetentionPolicy.RUNTIME)
@DefaultOverrideExplicitAliasRootMeta
@interface DefaultOverrideExplicitAliasRootMetaMeta {
}
@DefaultOverrideExplicitAliasRootMetaMeta
static class DefaultOverrideExplicitAliasRootMetaMetaClass {
}
// @formatter:on
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册