Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#352 Provide a workaround for spring-projects/spring-framework#24029
- Loading branch information
Showing
4 changed files
with
261 additions
and
2 deletions.
There are no files selected for viewing
94 changes: 94 additions & 0 deletions
94
spring/src/main/java/io/neba/spring/resourcemodels/registration/Annotations.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
/* | ||
Copyright 2013 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 io.neba.spring.resourcemodels.registration; | ||
|
||
import java.lang.annotation.Annotation; | ||
import java.lang.reflect.AnnotatedElement; | ||
import java.util.HashMap; | ||
import java.util.LinkedList; | ||
import java.util.Map; | ||
import java.util.Queue; | ||
|
||
import static java.util.Arrays.asList; | ||
import static java.util.Collections.addAll; | ||
|
||
/** | ||
* Supports meta-annotations by looking up annotations in the transitive | ||
* hull (annotations and their annotations, called meta-annotations) of a given | ||
* {@link AnnotatedElement}. | ||
* | ||
* @author Olaf Otto | ||
*/ | ||
public class Annotations { | ||
private final AnnotatedElement annotatedElement; | ||
private Map<Class<? extends Annotation>, Annotation> annotations = null; | ||
|
||
/** | ||
* @param annotatedElement must not be <code>null</code> | ||
* @return never null. | ||
*/ | ||
static Annotations annotations(AnnotatedElement annotatedElement) { | ||
if (annotatedElement == null) { | ||
throw new IllegalArgumentException("Method argument annotatedElement must not be null."); | ||
} | ||
return new Annotations(annotatedElement); | ||
} | ||
|
||
/** | ||
* @param annotatedElement must not be <code>null</code>. | ||
*/ | ||
private Annotations(AnnotatedElement annotatedElement) { | ||
if (annotatedElement == null) { | ||
throw new IllegalArgumentException("Constructor parameter annotatedElement must not be null."); | ||
} | ||
this.annotatedElement = annotatedElement; | ||
} | ||
|
||
/** | ||
* @param type must not be <code>null</code>. | ||
* @return the annotation if present on the given element or any meta-annotation thereof, or <code>null</code>. | ||
*/ | ||
@SuppressWarnings("unchecked") | ||
public <T extends Annotation> T get(Class<T> type) { | ||
if (type == null) { | ||
throw new IllegalArgumentException("Method argument type must not be null."); | ||
} | ||
|
||
return (T) getAnnotationMap().get(type); | ||
} | ||
|
||
/** | ||
* @return all annotations and meta-annotations present on the element. Never <code>null</code> but rather an empty map. | ||
*/ | ||
private Map<Class<? extends Annotation>, Annotation> getAnnotationMap() { | ||
if (this.annotations == null) { | ||
// We do not care about calculating the same thing twice in case of concurrent access. | ||
HashMap<Class<? extends Annotation>, Annotation> annotations = new HashMap<>(); | ||
Queue<Annotation> queue = new LinkedList<>(asList(this.annotatedElement.getAnnotations())); | ||
while (!queue.isEmpty()) { | ||
Annotation annotation = queue.remove(); | ||
// Prevent lookup loops (@A annotated with @B annotated with @A ...) | ||
if (!annotations.containsKey(annotation.annotationType())) { | ||
annotations.put(annotation.annotationType(), annotation); | ||
addAll(queue, annotation.annotationType().getAnnotations()); | ||
} | ||
} | ||
this.annotations = annotations; | ||
} | ||
|
||
return this.annotations; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
99 changes: 99 additions & 0 deletions
99
spring/src/test/java/io/neba/spring/resourcemodels/registration/AnnotationsTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
/* | ||
Copyright 2013 the original author or authors. | ||
<p> | ||
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 | ||
<p> | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
<p> | ||
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 io.neba.spring.resourcemodels.registration; | ||
|
||
import org.junit.Test; | ||
|
||
import java.lang.annotation.Annotation; | ||
import java.lang.annotation.Retention; | ||
|
||
import static io.neba.spring.resourcemodels.registration.Annotations.annotations; | ||
import static java.lang.annotation.RetentionPolicy.RUNTIME; | ||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
public class AnnotationsTest { | ||
@TestAnnotation | ||
private static class TestType { | ||
} | ||
|
||
@Retention(RUNTIME) | ||
@MetaAnnotation | ||
private @interface TestAnnotation { | ||
} | ||
|
||
@Retention(RUNTIME) | ||
@CyclicAnnotation | ||
private @interface MetaAnnotation { | ||
} | ||
|
||
@Retention(RUNTIME) | ||
@MetaAnnotation | ||
@CyclicAnnotation | ||
private @interface CyclicAnnotation { | ||
} | ||
|
||
private Annotations testee = annotations(TestType.class); | ||
|
||
@Test | ||
public void testDetectionOfDirectAnnotation() { | ||
assertAnnotationIsPresent(TestAnnotation.class); | ||
assertAnnotationInstanceCanBeObtained(TestAnnotation.class); | ||
} | ||
|
||
@Test | ||
public void testDetectionOfMetaAnnotations() { | ||
assertAnnotationIsPresent(MetaAnnotation.class); | ||
assertAnnotationInstanceCanBeObtained(MetaAnnotation.class); | ||
} | ||
|
||
@Test | ||
public void testDetectionOfCyclicMetaAnnotation() { | ||
assertAnnotationIsPresent(CyclicAnnotation.class); | ||
assertAnnotationInstanceCanBeObtained(CyclicAnnotation.class); | ||
} | ||
|
||
|
||
@Test(expected = IllegalArgumentException.class) | ||
public void testHandlingOfNullElementArgumentForLookup() { | ||
this.testee.get(null); | ||
} | ||
|
||
|
||
@Test(expected = IllegalArgumentException.class) | ||
public void testHandlingOfNullTypeArgumentForConstructor() { | ||
annotations(null); | ||
} | ||
|
||
@Test | ||
public void testGetAnnotations() { | ||
assertAnnotationsAre(MetaAnnotation.class, CyclicAnnotation.class); | ||
} | ||
|
||
@SafeVarargs | ||
private final void assertAnnotationsAre(Class<? extends Annotation>... annotations) { | ||
for (Class<? extends Annotation> annotationType : annotations) { | ||
assertThat(this.testee.get(annotationType)).isNotNull(); | ||
} | ||
} | ||
|
||
private void assertAnnotationInstanceCanBeObtained(Class<? extends Annotation> type) { | ||
assertThat(this.testee.get(type)).isInstanceOf(type); | ||
} | ||
|
||
private void assertAnnotationIsPresent(Class<? extends Annotation> type) { | ||
assertThat(this.testee.get(type)).isNotNull(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters