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:
committed by
Juergen Hoeller
parent
fdacda8b01
commit
4972d85ae0
@@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user