Create a new API for handling merged annotations

Add new `MergedAnnotations` and `MergedAnnotation` interfaces that
attempt to provide a uniform way for dealing with merged annotations.

Specifically, the new API provides alternatives for the static methods
in `AnnotationUtils` and `AnnotatedElementUtils` and supports Spring's
comprehensive annotation attribute `@AliasFor` features. The interfaces
also open the possibility of the same API being exposed from the
`AnnotationMetadata` interface.

Additional utility classes for collecting, filtering and selecting
annotations have also been added.

Typical usage for the new API would be something like:

	MergedAnnotations.from(Example.class)
		.stream(MyAnnotation.class)
		.map(a -> a.getString("value"))
		.forEach(System.out::println);

Closes gh-21697
This commit is contained in:
Phillip Webb
2019-01-31 11:03:57 -08:00
committed by Juergen Hoeller
parent fdacda8b01
commit 4972d85ae0
35 changed files with 13436 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests to ensure back-compatibility with Spring Framework 5.1.
*
* @author Phillip Webb
* @since 5.2
*/
public class AnnotationBackCompatibiltyTests {
@Test
public void multiplRoutesToMetaAnnotation() {
Class<WithMetaMetaTestAnnotation1AndMetaTestAnnotation2> source = WithMetaMetaTestAnnotation1AndMetaTestAnnotation2.class;
// Merged annotation chooses lowest depth
MergedAnnotation<TestAnnotation> mergedAnnotation = MergedAnnotations.from(source).get(TestAnnotation.class);
assertThat(mergedAnnotation.getString("value")).isEqualTo("testAndMetaTest");
// AnnotatedElementUtils finds first
TestAnnotation previousVersion = AnnotatedElementUtils.getMergedAnnotation(source, TestAnnotation.class);
assertThat(previousVersion.value()).isEqualTo("metaTest");
}
@Test
public void defaultValue() {
DefaultValueAnnotation synthesized = MergedAnnotations.from(WithDefaultValue.class).get(DefaultValueAnnotation.class).synthesize();
assertThat(synthesized).isInstanceOf(SynthesizedAnnotation.class);
Object defaultValue = AnnotationUtils.getDefaultValue(synthesized, "enumValue");
assertThat(defaultValue).isEqualTo(TestEnum.ONE);
}
@Retention(RetentionPolicy.RUNTIME)
@interface TestAnnotation {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@TestAnnotation("metaTest")
@interface MetaTestAnnotation {
}
@Retention(RetentionPolicy.RUNTIME)
@TestAnnotation("testAndMetaTest")
@MetaTestAnnotation
@interface TestAndMetaTestAnnotation {
}
@Retention(RetentionPolicy.RUNTIME)
@MetaTestAnnotation
@interface MetaMetaTestAnnotation {
}
@MetaMetaTestAnnotation
@TestAndMetaTestAnnotation
static class WithMetaMetaTestAnnotation1AndMetaTestAnnotation2 {
}
@Retention(RetentionPolicy.RUNTIME)
@interface DefaultValueAnnotation {
@AliasFor("enumAlais")
TestEnum enumValue() default TestEnum.ONE;
@AliasFor("enumValue")
TestEnum enumAlais() default TestEnum.ONE;
}
@DefaultValueAnnotation
static class WithDefaultValue {
}
static enum TestEnum {
ONE,
TWO
}
}

View File

@@ -0,0 +1,191 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Test;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link AnnotationFilter}.
*
* @author Phillip Webb
*/
public class AnnotationFilterTests {
private static final AnnotationFilter FILTER = annotationType -> ObjectUtils.nullSafeEquals(
annotationType, TestAnnotation.class.getName());
@Test
public void matchesAnnotationWhenAnnotationIsNullReturnsFalse() {
TestAnnotation annotation = null;
assertThat(FILTER.matches(annotation)).isFalse();
}
@Test
public void matchesAnnotationWhenMatchReturnsTrue() {
TestAnnotation annotation = WithTestAnnotation.class.getDeclaredAnnotation(
TestAnnotation.class);
assertThat(FILTER.matches(annotation)).isTrue();
}
@Test
public void matchesAnnotationWhenNoMatchReturnsFalse() {
OtherAnnotation annotation = WithOtherAnnotation.class.getDeclaredAnnotation(
OtherAnnotation.class);
assertThat(FILTER.matches(annotation)).isFalse();
}
@Test
public void matchesAnnotationClassWhenAnnotationClassIsNullReturnsFalse() {
Class<Annotation> annotationType = null;
assertThat(FILTER.matches(annotationType)).isFalse();
}
@Test
public void matchesAnnotationClassWhenMatchReturnsTrue() {
Class<TestAnnotation> annotationType = TestAnnotation.class;
assertThat(FILTER.matches(annotationType)).isTrue();
}
@Test
public void matchesAnnotationClassWhenNoMatchReturnsFalse() {
Class<OtherAnnotation> annotationType = OtherAnnotation.class;
assertThat(FILTER.matches(annotationType)).isFalse();
}
@Test
public void plainWhenJavaLangAnnotationReturnsTrue() {
assertThat(AnnotationFilter.PLAIN.matches(Retention.class)).isTrue();
}
@Test
public void plainWhenSpringLangAnnotationReturnsTrue() {
assertThat(AnnotationFilter.PLAIN.matches(Nullable.class)).isTrue();
}
@Test
public void plainWhenOtherAnnotationReturnsFalse() {
assertThat(AnnotationFilter.PLAIN.matches(TestAnnotation.class)).isFalse();
}
@Test
public void javaWhenJavaLangAnnotationReturnsTrue() {
assertThat(AnnotationFilter.JAVA.matches(Retention.class)).isTrue();
}
@Test
public void javaWhenSpringLangAnnotationReturnsFalse() {
assertThat(AnnotationFilter.JAVA.matches(Nullable.class)).isFalse();
}
@Test
public void javaWhenOtherAnnotationReturnsFalse() {
assertThat(AnnotationFilter.JAVA.matches(TestAnnotation.class)).isFalse();
}
@Test
public void noneWhenNonNullReturnsFalse() {
assertThat(AnnotationFilter.NONE.matches(Retention.class)).isFalse();
assertThat(AnnotationFilter.NONE.matches(Nullable.class)).isFalse();
assertThat(AnnotationFilter.NONE.matches(TestAnnotation.class)).isFalse();
assertThat(AnnotationFilter.NONE.matches((Annotation) null)).isFalse();
assertThat(AnnotationFilter.NONE.matches((Class<Annotation>) null)).isFalse();
assertThat(AnnotationFilter.NONE.matches((String) null)).isFalse();
}
@Test
public void pacakgesReturnsPackagesAnnotationFilter() {
assertThat(AnnotationFilter.packages("com.example")).isInstanceOf(
PackagesAnnotationFilter.class);
}
@Test
public void mostAppropriateForCollectionWhenAnnotationTypesIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(
() -> AnnotationFilter.mostAppropriateFor(
(Collection<Class<? extends Annotation>>) null)).withMessage(
"AnnotationTypes must not be null");
}
@Test
public void mostAppropriateForCollectionReturnsPlainWhenPossible() {
AnnotationFilter filter = AnnotationFilter.mostAppropriateFor(
Arrays.asList(TestAnnotation.class, OtherAnnotation.class));
assertThat(filter).isSameAs(AnnotationFilter.PLAIN);
}
@Test
public void mostAppropriateForCollectionWhenCantUsePlainReturnsNone() {
AnnotationFilter filter = AnnotationFilter.mostAppropriateFor(Arrays.asList(
TestAnnotation.class, OtherAnnotation.class, Nullable.class));
assertThat(filter).isSameAs(AnnotationFilter.NONE);
}
@Test
public void mostAppropriateForArrayWhenAnnotationTypesIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(
() -> AnnotationFilter.mostAppropriateFor(
(Class<? extends Annotation>[]) null)).withMessage(
"AnnotationTypes must not be null");
}
@Test
public void mostAppropriateForArrayReturnsPlainWhenPossible() {
AnnotationFilter filter = AnnotationFilter.mostAppropriateFor(
TestAnnotation.class, OtherAnnotation.class);
assertThat(filter).isSameAs(AnnotationFilter.PLAIN);
}
@Test
public void mostAppropriateForArrayWhenCantUsePlainReturnsNone() {
AnnotationFilter filter = AnnotationFilter.mostAppropriateFor(
TestAnnotation.class, OtherAnnotation.class, Nullable.class);
assertThat(filter).isSameAs(AnnotationFilter.NONE);
}
@Retention(RetentionPolicy.RUNTIME)
static @interface TestAnnotation {
}
@TestAnnotation
static class WithTestAnnotation {
}
@Retention(RetentionPolicy.RUNTIME)
static @interface OtherAnnotation {
}
@OtherAnnotation
static class WithOtherAnnotation {
}
}

View File

