Polish MergedAnnotation support

This commit is contained in:
Sam Brannen 2019-05-13 13:59:26 +02:00
parent cfc4a59135
commit 785e8d8116
7 changed files with 40 additions and 58 deletions

View File

@ -647,7 +647,7 @@ final class AnnotationTypeMapping {
!ObjectUtils.nullSafeEquals(lastValue, value)) { !ObjectUtils.nullSafeEquals(lastValue, value)) {
String on = (source != null) ? " declared on " + source : ""; String on = (source != null) ? " declared on " + source : "";
throw new AnnotationConfigurationException(String.format( throw new AnnotationConfigurationException(String.format(
"Different @AliasFor mirror values for annotation [%s]%s, attribute '%s' " + "Different @AliasFor mirror values for annotation [%s]%s; attribute '%s' " +
"and its alias '%s' are declared with values of [%s] and [%s].", "and its alias '%s' are declared with values of [%s] and [%s].",
getAnnotationType().getName(), on, getAnnotationType().getName(), on,
attributes.get(result).getName(), attributes.get(result).getName(),

View File

@ -89,11 +89,11 @@ final class AttributeMethods {
/** /**
* Determine if this instance only contains only a single attribute named * Determine if this instance only contains a single attribute named
* {@code value}. * {@code value}.
* @return {@code true} if this is only a value attribute * @return {@code true} if there is only a value attribute
*/ */
boolean isOnlyValueAttribute() { boolean hasOnlyValueAttribute() {
return (this.attributeMethods.length == 1 && return (this.attributeMethods.length == 1 &&
MergedAnnotation.VALUE.equals(this.attributeMethods[0].getName())); MergedAnnotation.VALUE.equals(this.attributeMethods[0].getName()));
} }

View File

@ -77,7 +77,7 @@ public interface MergedAnnotation<A extends Annotation> {
* Return a complete type hierarchy from this annotation to the * Return a complete type hierarchy from this annotation to the
* {@link #getRoot() root}. Provides a useful way to uniquely identify a * {@link #getRoot() root}. Provides a useful way to uniquely identify a
* merged annotation instance. * merged annotation instance.
* @return the type heirarchy for the annotation * @return the type hierarchy for the annotation
* @see MergedAnnotationPredicates#unique(Function) * @see MergedAnnotationPredicates#unique(Function)
*/ */
List<Class<? extends Annotation>> getTypeHierarchy(); List<Class<? extends Annotation>> getTypeHierarchy();

View File

@ -35,10 +35,10 @@ import org.springframework.lang.Nullable;
* *
* <ul> * <ul>
* <li>Explicit and Implicit {@link AliasFor @AliasFor} declarations on one or * <li>Explicit and Implicit {@link AliasFor @AliasFor} declarations on one or
* more attributes within the annotation.</li> * more attributes within the annotation</li>
* <li>Explicit {@link AliasFor @AliasFor} declarations for a meta-annotation.</li> * <li>Explicit {@link AliasFor @AliasFor} declarations for a meta-annotation</li>
* <li>Convention based attribute aliases for a meta-annotation</li> * <li>Convention based attribute aliases for a meta-annotation</li>
* <li>From a meta-annotation declaration.</li> * <li>From a meta-annotation declaration</li>
* </ul> * </ul>
* *
* <p>For example, a {@code @PostMapping} annotation might be defined as follows: * <p>For example, a {@code @PostMapping} annotation might be defined as follows:
@ -116,7 +116,7 @@ import org.springframework.lang.Nullable;
* // get all ExampleAnnotation declarations (including any meta-annotations) and * // get all ExampleAnnotation declarations (including any meta-annotations) and
* // print the merged "value" attributes * // print the merged "value" attributes
* mergedAnnotations.stream(ExampleAnnotation.class).map( * mergedAnnotations.stream(ExampleAnnotation.class).map(
* a -> a.getString("value")).forEach(System.out::println); * a -&gt; a.getString("value")).forEach(System.out::println);
* </pre> * </pre>
* *
* @author Phillip Webb * @author Phillip Webb

View File

@ -163,11 +163,8 @@ public abstract class RepeatableContainers {
private static Object computeRepeatedAnnotationsMethod(Class<? extends Annotation> annotationType) { private static Object computeRepeatedAnnotationsMethod(Class<? extends Annotation> annotationType) {
AttributeMethods methods = AttributeMethods.forAnnotationType(annotationType); AttributeMethods methods = AttributeMethods.forAnnotationType(annotationType);
if (methods.isOnlyValueAttribute()) { if (methods.hasOnlyValueAttribute()) {
Method method = methods.get("value"); Method method = methods.get(0);
if (method == null) {
return NONE;
}
Class<?> returnType = method.getReturnType(); Class<?> returnType = method.getReturnType();
if (returnType.isArray()) { if (returnType.isArray()) {
Class<?> componentType = returnType.getComponentType(); Class<?> componentType = returnType.getComponentType();
@ -201,7 +198,7 @@ public abstract class RepeatableContainers {
if (container == null) { if (container == null) {
container = deduceContainer(repeatable); container = deduceContainer(repeatable);
} }
Method valueMethod = AttributeMethods.forAnnotationType(container).get("value"); Method valueMethod = AttributeMethods.forAnnotationType(container).get(MergedAnnotation.VALUE);
try { try {
if (valueMethod == null) { if (valueMethod == null) {
throw new NoSuchMethodException("No value method found"); throw new NoSuchMethodException("No value method found");

View File

@ -394,7 +394,7 @@ public class AnnotationTypeMappingsTests {
.withMessage("Different @AliasFor mirror values for annotation [" .withMessage("Different @AliasFor mirror values for annotation ["
+ AliasPair.class.getName() + "] declared on " + AliasPair.class.getName() + "] declared on "
+ WithDifferentValueAliasPair.class.getName() + WithDifferentValueAliasPair.class.getName()
+ ", attribute 'a' and its alias 'b' are declared with values of [test1] and [test2]."); + "; attribute 'a' and its alias 'b' are declared with values of [test1] and [test2].");
} }
@Test @Test

View File

@ -52,52 +52,46 @@ public class AttributeMethodsTests {
@Test @Test
public void forAnnotationTypeWhenHasMultipleAttributesReturnsAttributes() { public void forAnnotationTypeWhenHasMultipleAttributesReturnsAttributes() {
AttributeMethods methods = AttributeMethods.forAnnotationType( AttributeMethods methods = AttributeMethods.forAnnotationType(MultipleAttributes.class);
MultipleAttributes.class);
assertThat(methods.get("value").getName()).isEqualTo("value"); assertThat(methods.get("value").getName()).isEqualTo("value");
assertThat(methods.get("intValue").getName()).isEqualTo("intValue"); assertThat(methods.get("intValue").getName()).isEqualTo("intValue");
assertThat(getAll(methods)).flatExtracting(Method::getName).containsExactly( assertThat(getAll(methods)).flatExtracting(Method::getName).containsExactly("intValue", "value");
"intValue", "value");
} }
@Test @Test
public void isOnlyValueAttributeWhenHasOnlyValueAttributeReturnsTrue() { public void hasOnlyValueAttributeWhenHasOnlyValueAttributeReturnsTrue() {
AttributeMethods methods = AttributeMethods.forAnnotationType(ValueOnly.class); AttributeMethods methods = AttributeMethods.forAnnotationType(ValueOnly.class);
assertThat(methods.isOnlyValueAttribute()).isTrue(); assertThat(methods.hasOnlyValueAttribute()).isTrue();
} }
@Test @Test
public void isOnlyValueAttributeWhenHasOnlySingleNonValueAttributeReturnsFalse() { public void hasOnlyValueAttributeWhenHasOnlySingleNonValueAttributeReturnsFalse() {
AttributeMethods methods = AttributeMethods.forAnnotationType(NonValueOnly.class); AttributeMethods methods = AttributeMethods.forAnnotationType(NonValueOnly.class);
assertThat(methods.isOnlyValueAttribute()).isFalse(); assertThat(methods.hasOnlyValueAttribute()).isFalse();
} }
@Test @Test
public void isOnlyValueAttributeWhenHasOnlyMultipleAttributesIncludingValueReturnsFalse() { public void hasOnlyValueAttributeWhenHasOnlyMultipleAttributesIncludingValueReturnsFalse() {
AttributeMethods methods = AttributeMethods.forAnnotationType( AttributeMethods methods = AttributeMethods.forAnnotationType(MultipleAttributes.class);
MultipleAttributes.class); assertThat(methods.hasOnlyValueAttribute()).isFalse();
assertThat(methods.isOnlyValueAttribute()).isFalse();
} }
@Test @Test
public void indexOfNameReturnsIndex() { public void indexOfNameReturnsIndex() {
AttributeMethods methods = AttributeMethods.forAnnotationType( AttributeMethods methods = AttributeMethods.forAnnotationType(MultipleAttributes.class);
MultipleAttributes.class);
assertThat(methods.indexOf("value")).isEqualTo(1); assertThat(methods.indexOf("value")).isEqualTo(1);
} }
@Test @Test
public void indexOfMethodReturnsIndex() throws Exception { public void indexOfMethodReturnsIndex() throws Exception {
AttributeMethods methods = AttributeMethods.forAnnotationType( AttributeMethods methods = AttributeMethods.forAnnotationType(MultipleAttributes.class);
MultipleAttributes.class);
Method method = MultipleAttributes.class.getDeclaredMethod("value"); Method method = MultipleAttributes.class.getDeclaredMethod("value");
assertThat(methods.indexOf(method)).isEqualTo(1); assertThat(methods.indexOf(method)).isEqualTo(1);
} }
@Test @Test
public void sizeReturnsSize() { public void sizeReturnsSize() {
AttributeMethods methods = AttributeMethods.forAnnotationType( AttributeMethods methods = AttributeMethods.forAnnotationType(MultipleAttributes.class);
MultipleAttributes.class);
assertThat(methods.size()).isEqualTo(2); assertThat(methods.size()).isEqualTo(2);
} }
@ -109,8 +103,7 @@ public class AttributeMethodsTests {
@Test @Test
public void canThrowTypeNotPresentExceptionWhenHasClassArrayAttributeReturnsTrue() { public void canThrowTypeNotPresentExceptionWhenHasClassArrayAttributeReturnsTrue() {
AttributeMethods methods = AttributeMethods.forAnnotationType( AttributeMethods methods = AttributeMethods.forAnnotationType(ClassArrayValue.class);
ClassArrayValue.class);
assertThat(methods.canThrowTypeNotPresentException(0)).isTrue(); assertThat(methods.canThrowTypeNotPresentException(0)).isTrue();
} }
@ -122,15 +115,13 @@ public class AttributeMethodsTests {
@Test @Test
public void hasDefaultValueMethodWhenHasDefaultValueMethodReturnsTrue() { public void hasDefaultValueMethodWhenHasDefaultValueMethodReturnsTrue() {
AttributeMethods methods = AttributeMethods.forAnnotationType( AttributeMethods methods = AttributeMethods.forAnnotationType(DefaultValueAttribute.class);
DefaultValueAttribute.class);
assertThat(methods.hasDefaultValueMethod()).isTrue(); assertThat(methods.hasDefaultValueMethod()).isTrue();
} }
@Test @Test
public void hasDefaultValueMethodWhenHasNoDefaultValueMethodsReturnsFalse() { public void hasDefaultValueMethodWhenHasNoDefaultValueMethodsReturnsFalse() {
AttributeMethods methods = AttributeMethods.forAnnotationType( AttributeMethods methods = AttributeMethods.forAnnotationType(MultipleAttributes.class);
MultipleAttributes.class);
assertThat(methods.hasDefaultValueMethod()).isFalse(); assertThat(methods.hasDefaultValueMethod()).isFalse();
} }
@ -138,8 +129,7 @@ public class AttributeMethodsTests {
public void isValidWhenHasTypeNotPresentExceptionReturnsFalse() { public void isValidWhenHasTypeNotPresentExceptionReturnsFalse() {
ClassValue annotation = mockAnnotation(ClassValue.class); ClassValue annotation = mockAnnotation(ClassValue.class);
given(annotation.value()).willThrow(TypeNotPresentException.class); given(annotation.value()).willThrow(TypeNotPresentException.class);
AttributeMethods attributes = AttributeMethods.forAnnotationType( AttributeMethods attributes = AttributeMethods.forAnnotationType(annotation.annotationType());
annotation.annotationType());
assertThat(attributes.isValid(annotation)).isFalse(); assertThat(attributes.isValid(annotation)).isFalse();
} }
@ -148,8 +138,7 @@ public class AttributeMethodsTests {
public void isValidWhenDoesNotHaveTypeNotPresentExceptionReturnsTrue() { public void isValidWhenDoesNotHaveTypeNotPresentExceptionReturnsTrue() {
ClassValue annotation = mock(ClassValue.class); ClassValue annotation = mock(ClassValue.class);
given(annotation.value()).willReturn((Class) InputStream.class); given(annotation.value()).willReturn((Class) InputStream.class);
AttributeMethods attributes = AttributeMethods.forAnnotationType( AttributeMethods attributes = AttributeMethods.forAnnotationType(annotation.annotationType());
annotation.annotationType());
assertThat(attributes.isValid(annotation)).isTrue(); assertThat(attributes.isValid(annotation)).isTrue();
} }
@ -157,10 +146,8 @@ public class AttributeMethodsTests {
public void validateWhenHasTypeNotPresentExceptionThrowsException() { public void validateWhenHasTypeNotPresentExceptionThrowsException() {
ClassValue annotation = mockAnnotation(ClassValue.class); ClassValue annotation = mockAnnotation(ClassValue.class);
given(annotation.value()).willThrow(TypeNotPresentException.class); given(annotation.value()).willThrow(TypeNotPresentException.class);
AttributeMethods attributes = AttributeMethods.forAnnotationType( AttributeMethods attributes = AttributeMethods.forAnnotationType(annotation.annotationType());
annotation.annotationType()); assertThatIllegalStateException().isThrownBy(() -> attributes.validate(annotation));
assertThatIllegalStateException().isThrownBy(() ->
attributes.validate(annotation));
} }
@Test @Test
@ -168,8 +155,7 @@ public class AttributeMethodsTests {
public void validateWhenDoesNotHaveTypeNotPresentExceptionThrowsNothing() { public void validateWhenDoesNotHaveTypeNotPresentExceptionThrowsNothing() {
ClassValue annotation = mockAnnotation(ClassValue.class); ClassValue annotation = mockAnnotation(ClassValue.class);
given(annotation.value()).willReturn((Class) InputStream.class); given(annotation.value()).willReturn((Class) InputStream.class);
AttributeMethods attributes = AttributeMethods.forAnnotationType( AttributeMethods attributes = AttributeMethods.forAnnotationType(annotation.annotationType());
annotation.annotationType());
attributes.validate(annotation); attributes.validate(annotation);
} }
@ -189,12 +175,11 @@ public class AttributeMethodsTests {
} }
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
static @interface NoAttributes { @interface NoAttributes {
} }
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
static @interface MultipleAttributes { @interface MultipleAttributes {
int intValue(); int intValue();
@ -203,35 +188,35 @@ public class AttributeMethodsTests {
} }
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
static @interface ValueOnly { @interface ValueOnly {
String value(); String value();
} }
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
static @interface NonValueOnly { @interface NonValueOnly {
String test(); String test();
} }
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
static @interface ClassValue { @interface ClassValue {
Class<?> value(); Class<?> value();
} }
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
static @interface ClassArrayValue { @interface ClassArrayValue {
Class<?>[] value(); Class<?>[] value();
} }
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
static @interface DefaultValueAttribute { @interface DefaultValueAttribute {
String one(); String one();