@@ -78,6 +78,24 @@ public class AnnotationIntrospectionFailureTests {
exampleMetaAnnotationClass)).isTrue();
}
@Test
@SuppressWarnings("unchecked")
public void filteredTypeInMetaAnnotationWhenUsingMergedAnnotationsHandlesException() throws Exception {
FilteringClassLoader classLoader = new FilteringClassLoader(
getClass().getClassLoader());
Class<?> withExampleMetaAnnotation = ClassUtils.forName(
WithExampleMetaAnnotation.class.getName(), classLoader);
Class<Annotation> exampleAnnotationClass = (Class<Annotation>) ClassUtils.forName(
ExampleAnnotation.class.getName(), classLoader);
Class<Annotation> exampleMetaAnnotationClass = (Class<Annotation>) ClassUtils.forName(
ExampleMetaAnnotation.class.getName(), classLoader);
MergedAnnotations annotations = MergedAnnotations.from(withExampleMetaAnnotation);
assertThat(annotations.get(exampleAnnotationClass).isPresent()).isFalse();
assertThat(annotations.get(exampleMetaAnnotationClass).isPresent()).isFalse();
assertThat(annotations.isPresent(exampleMetaAnnotationClass)).isFalse();
assertThat(annotations.isPresent(exampleAnnotationClass)).isFalse();
}
static class FilteringClassLoader extends OverridingClassLoader {

View File

@@ -0,0 +1,825 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import org.junit.Test;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.*;
/**
* Tests for {@link AnnotationsScanner}.
*
* @author Phillip Webb
*/
public class AnnotationsScannerTests {
@Test
public void directStrategyOnClassWhenNotAnnoatedScansNone() {
Class<?> source = WithNoAnnotations.class;
assertThat(scan(source, SearchStrategy.DIRECT)).isEmpty();
}
@Test
public void directStrategyOnClassScansAnnotations() {
Class<?> source = WithSingleAnnotation.class;
assertThat(scan(source, SearchStrategy.DIRECT)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void directStrategyOnClassWhenMultipleAnnotationsScansAnnotations() {
Class<?> source = WithMultipleAnnotations.class;
assertThat(scan(source, SearchStrategy.DIRECT)).containsExactly(
"0:TestAnnotation1", "0:TestAnnotation2");
}
@Test
public void directStrategyOnClassWhenHasSuperclassScansOnlyDirect() {
Class<?> source = WithSingleSuperclass.class;
assertThat(scan(source, SearchStrategy.DIRECT)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void directStrategyOnClassWhenHasInterfaceScansOnlyDirect() {
Class<?> source = WithSingleInterface.class;
assertThat(scan(source, SearchStrategy.DIRECT)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void directStrategyOnClassHierarchyScansInCorrectOrder() {
Class<?> source = WithHierarchy.class;
assertThat(scan(source, SearchStrategy.DIRECT)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void inheritedAnnotationsStrategyOnClassWhenNotAnnoatedScansNone() {
Class<?> source = WithNoAnnotations.class;
assertThat(scan(source, SearchStrategy.INHERITED_ANNOTATIONS)).isEmpty();
}
@Test
public void inheritedAnnotationsStrategyOnClassScansAnnotations() {
Class<?> source = WithSingleAnnotation.class;
assertThat(scan(source, SearchStrategy.INHERITED_ANNOTATIONS)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void inheritedAnnotationsStrategyOnClassWhenMultipleAnnotationsScansAnnotations() {
Class<?> source = WithMultipleAnnotations.class;
assertThat(scan(source, SearchStrategy.INHERITED_ANNOTATIONS)).containsExactly(
"0:TestAnnotation1", "0:TestAnnotation2");
}
@Test
public void inheritedAnnotationsStrategyOnClassWhenHasSuperclassScansOnlyInherited() {
Class<?> source = WithSingleSuperclass.class;
assertThat(scan(source, SearchStrategy.INHERITED_ANNOTATIONS)).containsExactly(
"0:TestAnnotation1", "1:TestInheritedAnnotation2");
}
@Test
public void inheritedAnnotationsStrategyOnClassWhenHasInterfaceDoesNotIncludeInterfaces() {
Class<?> source = WithSingleInterface.class;
assertThat(scan(source, SearchStrategy.INHERITED_ANNOTATIONS)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void inheritedAnnotationsStrategyOnClassHierarchyScansInCorrectOrder() {
Class<?> source = WithHierarchy.class;
assertThat(scan(source, SearchStrategy.INHERITED_ANNOTATIONS)).containsExactly(
"0:TestAnnotation1", "1:TestInheritedAnnotation2");
}
@Test
public void inheritedAnnotationsStrategyOnClassWhenHasAnnotationOnBothClassesIncudesOnlyOne() {
Class<?> source = WithSingleSuperclassAndDoubleInherited.class;
assertThat(Arrays.stream(source.getAnnotations()).map(
Annotation::annotationType).map(Class::getName)).containsExactly(
TestInheritedAnnotation2.class.getName());
assertThat(scan(source, SearchStrategy.INHERITED_ANNOTATIONS)).containsOnly(
"0:TestInheritedAnnotation2");
}
@Test
public void superclassStrategyOnClassWhenNotAnnoatedScansNone() {
Class<?> source = WithNoAnnotations.class;
assertThat(scan(source, SearchStrategy.SUPER_CLASS)).isEmpty();
}
@Test
public void superclassStrategyOnClassScansAnnotations() {
Class<?> source = WithSingleAnnotation.class;
assertThat(scan(source, SearchStrategy.SUPER_CLASS)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void superclassStrategyOnClassWhenMultipleAnnotationsScansAnnotations() {
Class<?> source = WithMultipleAnnotations.class;
assertThat(scan(source, SearchStrategy.SUPER_CLASS)).containsExactly(
"0:TestAnnotation1", "0:TestAnnotation2");
}
@Test
public void superclassStrategyOnClassWhenHasSuperclassScansSuperclass() {
Class<?> source = WithSingleSuperclass.class;
assertThat(scan(source, SearchStrategy.SUPER_CLASS)).containsExactly(
"0:TestAnnotation1", "1:TestAnnotation2", "1:TestInheritedAnnotation2");
}
@Test
public void superclassStrategyOnClassWhenHasInterfaceDoesNotIncludeInterfaces() {
Class<?> source = WithSingleInterface.class;
assertThat(scan(source, SearchStrategy.SUPER_CLASS)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void superclassStrategyOnClassHierarchyScansInCorrectOrder() {
Class<?> source = WithHierarchy.class;
assertThat(scan(source, SearchStrategy.SUPER_CLASS)).containsExactly(
"0:TestAnnotation1", "1:TestAnnotation2", "1:TestInheritedAnnotation2",
"2:TestAnnotation3");
}
@Test
public void exhaustiveStrategyOnClassWhenNotAnnoatedScansNone() {
Class<?> source = WithNoAnnotations.class;
assertThat(scan(source, SearchStrategy.EXHAUSTIVE)).isEmpty();
}
@Test
public void exhaustiveStrategyOnClassScansAnnotations() {
Class<?> source = WithSingleAnnotation.class;
assertThat(scan(source, SearchStrategy.EXHAUSTIVE)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void exhaustiveStrategyOnClassWhenMultipleAnnotationsScansAnnotations() {
Class<?> source = WithMultipleAnnotations.class;
assertThat(scan(source, SearchStrategy.EXHAUSTIVE)).containsExactly(
"0:TestAnnotation1", "0:TestAnnotation2");
}
@Test
public void exhaustiveStrategyOnClassWhenHasSuperclassScansSuperclass() {
Class<?> source = WithSingleSuperclass.class;
assertThat(scan(source, SearchStrategy.EXHAUSTIVE)).containsExactly(
"0:TestAnnotation1", "1:TestAnnotation2", "1:TestInheritedAnnotation2");
}
@Test
public void exhaustiveStrategyOnClassWhenHasInterfaceDoesNotIncludeInterfaces() {
Class<?> source = WithSingleInterface.class;
assertThat(scan(source, SearchStrategy.EXHAUSTIVE)).containsExactly(
"0:TestAnnotation1", "1:TestAnnotation2", "1:TestInheritedAnnotation2");
}
@Test
public void exhaustiveStrategyOnClassHierarchyScansInCorrectOrder() {
Class<?> source = WithHierarchy.class;
assertThat(scan(source, SearchStrategy.EXHAUSTIVE)).containsExactly(
"0:TestAnnotation1", "1:TestAnnotation5", "1:TestInheritedAnnotation5",
"2:TestAnnotation6", "3:TestAnnotation2", "3:TestInheritedAnnotation2",
"4:TestAnnotation3", "5:TestAnnotation4");
}
@Test
public void directStrategyOnMethodWhenNotAnnoatedScansNone() {
Method source = methodFrom(WithNoAnnotations.class);
assertThat(scan(source, SearchStrategy.DIRECT)).isEmpty();
}
@Test
public void directStrategyOnMethodScansAnnotations() {
Method source = methodFrom(WithSingleAnnotation.class);
assertThat(scan(source, SearchStrategy.DIRECT)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void directStrategyOnMethodWhenMultipleAnnotationsScansAnnotations() {
Method source = methodFrom(WithMultipleAnnotations.class);
assertThat(scan(source, SearchStrategy.DIRECT)).containsExactly(
"0:TestAnnotation1", "0:TestAnnotation2");
}
@Test
public void directStrategyOnMethodWhenHasSuperclassScansOnlyDirect() {
Method source = methodFrom(WithSingleSuperclass.class);
assertThat(scan(source, SearchStrategy.DIRECT)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void directStrategyOnMethodWhenHasInterfaceScansOnlyDirect() {
Method source = methodFrom(WithSingleInterface.class);
assertThat(scan(source, SearchStrategy.DIRECT)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void directStrategyOnMethodHierarchyScansInCorrectOrder() {
Method source = methodFrom(WithHierarchy.class);
assertThat(scan(source, SearchStrategy.DIRECT)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void inheritedAnnotationsStrategyOnMethodWhenNotAnnoatedScansNone() {
Method source = methodFrom(WithNoAnnotations.class);
assertThat(scan(source, SearchStrategy.INHERITED_ANNOTATIONS)).isEmpty();
}
@Test
public void inheritedAnnotationsStrategyOnMethodScansAnnotations() {
Method source = methodFrom(WithSingleAnnotation.class);
assertThat(scan(source, SearchStrategy.INHERITED_ANNOTATIONS)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void inheritedAnnotationsStrategyOnMethodWhenMultipleAnnotationsScansAnnotations() {
Method source = methodFrom(WithMultipleAnnotations.class);
assertThat(scan(source, SearchStrategy.INHERITED_ANNOTATIONS)).containsExactly(
"0:TestAnnotation1", "0:TestAnnotation2");
}
@Test
public void inheritedAnnotationsMethodOnMethodWhenHasSuperclassIgnoresInherited() {
Method source = methodFrom(WithSingleSuperclass.class);
assertThat(scan(source, SearchStrategy.INHERITED_ANNOTATIONS)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void inheritedAnnotationsStrategyOnMethodWhenHasInterfaceDoesNotIncludeInterfaces() {
Method source = methodFrom(WithSingleInterface.class);
assertThat(scan(source, SearchStrategy.INHERITED_ANNOTATIONS)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void inheritedAnnotationsStrategyOnMethodHierarchyScansInCorrectOrder() {
Method source = methodFrom(WithHierarchy.class);
assertThat(scan(source, SearchStrategy.INHERITED_ANNOTATIONS)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void superclassStrategyOnMethodWhenNotAnnoatedScansNone() {
Method source = methodFrom(WithNoAnnotations.class);
assertThat(scan(source, SearchStrategy.SUPER_CLASS)).isEmpty();
}
@Test
public void superclassStrategyOnMethodScansAnnotations() {
Method source = methodFrom(WithSingleAnnotation.class);
assertThat(scan(source, SearchStrategy.SUPER_CLASS)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void superclassStrategyOnMethodWhenMultipleAnnotationsScansAnnotations() {
Method source = methodFrom(WithMultipleAnnotations.class);
assertThat(scan(source, SearchStrategy.SUPER_CLASS)).containsExactly(
"0:TestAnnotation1", "0:TestAnnotation2");
}
@Test
public void superclassStrategyOnMethodWhenHasSuperclassScansSuperclass() {
Method source = methodFrom(WithSingleSuperclass.class);
assertThat(scan(source, SearchStrategy.SUPER_CLASS)).containsExactly(
"0:TestAnnotation1", "1:TestAnnotation2", "1:TestInheritedAnnotation2");
}
@Test
public void superclassStrategyOnMethodWhenHasInterfaceDoesNotIncludeInterfaces() {
Method source = methodFrom(WithSingleInterface.class);
assertThat(scan(source, SearchStrategy.SUPER_CLASS)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void superclassStrategyOnMethodHierarchyScansInCorrectOrder() {
Method source = methodFrom(WithHierarchy.class);
assertThat(scan(source, SearchStrategy.SUPER_CLASS)).containsExactly(
"0:TestAnnotation1", "1:TestAnnotation2", "1:TestInheritedAnnotation2",
"2:TestAnnotation3");
}
@Test
public void exhaustiveStrategyOnMethodWhenNotAnnoatedScansNone() {
Method source = methodFrom(WithNoAnnotations.class);
assertThat(scan(source, SearchStrategy.EXHAUSTIVE)).isEmpty();
}
@Test
public void exhaustiveStrategyOnMethodScansAnnotations() {
Method source = methodFrom(WithSingleAnnotation.class);
assertThat(scan(source, SearchStrategy.EXHAUSTIVE)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void exhaustiveStrategyOnMethodWhenMultipleAnnotationsScansAnnotations() {
Method source = methodFrom(WithMultipleAnnotations.class);
assertThat(scan(source, SearchStrategy.EXHAUSTIVE)).containsExactly(
"0:TestAnnotation1", "0:TestAnnotation2");
}
@Test
public void exhaustiveStrategyOnMethodWhenHasSuperclassScansSuperclass() {
Method source = methodFrom(WithSingleSuperclass.class);
assertThat(scan(source, SearchStrategy.EXHAUSTIVE)).containsExactly(
"0:TestAnnotation1", "1:TestAnnotation2", "1:TestInheritedAnnotation2");
}
@Test
public void exhaustiveStrategyOnMethodWhenHasInterfaceDoesNotIncludeInterfaces() {
Method source = methodFrom(WithSingleInterface.class);
assertThat(scan(source, SearchStrategy.EXHAUSTIVE)).containsExactly(
"0:TestAnnotation1", "1:TestAnnotation2", "1:TestInheritedAnnotation2");
}
@Test
public void exhaustiveStrategyOnMethodHierarchyScansInCorrectOrder() {
Method source = methodFrom(WithHierarchy.class);
assertThat(scan(source, SearchStrategy.EXHAUSTIVE)).containsExactly(
"0:TestAnnotation1", "1:TestAnnotation5", "1:TestInheritedAnnotation5",
"2:TestAnnotation6", "3:TestAnnotation2", "3:TestInheritedAnnotation2",
"4:TestAnnotation3", "5:TestAnnotation4");
}
@Test
public void exhaustiveStrategyOnBridgeMethodScansAnnotations() throws Exception {
Method source = BridgedMethod.class.getDeclaredMethod("method", Object.class);
assertThat(source.isBridge()).isTrue();
assertThat(scan(source, SearchStrategy.EXHAUSTIVE)).containsExactly(
"0:TestAnnotation1", "1:TestAnnotation2");
}
@Test
public void exhaustiveStrategyOnBridgedMethodScansAnnotations() throws Exception {
Method source = BridgedMethod.class.getDeclaredMethod("method", String.class);
assertThat(source.isBridge()).isFalse();
assertThat(scan(source, SearchStrategy.EXHAUSTIVE)).containsExactly(
"0:TestAnnotation1", "1:TestAnnotation2");
}
@Test
public void directStrategyOnBridgeMethodScansAnnotations() throws Exception {
Method source = BridgedMethod.class.getDeclaredMethod("method", Object.class);
assertThat(source.isBridge()).isTrue();
assertThat(scan(source, SearchStrategy.DIRECT)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void dirextStrategyOnBridgedMethodScansAnnotations() throws Exception {
Method source = BridgedMethod.class.getDeclaredMethod("method", String.class);
assertThat(source.isBridge()).isFalse();
assertThat(scan(source, SearchStrategy.DIRECT)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void exhaustiveStrategyOnMethodWithIgnorablesScansAnnotations()
throws Exception {
Method source = methodFrom(Ignoreable.class);
assertThat(scan(source, SearchStrategy.EXHAUSTIVE)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void exhaustiveStrategyOnMethodWithMultipleCandidatesScansAnnotations()
throws Exception {
Method source = methodFrom(MultipleMethods.class);
assertThat(scan(source, SearchStrategy.EXHAUSTIVE)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void exhaustiveStrategyOnMethodWithGenericParameterOverrideScansAnnotations()
throws Exception {
Method source = ReflectionUtils.findMethod(GenericOverride.class, "method",
String.class);
assertThat(scan(source, SearchStrategy.EXHAUSTIVE)).containsExactly(
"0:TestAnnotation1", "1:TestAnnotation2");
}
@Test
public void exhaustiveStrategyOnMethodWithGenericParameterNonOverrideScansAnnotations()
throws Exception {
Method source = ReflectionUtils.findMethod(GenericNonOverride.class, "method",
StringBuilder.class);
assertThat(scan(source, SearchStrategy.EXHAUSTIVE)).containsExactly(
"0:TestAnnotation1");
}
@Test
public void scanWhenProcessorReturnsFromDoWithAggregateExitsEarly() {
String result = AnnotationsScanner.scan(this, WithSingleSuperclass.class,
SearchStrategy.EXHAUSTIVE, new AnnotationsProcessor<Object, String>() {
@Override
@Nullable
public String doWithAggregate(Object context, int aggregateIndex) {
return "";
}
@Override
@Nullable
public String doWithAnnotations(Object context, int aggregateIndex,
Object source, Annotation[] annotations) {
throw new IllegalStateException("Should not call");
}
});
assertThat(result).isEmpty();
}
@Test
public void scanWhenProcessorReturnsFromDoWithAnnotationsExitsEarly() {
List<Integer> indexes = new ArrayList<>();
String result = AnnotationsScanner.scan(this, WithSingleSuperclass.class,
SearchStrategy.EXHAUSTIVE,
(context, aggregateIndex, source, annotations) -> {
indexes.add(aggregateIndex);
return "";
});
assertThat(result).isEmpty();
assertThat(indexes).containsOnly(0);
}
@Test
public void scanWhenProcessorHasFinishMethodUsesFinishResult() {
String result = AnnotationsScanner.scan(this, WithSingleSuperclass.class,
SearchStrategy.EXHAUSTIVE, new AnnotationsProcessor<Object, String>() {
@Override
@Nullable
public String doWithAnnotations(Object context, int aggregateIndex,
Object source, Annotation[] annotations) {
return "K";
}
@Override
@Nullable
public String finish(String result) {
return "O" + result;
}
});
assertThat(result).isEqualTo("OK");
}
private Method methodFrom(Class<?> type) {
return ReflectionUtils.findMethod(type, "method");
}
private Stream<String> scan(AnnotatedElement element, SearchStrategy searchStrategy) {
List<String> result = new ArrayList<>();
AnnotationsScanner.scan(this, element, searchStrategy,
(criteria, aggregateIndex, source, annotations) -> {
for (Annotation annotation : annotations) {
if (annotation != null) {
String name = ClassUtils.getShortName(
annotation.annotationType());
name = name.substring(name.lastIndexOf(".") + 1);
result.add(aggregateIndex + ":" + name);
}
}
return null;
});
return result.stream();
}
@Retention(RetentionPolicy.RUNTIME)
static @interface TestAnnotation1 {
}
@Retention(RetentionPolicy.RUNTIME)
static @interface TestAnnotation2 {
}
@Retention(RetentionPolicy.RUNTIME)
static @interface TestAnnotation3 {
}
@Retention(RetentionPolicy.RUNTIME)
static @interface TestAnnotation4 {
}
@Retention(RetentionPolicy.RUNTIME)
static @interface TestAnnotation5 {
}
@Retention(RetentionPolicy.RUNTIME)
static @interface TestAnnotation6 {
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
static @interface TestInheritedAnnotation1 {
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
static @interface TestInheritedAnnotation2 {
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
static @interface TestInheritedAnnotation3 {
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
static @interface TestInheritedAnnotation4 {
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
static @interface TestInheritedAnnotation5 {
}
@Retention(RetentionPolicy.RUNTIME)
static @interface OnSuperClass {
}
@Retention(RetentionPolicy.RUNTIME)
static @interface OnInterface {
}
static class WithNoAnnotations {
public void method() {
}
}
@TestAnnotation1
static class WithSingleAnnotation {
@TestAnnotation1
public void method() {
}
}
@TestAnnotation1
@TestAnnotation2
static class WithMultipleAnnotations {
@TestAnnotation1
@TestAnnotation2
public void method() {
}
}
@TestAnnotation2
@TestInheritedAnnotation2
static class SingleSuperclass {
@TestAnnotation2
@TestInheritedAnnotation2
public void method() {
}
}
@TestAnnotation1
static class WithSingleSuperclass extends SingleSuperclass {
@TestAnnotation1
public void method() {
}
}
@TestInheritedAnnotation2
static class WithSingleSuperclassAndDoubleInherited extends SingleSuperclass {
@TestAnnotation1
public void method() {
}
}
@TestAnnotation1
static class WithSingleInterface implements SingleInterface {
@TestAnnotation1
public void method() {
}
}
@TestAnnotation2
@TestInheritedAnnotation2
static interface SingleInterface {
@TestAnnotation2
@TestInheritedAnnotation2
public void method();
}
@TestAnnotation1
static class WithHierarchy extends HierarchySuperclass implements HierarchyInterface {
@TestAnnotation1
public void method() {
}
}
@TestAnnotation2
@TestInheritedAnnotation2
static class HierarchySuperclass extends HierarchySuperSuperclass {
@TestAnnotation2
@TestInheritedAnnotation2
public void method() {
}
}
@TestAnnotation3
static class HierarchySuperSuperclass implements HierarchySuperSuperclassInterface {
@TestAnnotation3
public void method() {
}
}
@TestAnnotation4
static interface HierarchySuperSuperclassInterface {
@TestAnnotation4
public void method();
}
@TestAnnotation5
@TestInheritedAnnotation5
static interface HierarchyInterface extends HierarchyInterfaceInterface {
@TestAnnotation5
@TestInheritedAnnotation5
public void method();
}
@TestAnnotation6
static interface HierarchyInterfaceInterface {
@TestAnnotation6
public void method();
}
static class BridgedMethod implements BridgeMethod<String> {
@Override
@TestAnnotation1
public void method(String arg) {
}
}
static interface BridgeMethod<T> {
@TestAnnotation2
void method(T arg);
}
static class Ignoreable implements IgnoreableOverrideInterface1,
IgnoreableOverrideInterface2, Serializable {
@TestAnnotation1
public void method() {
}
}
static interface IgnoreableOverrideInterface1 {
@Nullable
public void method();
}
static interface IgnoreableOverrideInterface2 {
@Nullable
public void method();
}
static abstract class MultipleMethods implements MultipleMethodsInterface {
@TestAnnotation1
public void method() {
}
}
interface MultipleMethodsInterface {
@TestAnnotation2
void method(String arg);
@TestAnnotation2
void method1();
}
static class GenericOverride implements GenericOverrideInterface<String> {
@TestAnnotation1
public void method(String argument) {
}
}
static interface GenericOverrideInterface<T extends CharSequence> {
@TestAnnotation2
void method(T argument);
}
static abstract class GenericNonOverride
implements GenericNonOverrideInterface<String> {
@TestAnnotation1
public void method(StringBuilder argument) {
}
}
static interface GenericNonOverrideInterface<T extends CharSequence> {
@TestAnnotation2
void method(T argument);
}
}

View File

@@ -0,0 +1,242 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.BDDMockito.*;
/**
* Tests for {@link AttributeMethods}.
*
* @author Phillip Webb
*/
public class AttributeMethodsTests {
@Test
public void forAnnotationTypeWhenNullReturnsNone() {
AttributeMethods methods = AttributeMethods.forAnnotationType(null);
assertThat(methods).isSameAs(AttributeMethods.NONE);
}
@Test
public void forAnnotationTypeWhenHasNoAttributesReturnsNone() {
AttributeMethods methods = AttributeMethods.forAnnotationType(NoAttributes.class);
assertThat(methods).isSameAs(AttributeMethods.NONE);
}
@Test
public void forAnnotationTypeWhenHasMultipleAttributesReturnsAttributes() {
AttributeMethods methods = AttributeMethods.forAnnotationType(
MultipleAttributes.class);
assertThat(methods.get("value").getName()).isEqualTo("value");
assertThat(methods.get("intValue").getName()).isEqualTo("intValue");
assertThat(getAll(methods)).flatExtracting(Method::getName).containsExactly(
"intValue", "value");
}
@Test
public void isOnlyValueAttributeWhenHasOnlyValueAttributeReturnsTrue() {
AttributeMethods methods = AttributeMethods.forAnnotationType(ValueOnly.class);
assertThat(methods.isOnlyValueAttribute()).isTrue();
}
@Test
public void isOnlyValueAttributeWhenHasOnlySingleNonValueAttributeReturnsFalse() {
AttributeMethods methods = AttributeMethods.forAnnotationType(NonValueOnly.class);
assertThat(methods.isOnlyValueAttribute()).isFalse();
}
@Test
public void isOnlyValueAttributeWhenHasOnlyMultipleAttributesIncludingValueReturnsFalse() {
AttributeMethods methods = AttributeMethods.forAnnotationType(
MultipleAttributes.class);
assertThat(methods.isOnlyValueAttribute()).isFalse();
}
@Test
public void indexOfNameReturnsIndex() {
AttributeMethods methods = AttributeMethods.forAnnotationType(
MultipleAttributes.class);
assertThat(methods.indexOf("value")).isEqualTo(1);
}
@Test
public void indexOfMethodReturnsIndex() throws Exception {
AttributeMethods methods = AttributeMethods.forAnnotationType(
MultipleAttributes.class);
Method method = MultipleAttributes.class.getDeclaredMethod("value");
assertThat(methods.indexOf(method)).isEqualTo(1);
}
@Test
public void sizeReturnsSize() {
AttributeMethods methods = AttributeMethods.forAnnotationType(
MultipleAttributes.class);
assertThat(methods.size()).isEqualTo(2);
}
@Test
public void canThrowTypeNotPresentExceptionWhenHasClassAttributeReturnsTrue() {
AttributeMethods methods = AttributeMethods.forAnnotationType(ClassValue.class);
assertThat(methods.canThrowTypeNotPresentException(0)).isTrue();
}
@Test
public void canThrowTypeNotPresentExceptionWhenHasClassArrayAttributeReturnsTrue() {
AttributeMethods methods = AttributeMethods.forAnnotationType(
ClassArrayValue.class);
assertThat(methods.canThrowTypeNotPresentException(0)).isTrue();
}
@Test
public void canThrowTypeNotPresentExceptionWhenNotClassOrClassArrayAttributeReturnsFalse() {
AttributeMethods methods = AttributeMethods.forAnnotationType(ValueOnly.class);
assertThat(methods.canThrowTypeNotPresentException(0)).isFalse();
}
@Test
public void hasDefaultValueMethodWhenHasDefaultValueMethodReturnsTrue() {
AttributeMethods methods = AttributeMethods.forAnnotationType(
DefaultValueAttribute.class);
assertThat(methods.hasDefaultValueMethod()).isTrue();
}
@Test
public void hasDefaultValueMethodWhenHasNoDefaultValueMethodsReturnsFalse() {
AttributeMethods methods = AttributeMethods.forAnnotationType(
MultipleAttributes.class);
assertThat(methods.hasDefaultValueMethod()).isFalse();
}
@Test
public void isValidWhenHasTypeNotPresentExceptionReturnsFalse() {
ClassValue annotation = mockAnnotation(ClassValue.class);
given(annotation.value()).willThrow(TypeNotPresentException.class);
AttributeMethods attributes = AttributeMethods.forAnnotationType(
annotation.annotationType());
assertThat(attributes.isValid(annotation)).isFalse();
}
@Test
@SuppressWarnings({ "unchecked", "rawtypes" })
public void isValidWhenDoesNotHaveTypeNotPresentExceptionReturnsTrue() {
ClassValue annotation = mock(ClassValue.class);
given(annotation.value()).willReturn((Class) InputStream.class);
AttributeMethods attributes = AttributeMethods.forAnnotationType(
annotation.annotationType());
assertThat(attributes.isValid(annotation)).isTrue();
}
@Test
public void validateWhenHasTypeNotPresentExceptionThrowsException() {
ClassValue annotation = mockAnnotation(ClassValue.class);
given(annotation.value()).willThrow(TypeNotPresentException.class);
AttributeMethods attributes = AttributeMethods.forAnnotationType(
annotation.annotationType());
assertThatIllegalStateException().isThrownBy(
() -> attributes.validate(annotation));
}
@Test
@SuppressWarnings({ "unchecked", "rawtypes" })
public void validateWhenDoesNotHaveTypeNotPresentExceptionThrowsNothing() {
ClassValue annotation = mockAnnotation(ClassValue.class);
given(annotation.value()).willReturn((Class) InputStream.class);
AttributeMethods attributes = AttributeMethods.forAnnotationType(
annotation.annotationType());
attributes.validate(annotation);
}
private List<Method> getAll(AttributeMethods attributes) {
List<Method> result = new ArrayList<>(attributes.size());
for (int i = 0; i < attributes.size(); i++) {
result.add(attributes.get(i));
}
return result;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private <A extends Annotation> A mockAnnotation(Class<A> annotationType) {
A annotation = mock(annotationType);
given(annotation.annotationType()).willReturn((Class) annotationType);
return annotation;
}
@Retention(RetentionPolicy.RUNTIME)
static @interface NoAttributes {
}
@Retention(RetentionPolicy.RUNTIME)
static @interface MultipleAttributes {
int intValue();
String value();
}
@Retention(RetentionPolicy.RUNTIME)
static @interface ValueOnly {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
static @interface NonValueOnly {
String test();
}
@Retention(RetentionPolicy.RUNTIME)
static @interface ClassValue {
Class<?> value();
}
@Retention(RetentionPolicy.RUNTIME)
static @interface ClassArrayValue {
Class<?>[] value();
}
@Retention(RetentionPolicy.RUNTIME)
static @interface DefaultValueAttribute {
String one();
String two();
String three() default "3";
}
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import org.junit.Test;
import org.springframework.core.OverridingClassLoader;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link MergedAnnotation} to ensure the correct class loader is
* used.
*
* @author Phillip Webb
* @since 5.2
*/
public class MergedAnnotationClassLoaderTests {
private static final String TEST_ANNOTATION = TestAnnotation.class.getName();
private static final String TEST_META_ANNOTATION = TestMetaAnnotation.class.getName();
private static final String WITH_TEST_ANNOTATION = WithTestAnnotation.class.getName();
private static final String TEST_REFERENCE = TestReference.class.getName();
@Test
public void synthesizedUsesCorrectClassLoader() throws Exception {
ClassLoader parent = getClass().getClassLoader();
TestClassLoader child = new TestClassLoader(parent);
Class<?> source = child.loadClass(WITH_TEST_ANNOTATION);
Annotation annotation = getDeclaredAnnotation(source, TEST_ANNOTATION);
Annotation metaAnnotation = getDeclaredAnnotation(annotation.annotationType(),
TEST_META_ANNOTATION);
// We should have loaded the source and initial annotation from child
assertThat(source.getClassLoader()).isEqualTo(child);
assertThat(annotation.getClass().getClassLoader()).isEqualTo(child);
assertThat(annotation.annotationType().getClassLoader()).isEqualTo(child);
// The meta-annotation should have been loaded by the parent
assertThat(metaAnnotation.getClass().getClassLoader()).isEqualTo(parent);
assertThat(metaAnnotation.getClass().getClassLoader()).isEqualTo(parent);
assertThat(
getEnumAttribute(metaAnnotation).getClass().getClassLoader()).isEqualTo(
parent);
assertThat(getClassAttribute(metaAnnotation).getClassLoader()).isEqualTo(child);
// MergedAnnotation should follow the same class loader logic
MergedAnnotations mergedAnnotations = MergedAnnotations.from(source);
Annotation synthesized = mergedAnnotations.get(TEST_ANNOTATION).synthesize();
Annotation synthesizedMeta = mergedAnnotations.get(
TEST_META_ANNOTATION).synthesize();
assertThat(synthesized.getClass().getClassLoader()).isEqualTo(child);
assertThat(synthesized.annotationType().getClassLoader()).isEqualTo(child);
assertThat(synthesizedMeta.getClass().getClassLoader()).isEqualTo(parent);
assertThat(synthesizedMeta.getClass().getClassLoader()).isEqualTo(parent);
assertThat(getClassAttribute(synthesizedMeta).getClassLoader()).isEqualTo(child);
assertThat(
getEnumAttribute(synthesizedMeta).getClass().getClassLoader()).isEqualTo(
parent);
assertThat(synthesized).isEqualTo(annotation);
assertThat(synthesizedMeta).isEqualTo(metaAnnotation);
// Also check utils version
Annotation utilsMeta = AnnotatedElementUtils.getMergedAnnotation(source,
TestMetaAnnotation.class);
assertThat(utilsMeta.getClass().getClassLoader()).isEqualTo(parent);
assertThat(utilsMeta.getClass().getClassLoader()).isEqualTo(parent);
assertThat(getClassAttribute(utilsMeta).getClassLoader()).isEqualTo(child);
assertThat(getEnumAttribute(utilsMeta).getClass().getClassLoader()).isEqualTo(
parent);
assertThat(utilsMeta).isEqualTo(metaAnnotation);
}
private Class<?> getClassAttribute(Annotation annotation) throws Exception {
return (Class<?>) getAttributeValue(annotation, "classValue");
}
private Enum<?> getEnumAttribute(Annotation annotation) throws Exception {
return (Enum<?>) getAttributeValue(annotation, "enumValue");
}
private Object getAttributeValue(Annotation annotation, String name)
throws Exception {
Method classValueMethod = annotation.annotationType().getDeclaredMethod(name);
classValueMethod.setAccessible(true);
return classValueMethod.invoke(annotation);
}
private Annotation getDeclaredAnnotation(Class<?> element, String annotationType) {
for (Annotation annotation : element.getDeclaredAnnotations()) {
if (annotation.annotationType().getName().equals(annotationType)) {
return annotation;
}
}
return null;
}
private static class TestClassLoader extends OverridingClassLoader {
public TestClassLoader(ClassLoader parent) {
super(parent);
}
@Override
protected boolean isEligibleForOverriding(String className) {
return WITH_TEST_ANNOTATION.equals(className)
|| TEST_ANNOTATION.equals(className)
|| TEST_REFERENCE.equals(className);
}
}
@Retention(RetentionPolicy.RUNTIME)
static @interface TestMetaAnnotation {
@AliasFor("d")
String c() default "";
@AliasFor("c")
String d() default "";
Class<?> classValue();
TestEnum enumValue();
}
@TestMetaAnnotation(classValue = TestReference.class, enumValue = TestEnum.TWO)
@Retention(RetentionPolicy.RUNTIME)
static @interface TestAnnotation {
@AliasFor("b")
String a() default "";
@AliasFor("a")
String b() default "";
}
@TestAnnotation
static class WithTestAnnotation {
}
static class TestReference {
}
static enum TestEnum {
ONE, TWO, THREE
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
import org.junit.Test;
import org.springframework.core.annotation.MergedAnnotation.MapValues;
import org.springframework.util.MultiValueMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link MergedAnnotationCollectors}.
*
* @author Phillip Webb
*/
public class MergedAnnotationCollectorsTests {
@Test
public void toAnnotationSetCollectsLinkedHashSetWithSynthesizedAnnotations() {
Set<TestAnnotation> set = stream().collect(
MergedAnnotationCollectors.toAnnotationSet());
assertThat(set).isInstanceOf(LinkedHashSet.class).flatExtracting(
TestAnnotation::value).containsExactly("a", "b", "c");
assertThat(set).allMatch(SynthesizedAnnotation.class::isInstance);
}
@Test
public void toAnnotationArrayCollectsAnnotationArrayWithSynthesizedAnnotations() {
Annotation[] array = stream().collect(
MergedAnnotationCollectors.toAnnotationArray());
assertThat(Arrays.stream(array).map(
annotation -> ((TestAnnotation) annotation).value())).containsExactly("a",
"b", "c");
assertThat(array).allMatch(SynthesizedAnnotation.class::isInstance);
}
@Test
public void toSuppliedAnnotationArrayCollectsAnnotationArrayWithSynthesizedAnnotations() {
TestAnnotation[] array = stream().collect(
MergedAnnotationCollectors.toAnnotationArray(TestAnnotation[]::new));
assertThat(Arrays.stream(array).map(TestAnnotation::value)).containsExactly("a",
"b", "c");
assertThat(array).allMatch(SynthesizedAnnotation.class::isInstance);
}
@Test
public void toMultiValueMapCollectsMultiValueMap() {
MultiValueMap<String, Object> map = stream().map(
MergedAnnotation::filterDefaultValues).collect(
MergedAnnotationCollectors.toMultiValueMap(
MapValues.CLASS_TO_STRING));
assertThat(map.get("value")).containsExactly("a", "b", "c");
assertThat(map.get("extra")).containsExactly("java.lang.String",
"java.lang.Integer");
}
@Test
public void toFinishedMultiValueMapCollectsMultiValueMap() {
MultiValueMap<String, Object> map = stream().collect(
MergedAnnotationCollectors.toMultiValueMap(result -> {
result.add("finished", true);
return result;
}));
assertThat(map.get("value")).containsExactly("a", "b", "c");
assertThat(map.get("extra")).containsExactly(void.class, String.class,
Integer.class);
assertThat(map.get("finished")).containsExactly(true);
}
@Test
@SuppressWarnings({ "unchecked", "rawtypes" })
public void toFinishedMultiValueMapWhenFinisherIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> stream().collect(
MergedAnnotationCollectors.toMultiValueMap((Function) null))).withMessage(
"Finisher must not be null");
}
private Stream<MergedAnnotation<TestAnnotation>> stream() {
return MergedAnnotations.from(WithTestAnnotations.class).stream(
TestAnnotation.class);
}
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(TestAnnotations.class)
@interface TestAnnotation {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
Class<?> extra() default void.class;
}
@Retention(RetentionPolicy.RUNTIME)
@interface TestAnnotations {
TestAnnotation[] value();
}
@TestAnnotation("a")
@TestAnnotation(name = "b", extra = String.class)
@TestAnnotation(name = "c", extra = Integer.class)
static class WithTestAnnotations {
}
}

View File

@@ -0,0 +1,197 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link MergedAnnotationPredicates}.
*
* @author Phillip Webb
*/
public class MergedAnnotationPredicatesTests {
@Test
public void typeInStringArrayWhenNameMatchesAccepts() {
MergedAnnotation<TestAnnotation> annotation = MergedAnnotations.from(
WithTestAnnotation.class).get(TestAnnotation.class);
assertThat(MergedAnnotationPredicates.typeIn(
TestAnnotation.class.getName())).accepts(annotation);
}
@Test
public void typeInStringArrayWhenNameDoesNotMatchRejects() {
MergedAnnotation<TestAnnotation> annotation = MergedAnnotations.from(
WithTestAnnotation.class).get(TestAnnotation.class);
assertThat(MergedAnnotationPredicates.typeIn(
MissingAnnotation.class.getName())).rejects(annotation);
}
@Test
public void typeInStringArrayWhenStringArraysIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(
() -> MergedAnnotationPredicates.typeIn((String[]) null)).withMessage(
"TypeNames must not be null");
}
@Test
public void typeInClassArrayWhenNameMatchesAccepts() {
MergedAnnotation<TestAnnotation> annotation = MergedAnnotations.from(
WithTestAnnotation.class).get(TestAnnotation.class);
assertThat(MergedAnnotationPredicates.typeIn(TestAnnotation.class)).accepts(
annotation);
}
@Test
public void typeInClassArrayWhenNameDoesNotMatchRejects() {
MergedAnnotation<TestAnnotation> annotation = MergedAnnotations.from(
WithTestAnnotation.class).get(TestAnnotation.class);
assertThat(MergedAnnotationPredicates.typeIn(MissingAnnotation.class)).rejects(
annotation);
}
@Test
public void typeInClassArrayWhenClassArraysIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(
() -> MergedAnnotationPredicates.typeIn(
(Class<Annotation>[]) null)).withMessage(
"Types must not be null");
}
@Test
public void typeInCollectionWhenMatchesStringInCollectionAccepts() {
MergedAnnotation<TestAnnotation> annotation = MergedAnnotations.from(
WithTestAnnotation.class).get(TestAnnotation.class);
assertThat(MergedAnnotationPredicates.typeIn(
Collections.singleton(TestAnnotation.class.getName()))).accepts(
annotation);
}
@Test
public void typeInCollectionWhenMatchesClassInCollectionAccepts() {
MergedAnnotation<TestAnnotation> annotation = MergedAnnotations.from(
WithTestAnnotation.class).get(TestAnnotation.class);
assertThat(MergedAnnotationPredicates.typeIn(
Collections.singleton(TestAnnotation.class))).accepts(annotation);
}
@Test
public void typeInCollectionWhenDoesNotMatchAnyRejects() {
MergedAnnotation<TestAnnotation> annotation = MergedAnnotations.from(
WithTestAnnotation.class).get(TestAnnotation.class);
assertThat(MergedAnnotationPredicates.typeIn(Arrays.asList(
MissingAnnotation.class.getName(), MissingAnnotation.class))).rejects(
annotation);
}
@Test
public void typeInCollectionWhenCollectionIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(
() -> MergedAnnotationPredicates.typeIn(
(Collection<?>) null)).withMessage("Types must not be null");
}
@Test
public void firstRunOfAcceptsOnlyFirstRun() {
List<MergedAnnotation<TestAnnotation>> filtered = MergedAnnotations.from(
WithMultipleTestAnnotation.class).stream(TestAnnotation.class).filter(
MergedAnnotationPredicates.firstRunOf(
this::firstCharOfValue)).collect(Collectors.toList());
assertThat(filtered.stream().map(
annotation -> annotation.getString("value"))).containsExactly("a1", "a2",
"a3");
}
@Test
public void firstRunOfWhenValueExtractorIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(
() -> MergedAnnotationPredicates.firstRunOf(null)).withMessage(
"ValueExtractor must not be null");
}
@Test
public void uniqueAcceptsUniquely() {
List<MergedAnnotation<TestAnnotation>> filtered = MergedAnnotations.from(
WithMultipleTestAnnotation.class).stream(TestAnnotation.class).filter(
MergedAnnotationPredicates.unique(
this::firstCharOfValue)).collect(Collectors.toList());
assertThat(filtered.stream().map(
annotation -> annotation.getString("value"))).containsExactly("a1", "b1",
"c1");
}
@Test
public void uniqueWhenKeyExtractorIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(
() -> MergedAnnotationPredicates.unique(null)).withMessage(
"KeyExtractor must not be null");
}
private char firstCharOfValue(MergedAnnotation<TestAnnotation> annotation) {
return annotation.getString("value").charAt(0);
}
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(TestAnnotations.class)
static @interface TestAnnotation {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
static @interface TestAnnotations {
TestAnnotation[] value();
}
static @interface MissingAnnotation {
}
@TestAnnotation("test")
static class WithTestAnnotation {
}
@TestAnnotation("a1")
@TestAnnotation("a2")
@TestAnnotation("a3")
@TestAnnotation("b1")
@TestAnnotation("b2")
@TestAnnotation("b3")
@TestAnnotation("c1")
@TestAnnotation("c2")
@TestAnnotation("c3")
static class WithMultipleTestAnnotation {
}
}

View File

@@ -0,0 +1,306 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import org.junit.Test;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.*;
/**
* Unit tests that verify support for finding multiple composed annotations on a single
* annotated element.
*
* @author Phillip Webb
* @author Sam Brannen
*/
public class MergedAnnotationsComposedOnSingleAnnotatedElementTests {
// See SPR-13486
@Test
public void inheritedStrategyMultipleComposedAnnotationsOnClass() {
assertInheritedStrategyBehavior(MultipleComposedCachesClass.class);
}
@Test
public void inheritedStrategyMultipleInheritedComposedAnnotationsOnSuperclass() {
assertInheritedStrategyBehavior(SubMultipleComposedCachesClass.class);
}
@Test
public void inheritedStrategyMultipleNoninheritedComposedAnnotationsOnClass() {
MergedAnnotations annotations = MergedAnnotations.from(
MultipleNoninheritedComposedCachesClass.class,
SearchStrategy.INHERITED_ANNOTATIONS);
assertThat(stream(annotations, "value")).containsExactly("noninheritedCache1",
"noninheritedCache2");
}
@Test
public void inheritedStrategyMultipleNoninheritedComposedAnnotationsOnSuperclass() {
MergedAnnotations annotations = MergedAnnotations.from(
SubMultipleNoninheritedComposedCachesClass.class,
SearchStrategy.INHERITED_ANNOTATIONS);
assertThat(annotations.stream(Cacheable.class)).isEmpty();
}
@Test
public void inheritedStrategyComposedPlusLocalAnnotationsOnClass() {
assertInheritedStrategyBehavior(ComposedPlusLocalCachesClass.class);
}
@Test
public void inheritedStrategyMultipleComposedAnnotationsOnInterface() {
MergedAnnotations annotations = MergedAnnotations.from(
MultipleComposedCachesOnInterfaceClass.class,
SearchStrategy.INHERITED_ANNOTATIONS);
assertThat(annotations.stream(Cacheable.class)).isEmpty();
}
@Test
public void inheritedStrategyMultipleComposedAnnotationsOnMethod() throws Exception {
assertInheritedStrategyBehavior(
getClass().getDeclaredMethod("multipleComposedCachesMethod"));
}
@Test
public void inheritedStrategyComposedPlusLocalAnnotationsOnMethod() throws Exception {
assertInheritedStrategyBehavior(
getClass().getDeclaredMethod("composedPlusLocalCachesMethod"));
}
private void assertInheritedStrategyBehavior(AnnotatedElement element) {
MergedAnnotations annotations = MergedAnnotations.from(element,
SearchStrategy.INHERITED_ANNOTATIONS);
assertThat(stream(annotations, "key")).containsExactly("fooKey", "barKey");
assertThat(stream(annotations, "value")).containsExactly("fooCache", "barCache");
}
@Test
public void exhaustiveStrategyMultipleComposedAnnotationsOnClass() {
assertExhaustiveStrategyBehavior(MultipleComposedCachesClass.class);
}
@Test
public void exhaustiveStrategyMultipleInheritedComposedAnnotationsOnSuperclass() {
assertExhaustiveStrategyBehavior(SubMultipleComposedCachesClass.class);
}
@Test
public void exhaustiveStrategyMultipleNoninheritedComposedAnnotationsOnClass() {
MergedAnnotations annotations = MergedAnnotations.from(
MultipleNoninheritedComposedCachesClass.class, SearchStrategy.EXHAUSTIVE);
assertThat(stream(annotations, "value")).containsExactly("noninheritedCache1",
"noninheritedCache2");
}
@Test
public void exhaustiveStrategyMultipleNoninheritedComposedAnnotationsOnSuperclass() {
MergedAnnotations annotations = MergedAnnotations.from(
SubMultipleNoninheritedComposedCachesClass.class,
SearchStrategy.EXHAUSTIVE);
assertThat(stream(annotations, "value")).containsExactly("noninheritedCache1",
"noninheritedCache2");
}
@Test
public void exhaustiveStrategyComposedPlusLocalAnnotationsOnClass() {
assertExhaustiveStrategyBehavior(ComposedPlusLocalCachesClass.class);
}
@Test
public void exhaustiveStrategyMultipleComposedAnnotationsOnInterface() {
assertExhaustiveStrategyBehavior(MultipleComposedCachesOnInterfaceClass.class);
}
@Test
public void exhaustiveStrategyComposedCacheOnInterfaceAndLocalCacheOnClass() {
assertExhaustiveStrategyBehavior(
ComposedCacheOnInterfaceAndLocalCacheClass.class);
}
@Test
public void exhaustiveStrategyMultipleComposedAnnotationsOnMethod() throws Exception {
assertExhaustiveStrategyBehavior(
getClass().getDeclaredMethod("multipleComposedCachesMethod"));
}
@Test
public void exhaustiveStrategyComposedPlusLocalAnnotationsOnMethod()
throws Exception {
assertExhaustiveStrategyBehavior(
getClass().getDeclaredMethod("composedPlusLocalCachesMethod"));
}
@Test
public void exhaustiveStrategyMultipleComposedAnnotationsOnBridgeMethod()
throws Exception {
assertExhaustiveStrategyBehavior(getBridgeMethod());
}
private void assertExhaustiveStrategyBehavior(AnnotatedElement element) {
MergedAnnotations annotations = MergedAnnotations.from(element,
SearchStrategy.EXHAUSTIVE);
assertThat(stream(annotations, "key")).containsExactly("fooKey", "barKey");
assertThat(stream(annotations, "value")).containsExactly("fooCache", "barCache");
}
public Method getBridgeMethod() throws NoSuchMethodException {
List<Method> methods = new ArrayList<>();
ReflectionUtils.doWithLocalMethods(StringGenericParameter.class, method -> {
if ("getFor".equals(method.getName())) {
methods.add(method);
}
});
Method bridgeMethod = methods.get(0).getReturnType().equals(Object.class)
? methods.get(0)
: methods.get(1);
assertThat(bridgeMethod.isBridge()).isTrue();
return bridgeMethod;
}
private Stream<String> stream(MergedAnnotations annotations, String attributeName) {
return annotations.stream(Cacheable.class).map(
annotation -> annotation.getString(attributeName));
}
// @formatter:off
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface Cacheable {
@AliasFor("cacheName")
String value() default "";
@AliasFor("value")
String cacheName() default "";
String key() default "";
}
@Cacheable("fooCache")
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface FooCache {
@AliasFor(annotation = Cacheable.class)
String key() default "";
}
@Cacheable("barCache")
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface BarCache {
@AliasFor(annotation = Cacheable.class)
String key();
}
@Cacheable("noninheritedCache1")
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@interface NoninheritedCache1 {
@AliasFor(annotation = Cacheable.class)
String key() default "";
}
@Cacheable("noninheritedCache2")
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@interface NoninheritedCache2 {
@AliasFor(annotation = Cacheable.class)
String key() default "";
}
@FooCache(key = "fooKey")
@BarCache(key = "barKey")
private static class MultipleComposedCachesClass {
}
private static class SubMultipleComposedCachesClass
extends MultipleComposedCachesClass {
}
@NoninheritedCache1
@NoninheritedCache2
private static class MultipleNoninheritedComposedCachesClass {
}
private static class SubMultipleNoninheritedComposedCachesClass
extends MultipleNoninheritedComposedCachesClass {
}
@Cacheable(cacheName = "fooCache", key = "fooKey")
@BarCache(key = "barKey")
private static class ComposedPlusLocalCachesClass {
}
@FooCache(key = "fooKey")
@BarCache(key = "barKey")
private interface MultipleComposedCachesInterface {
}
private static class MultipleComposedCachesOnInterfaceClass implements MultipleComposedCachesInterface {
}
@BarCache(key = "barKey")
private interface ComposedCacheInterface {
}
@Cacheable(cacheName = "fooCache", key = "fooKey")
private static class ComposedCacheOnInterfaceAndLocalCacheClass implements ComposedCacheInterface {
}
@FooCache(key = "fooKey")
@BarCache(key = "barKey")
private void multipleComposedCachesMethod() {
}
@Cacheable(cacheName = "fooCache", key = "fooKey")
@BarCache(key = "barKey")
private void composedPlusLocalCachesMethod() {
}
public interface GenericParameter<T> {
T getFor(Class<T> cls);
}
@SuppressWarnings("unused")
private static class StringGenericParameter implements GenericParameter<String> {
@FooCache(key = "fooKey")
@BarCache(key = "barKey")
@Override
public String getFor(Class<String> cls) { return "foo"; }
public String getFor(Integer integer) { return "foo"; }
}
// @formatter:on
}

View File

@@ -0,0 +1,424 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.util.Set;
import org.assertj.core.api.ThrowableTypeAssert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link MergedAnnotations} and {@link RepeatableContainers} that
* verify support for repeatable annotations.
*
* @author Phillip Webb
* @author Sam Brannen
*/
public class MergedAnnotationsRepeatableAnnotationTests {
// See SPR-13973
@Rule
public ExpectedException exception = ExpectedException.none();
@Test
public void inheritedAnnotationsWhenNonRepeatableThrowsException() {
assertThatIllegalArgumentException().isThrownBy(
() -> getAnnotations(null, NonRepeatable.class,
SearchStrategy.INHERITED_ANNOTATIONS, getClass())).satisfies(
this::nonRepeatableRequirements);
}
@Test
public void inheritedAnnotationsWhenContainerMissingValueAttributeThrowsException() {
assertThatAnnotationConfigurationException().isThrownBy(
() -> getAnnotations(ContainerMissingValueAttribute.class,
InvalidRepeatable.class, SearchStrategy.INHERITED_ANNOTATIONS,
getClass())).satisfies(this::missingValueAttributeRequirements);
}
@Test
public void inheritedAnnotationsWhenWhenNonArrayValueAttributeThrowsException() {
assertThatAnnotationConfigurationException().isThrownBy(
() -> getAnnotations(ContainerWithNonArrayValueAttribute.class,
InvalidRepeatable.class, SearchStrategy.INHERITED_ANNOTATIONS,
getClass())).satisfies(this::nonArrayValueAttributeRequirements);
}
@Test
public void inheritedAnnotationsWhenWrongComponentTypeThrowsException() {
assertThatAnnotationConfigurationException().isThrownBy(() -> getAnnotations(
ContainerWithArrayValueAttributeButWrongComponentType.class,
InvalidRepeatable.class, SearchStrategy.INHERITED_ANNOTATIONS,
getClass())).satisfies(this::wrongComponentTypeRequirements);
}
@Test
public void inheritedAnnotationsWhenOnClassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.INHERITED_ANNOTATIONS, RepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
}
@Test
public void inheritedAnnotationsWhenWhenOnSuperclassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.INHERITED_ANNOTATIONS, SubRepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
}
@Test
public void inheritedAnnotationsWhenComposedOnClassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.INHERITED_ANNOTATIONS, ComposedRepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
}
@Test
public void inheritedAnnotationsWhenComposedMixedWithContainerOnClassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.INHERITED_ANNOTATIONS,
ComposedRepeatableMixedWithContainerClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
}
@Test
public void inheritedAnnotationsWhenComposedContainerForRepeatableOnClassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.INHERITED_ANNOTATIONS, ComposedContainerClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
}
@Test
public void inheritedAnnotationsWhenNoninheritedComposedRepeatableOnClassReturnsAnnotations() {
Set<Noninherited> annotations = getAnnotations(null, Noninherited.class,
SearchStrategy.INHERITED_ANNOTATIONS, NoninheritedRepeatableClass.class);
assertThat(annotations.stream().map(Noninherited::value)).containsExactly("A",
"B", "C");
}
@Test
public void inheritedAnnotationsWhenNoninheritedComposedRepeatableOnSuperclassReturnsAnnotations() {
Set<Noninherited> annotations = getAnnotations(null, Noninherited.class,
SearchStrategy.INHERITED_ANNOTATIONS,
SubNoninheritedRepeatableClass.class);
assertThat(annotations).isEmpty();
}
@Test
public void exhaustiveWhenNonRepeatableThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> getAnnotations(null,
NonRepeatable.class, SearchStrategy.EXHAUSTIVE, getClass())).satisfies(
this::nonRepeatableRequirements);
}
@Test
public void exhaustiveWhenContainerMissingValueAttributeThrowsException() {
assertThatAnnotationConfigurationException().isThrownBy(
() -> getAnnotations(ContainerMissingValueAttribute.class,
InvalidRepeatable.class, SearchStrategy.EXHAUSTIVE,
getClass())).satisfies(this::missingValueAttributeRequirements);
}
@Test
public void exhaustiveWhenWhenNonArrayValueAttributeThrowsException() {
assertThatAnnotationConfigurationException().isThrownBy(
() -> getAnnotations(ContainerWithNonArrayValueAttribute.class,
InvalidRepeatable.class, SearchStrategy.EXHAUSTIVE,
getClass())).satisfies(this::nonArrayValueAttributeRequirements);
}
@Test
public void exhaustiveWhenWrongComponentTypeThrowsException() {
assertThatAnnotationConfigurationException().isThrownBy(() -> getAnnotations(
ContainerWithArrayValueAttributeButWrongComponentType.class,
InvalidRepeatable.class, SearchStrategy.EXHAUSTIVE,
getClass())).satisfies(this::wrongComponentTypeRequirements);
}
@Test
public void exhaustiveWhenOnClassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.EXHAUSTIVE, RepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
}
@Test
public void exhaustiveWhenWhenOnSuperclassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.EXHAUSTIVE, SubRepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
}
@Test
public void exhaustiveWhenComposedOnClassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.EXHAUSTIVE, ComposedRepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
}
@Test
public void exhaustiveWhenComposedMixedWithContainerOnClassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.EXHAUSTIVE,
ComposedRepeatableMixedWithContainerClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
}
@Test
public void exhaustiveWhenComposedContainerForRepeatableOnClassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.EXHAUSTIVE, ComposedContainerClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
}
@Test
public void exhaustiveAnnotationsWhenNoninheritedComposedRepeatableOnClassReturnsAnnotations() {
Set<Noninherited> annotations = getAnnotations(null, Noninherited.class,
SearchStrategy.EXHAUSTIVE, NoninheritedRepeatableClass.class);
assertThat(annotations.stream().map(Noninherited::value)).containsExactly("A",
"B", "C");
}
@Test
public void exhaustiveAnnotationsWhenNoninheritedComposedRepeatableOnSuperclassReturnsAnnotations() {
Set<Noninherited> annotations = getAnnotations(null, Noninherited.class,
SearchStrategy.EXHAUSTIVE, SubNoninheritedRepeatableClass.class);
assertThat(annotations.stream().map(Noninherited::value)).containsExactly("A",
"B", "C");
}
private <A extends Annotation> Set<A> getAnnotations(
Class<? extends Annotation> container, Class<A> repeatable,
SearchStrategy searchStrategy, AnnotatedElement element) {
RepeatableContainers containers = RepeatableContainers.of(repeatable, container);
MergedAnnotations annotations = MergedAnnotations.from(element,
searchStrategy, containers, AnnotationFilter.PLAIN);
return annotations.stream(repeatable).collect(
MergedAnnotationCollectors.toAnnotationSet());
}
private void nonRepeatableRequirements(Exception ex) {
assertThat(ex.getMessage()).startsWith(
"Annotation type must be a repeatable annotation").contains(
"failed to resolve container type for",
NonRepeatable.class.getName());
}
private void missingValueAttributeRequirements(Exception ex) {
ex.printStackTrace();
assertThat(ex.getMessage()).startsWith(
"Invalid declaration of container type").contains(
ContainerMissingValueAttribute.class.getName(),
"for repeatable annotation", InvalidRepeatable.class.getName());
assertThat(ex).hasCauseInstanceOf(NoSuchMethodException.class);
}
private void nonArrayValueAttributeRequirements(Exception ex) {
assertThat(ex.getMessage()).startsWith("Container type").contains(
ContainerWithNonArrayValueAttribute.class.getName(),
"must declare a 'value' attribute for an array of type",
InvalidRepeatable.class.getName());
}
private void wrongComponentTypeRequirements(Exception ex) {
assertThat(ex.getMessage()).startsWith("Container type").contains(
ContainerWithArrayValueAttributeButWrongComponentType.class.getName(),
"must declare a 'value' attribute for an array of type",
InvalidRepeatable.class.getName());
}
private static ThrowableTypeAssert<AnnotationConfigurationException> assertThatAnnotationConfigurationException() {
return assertThatExceptionOfType(AnnotationConfigurationException.class);
}
@Retention(RetentionPolicy.RUNTIME)
@interface NonRepeatable {
}
@Retention(RetentionPolicy.RUNTIME)
@interface ContainerMissingValueAttribute {
// InvalidRepeatable[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@interface ContainerWithNonArrayValueAttribute {
InvalidRepeatable value();
}
@Retention(RetentionPolicy.RUNTIME)
@interface ContainerWithArrayValueAttributeButWrongComponentType {
String[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@interface InvalidRepeatable {
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface PeteRepeats {
PeteRepeat[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Repeatable(PeteRepeats.class)
@interface PeteRepeat {
String value();
}
@PeteRepeat("shadowed")
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface ForPetesSake {
@AliasFor(annotation = PeteRepeat.class)
String value();
}
@PeteRepeat("shadowed")
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface ForTheLoveOfFoo {
@AliasFor(annotation = PeteRepeat.class)
String value();
}
@PeteRepeats({ @PeteRepeat("B"), @PeteRepeat("C") })
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface ComposedContainer {
}
@PeteRepeat("A")
@PeteRepeats({ @PeteRepeat("B"), @PeteRepeat("C") })
static class RepeatableClass {
}
static class SubRepeatableClass extends RepeatableClass {
}
@ForPetesSake("B")
@ForTheLoveOfFoo("C")
@PeteRepeat("A")
static class ComposedRepeatableClass {
}
@ForPetesSake("C")
@PeteRepeats(@PeteRepeat("A"))
@PeteRepeat("B")
static class ComposedRepeatableMixedWithContainerClass {
}
@PeteRepeat("A")
@ComposedContainer
static class ComposedContainerClass {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Noninheriteds {
Noninherited[] value();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Noninheriteds.class)
@interface Noninherited {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
}
@Noninherited(name = "shadowed")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface ComposedNoninherited {
@AliasFor(annotation = Noninherited.class)
String name() default "";
}
@ComposedNoninherited(name = "C")
@Noninheriteds({ @Noninherited(value = "A"), @Noninherited(name = "B") })
static class NoninheritedRepeatableClass {
}
static class SubNoninheritedRepeatableClass extends NoninheritedRepeatableClass {
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link PackagesAnnotationFilter}.
*
* @author Phillip Webb
*/
public class PackagesAnnotationFilterTests {
@Test
public void createWhenPackagesIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(
() -> new PackagesAnnotationFilter((String[]) null)).withMessage(
"Packages must not be null");
}
@Test
public void createWhenPackagesContainsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(
() -> new PackagesAnnotationFilter((String) null)).withMessage(
"Package must not have empty elements");
}
@Test
public void createWhenPackagesContainsEmptyTextThrowsException() {
assertThatIllegalArgumentException().isThrownBy(
() -> new PackagesAnnotationFilter("")).withMessage(
"Package must not have empty elements");
}
@Test
public void matchesWhenInPackageReturnsTrue() {
PackagesAnnotationFilter filter = new PackagesAnnotationFilter("com.example");
assertThat(filter.matches("com.example.Component")).isTrue();
}
@Test
public void matchesWhenNotInPackageReturnsFalse() {
PackagesAnnotationFilter filter = new PackagesAnnotationFilter("com.example");
assertThat(filter.matches("org.springframework.sterotype.Component")).isFalse();
}
@Test
public void matchesWhenInSimilarPackageReturnsFalse() {
PackagesAnnotationFilter filter = new PackagesAnnotationFilter("com.example");
assertThat(filter.matches("com.examples.Component")).isFalse();
}
@Test
public void equalsAndHashCode() {
PackagesAnnotationFilter filter1 = new PackagesAnnotationFilter("com.example",
"org.springframework");
PackagesAnnotationFilter filter2 = new PackagesAnnotationFilter(
"org.springframework", "com.example");
PackagesAnnotationFilter filter3 = new PackagesAnnotationFilter("com.examples");
assertThat(filter1.hashCode()).isEqualTo(filter2.hashCode());
assertThat(filter1).isEqualTo(filter1).isEqualTo(filter2).isNotEqualTo(filter3);
}
}

View File

@@ -0,0 +1,283 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.junit.Test;
import static org.assertj.core.api.Assertions.*;
/**
* Tests for {@link RepeatableContainers}.
*
* @author Phillip Webb
*/
public class RepeatableContainersTests {
@Test
public void standardRepeatablesWhenNonRepeatableReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.standardRepeatables(), WithNonRepeatable.class,
NonRepeatable.class);
assertThat(values).isNull();
}
@Test
public void standardRepeatablesWhenSingleReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.standardRepeatables(),
WithSingleStandardRepeatable.class, StandardRepeatable.class);
assertThat(values).isNull();
}
@Test
public void standardRepeatablesWhenContainerReturnsRepeats() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.standardRepeatables(), WithStandardRepeatables.class,
StandardContainer.class);
assertThat(values).containsExactly("a", "b");
}
@Test
public void standardRepeatablesWhenContainerButNotRepeatableReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.standardRepeatables(), WithExplicitRepeatables.class,
ExplicitContainer.class);
assertThat(values).isNull();
}
@Test
public void ofExplicitWhenNonRepeatableReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.of(ExplicitRepeatable.class,
ExplicitContainer.class),
WithNonRepeatable.class, NonRepeatable.class);
assertThat(values).isNull();
}
@Test
public void ofExplicitWhenStandardRepeatableContainerReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.of(ExplicitRepeatable.class,
ExplicitContainer.class),
WithStandardRepeatables.class, StandardContainer.class);
assertThat(values).isNull();
}
@Test
public void ofExplicitWhenContainerReturnsRepeats() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.of(ExplicitRepeatable.class,
ExplicitContainer.class),
WithExplicitRepeatables.class, ExplicitContainer.class);
assertThat(values).containsExactly("a", "b");
}
@Test
public void ofExplicitWhenHasNoValueThrowsException() {
assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(
() -> RepeatableContainers.of(ExplicitRepeatable.class,
InvalidNoValue.class)).withMessageContaining(
"Invalid declaration of container type ["
+ InvalidNoValue.class.getName()
+ "] for repeatable annotation ["
+ ExplicitRepeatable.class.getName() + "]");
}
@Test
public void ofExplicitWhenValueIsNotArrayThrowsException() {
assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(
() -> RepeatableContainers.of(ExplicitRepeatable.class,
InvalidNotArray.class)).withMessage("Container type ["
+ InvalidNotArray.class.getName()
+ "] must declare a 'value' attribute for an array of type ["
+ ExplicitRepeatable.class.getName() + "]");
}
@Test
public void ofExplicitWhenValueIsArrayOfWrongTypeThrowsException() {
assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(
() -> RepeatableContainers.of(ExplicitRepeatable.class,
InvalidWrongArrayType.class)).withMessage("Container type ["
+ InvalidWrongArrayType.class.getName()
+ "] must declare a 'value' attribute for an array of type ["
+ ExplicitRepeatable.class.getName() + "]");
}
@Test
public void ofExplicitWhenAnnotationIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(
() -> RepeatableContainers.of(null, null)).withMessage(
"Repeatable must not be null");
}
@Test
public void ofExplicitWhenContainerIsNullDeducesContainer() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.of(StandardRepeatable.class, null),
WithStandardRepeatables.class, StandardContainer.class);
assertThat(values).containsExactly("a", "b");
}
@Test
public void ofExplicitWhenContainerIsNullAndNotRepeatableThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> RepeatableContainers.of(
ExplicitRepeatable.class, null)).withMessage(
"Annotation type must be a repeatable annotation: "
+ "failed to resolve container type for "
+ ExplicitRepeatable.class.getName());
}
@Test
public void standardAndExplicitReturnsRepeats() {
RepeatableContainers repeatableContainers = RepeatableContainers.standardRepeatables().and(
ExplicitContainer.class, ExplicitRepeatable.class);
assertThat(findRepeatedAnnotationValues(repeatableContainers,
WithStandardRepeatables.class, StandardContainer.class)).containsExactly(
"a", "b");
assertThat(findRepeatedAnnotationValues(repeatableContainers,
WithExplicitRepeatables.class, ExplicitContainer.class)).containsExactly(
"a", "b");
}
@Test
public void noneAlwaysReturnsNull() {
Object[] values = findRepeatedAnnotationValues(
RepeatableContainers.none(), WithStandardRepeatables.class,
StandardContainer.class);
assertThat(values).isNull();
}
@Test
public void equalsAndHashcode() {
RepeatableContainers c1 = RepeatableContainers.of(ExplicitRepeatable.class,
ExplicitContainer.class);
RepeatableContainers c2 = RepeatableContainers.of(ExplicitRepeatable.class,
ExplicitContainer.class);
RepeatableContainers c3 = RepeatableContainers.standardRepeatables();
RepeatableContainers c4 = RepeatableContainers.standardRepeatables().and(
ExplicitContainer.class, ExplicitRepeatable.class);
assertThat(c1.hashCode()).isEqualTo(c2.hashCode());
assertThat(c1).isEqualTo(c1).isEqualTo(c2);
assertThat(c1).isNotEqualTo(c3).isNotEqualTo(c4);
}
private Object[] findRepeatedAnnotationValues(RepeatableContainers containers,
Class<?> element, Class<? extends Annotation> annotationType) {
Annotation[] annotations = containers.findRepeatedAnnotations(
element.getAnnotation(annotationType));
return extractValues(annotations);
}
private Object[] extractValues(Annotation[] annotations) {
try {
if (annotations == null) {
return null;
}
Object[] result = new String[annotations.length];
for (int i = 0; i < annotations.length; i++) {
result[i] = annotations[i].annotationType().getMethod("value").invoke(
annotations[i]);
}
return result;
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
@Retention(RetentionPolicy.RUNTIME)
static @interface NonRepeatable {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(StandardContainer.class)
static @interface StandardRepeatable {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
static @interface StandardContainer {
StandardRepeatable[] value();
}
@Retention(RetentionPolicy.RUNTIME)
static @interface ExplicitRepeatable {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
static @interface ExplicitContainer {
ExplicitRepeatable[] value();
}
@Retention(RetentionPolicy.RUNTIME)
static @interface InvalidNoValue {
}
@Retention(RetentionPolicy.RUNTIME)
static @interface InvalidNotArray {
int value();
}
@Retention(RetentionPolicy.RUNTIME)
static @interface InvalidWrongArrayType {
StandardRepeatable[] value();
}
@NonRepeatable("a")
static class WithNonRepeatable {
}
@StandardRepeatable("a")
static class WithSingleStandardRepeatable {
}
@StandardRepeatable("a")
@StandardRepeatable("b")
static class WithStandardRepeatables {
}
@ExplicitContainer({ @ExplicitRepeatable("a"), @ExplicitRepeatable("b") })
static class WithExplicitRepeatables {
}
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.junit.Test;
import static org.assertj.core.api.Assertions.*;
/**
* Tests for {@link TypeMappedAnnotation}. See also
* {@link MergedAnnotationsTests} for a much more extensive collection of tests.
*
* @author Phillip Webb
*/
public class TypeMappedAnnotationTests {
@Test
public void mappingWhenMirroredReturnsMirroredValues() {
testExplicitMirror(WithExplicitMirrorA.class);
testExplicitMirror(WithExplicitMirrorB.class);
}
private void testExplicitMirror(Class<?> annotatedClass) {
TypeMappedAnnotation<ExplicitMirror> annotation = getTypeMappedAnnotation(
annotatedClass, ExplicitMirror.class);
assertThat(annotation.getString("a")).isEqualTo("test");
assertThat(annotation.getString("b")).isEqualTo("test");
}
@Test
public void mappingExplicitAliasToMetaAnnotationReturnsMappedValues() {
TypeMappedAnnotation<?> annotation = getTypeMappedAnnotation(
WithExplicitAliasToMetaAnnotation.class,
ExplicitAliasToMetaAnnotation.class,
ExplicitAliasMetaAnnotationTarget.class);
assertThat(annotation.getString("aliased")).isEqualTo("aliased");
assertThat(annotation.getString("nonAliased")).isEqualTo("nonAliased");
}
@Test
public void mappingConventionAliasToMetaAnnotationReturnsMappedValues() {
TypeMappedAnnotation<?> annotation = getTypeMappedAnnotation(
WithConventionAliasToMetaAnnotation.class,
ConventionAliasToMetaAnnotation.class,
ConventionAliasMetaAnnotationTarget.class);
assertThat(annotation.getString("value")).isEqualTo("");
assertThat(annotation.getString("convention")).isEqualTo("convention");
}
private <A extends Annotation> TypeMappedAnnotation<A> getTypeMappedAnnotation(
Class<?> source, Class<A> annotationType) {
return getTypeMappedAnnotation(source, annotationType, annotationType);
}
private <A extends Annotation> TypeMappedAnnotation<A> getTypeMappedAnnotation(
Class<?> source, Class<? extends Annotation> rootAnnotationType,
Class<A> annotationType) {
Annotation rootAnnotation = source.getAnnotation(rootAnnotationType);
AnnotationTypeMapping mapping = getMapping(rootAnnotation, annotationType);
return TypeMappedAnnotation.createIfPossible(mapping, source, rootAnnotation, 0, IntrospectionFailureLogger.INFO);
}
private AnnotationTypeMapping getMapping(Annotation annotation,
Class<? extends Annotation> mappedAnnotationType) {
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(
annotation.annotationType());
for (int i = 0; i < mappings.size(); i++) {
AnnotationTypeMapping candidate = mappings.get(i);
if (candidate.getAnnotationType().equals(mappedAnnotationType)) {
return candidate;
}
}
throw new IllegalStateException(
"No mapping from " + annotation + " to " + mappedAnnotationType);
}
@Retention(RetentionPolicy.RUNTIME)
static @interface ExplicitMirror {
@AliasFor("b")
String a() default "";
@AliasFor("a")
String b() default "";
}
@ExplicitMirror(a = "test")
static class WithExplicitMirrorA {
}
@ExplicitMirror(b = "test")
static class WithExplicitMirrorB {
}
@Retention(RetentionPolicy.RUNTIME)
@ExplicitAliasMetaAnnotationTarget(nonAliased = "nonAliased")
static @interface ExplicitAliasToMetaAnnotation {
@AliasFor(annotation = ExplicitAliasMetaAnnotationTarget.class)
String aliased() default "";
}
@Retention(RetentionPolicy.RUNTIME)
static @interface ExplicitAliasMetaAnnotationTarget {
String aliased() default "";
String nonAliased() default "";
}
@ExplicitAliasToMetaAnnotation(aliased = "aliased")
private static class WithExplicitAliasToMetaAnnotation {
}
@Retention(RetentionPolicy.RUNTIME)
@ConventionAliasMetaAnnotationTarget
static @interface ConventionAliasToMetaAnnotation {
String value() default "";
String convention() default "";
}
@Retention(RetentionPolicy.RUNTIME)
static @interface ConventionAliasMetaAnnotationTarget {
String value() default "";
String convention() default "";
}
@ConventionAliasToMetaAnnotation(value = "value", convention = "convention")
private static class WithConventionAliasToMetaAnnotation {
}
}