This is an automated email from the ASF dual-hosted git repository. mattsicker pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit e799945d785d9510d065c30d6a349cd9b7c86dc7 Author: Matt Sicker <[email protected]> AuthorDate: Sun May 22 12:54:07 2022 -0500 Add annotation stereotype utils - Updates AnnotationUtil::getMetaAnnotation to support meta-annotation stereotypes - Adds AnnotationUtil::getLogicalAnnotation to support annotation stereotypes Signed-off-by: Matt Sicker <[email protected]> --- .../log4j/plugins/util/AnnotationUtilTest.java | 142 +++++++++++++++++++++ .../logging/log4j/plugins/util/AnnotationUtil.java | 54 +++++++- 2 files changed, 192 insertions(+), 4 deletions(-) diff --git a/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/util/AnnotationUtilTest.java b/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/util/AnnotationUtilTest.java new file mode 100644 index 0000000000..eaaee492a4 --- /dev/null +++ b/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/util/AnnotationUtilTest.java @@ -0,0 +1,142 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.logging.log4j.plugins.util; + +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +class AnnotationUtilTest { + + @Retention(RetentionPolicy.RUNTIME) + @interface MetaAnnotation { + } + + @Retention(RetentionPolicy.RUNTIME) + @MetaAnnotation + @interface StereotypeAnnotation { + } + + @Retention(RetentionPolicy.RUNTIME) + @StereotypeAnnotation + @interface AliasedStereotypeAnnotation { + } + + @StereotypeAnnotation + static class HasMetaAnnotation { + } + + @AliasedStereotypeAnnotation + static class HasAliasedMetaAnnotation { + } + + @TestFactory + Stream<DynamicTest> isMetaAnnotationPresent() { + return Stream.of(HasMetaAnnotation.class, HasAliasedMetaAnnotation.class).map(clazz -> DynamicTest.dynamicTest( + "isMetaAnnotationPresent(" + clazz.getSimpleName() + ", MetaAnnotation.class)", + () -> assertTrue(AnnotationUtil.isMetaAnnotationPresent(clazz, MetaAnnotation.class)))); + } + + @TestFactory + Stream<DynamicTest> getMetaAnnotation() { + return Stream.of(HasMetaAnnotation.class, HasAliasedMetaAnnotation.class) + .map(clazz -> DynamicTest.dynamicTest("getMetaAnnotation(" + clazz.getSimpleName() + ", MetaAnnotation.class)", + () -> { + final Annotation annotation = AnnotationUtil.getMetaAnnotation(clazz, MetaAnnotation.class); + assertNotNull(annotation); + assertEquals(StereotypeAnnotation.class, annotation.annotationType()); + })); + } + + @Retention(RetentionPolicy.RUNTIME) + @interface LogicalAnnotation { + } + + @Retention(RetentionPolicy.RUNTIME) + @LogicalAnnotation + @interface AliasedAnnotation { + } + + @Retention(RetentionPolicy.RUNTIME) + @AliasedAnnotation + @interface MetaAliasedAnnotation { + } + + @LogicalAnnotation + static class HasLogicalAnnotation { + } + + @AliasedAnnotation + static class HasAliasedAnnotation { + } + + @MetaAliasedAnnotation + static class HasMetaAliasedAnnotation { + } + + @TestFactory + Stream<DynamicTest> getLogicalAnnotation() { + return Stream.of(HasLogicalAnnotation.class, HasAliasedAnnotation.class, HasMetaAliasedAnnotation.class) + .map(clazz -> DynamicTest.dynamicTest(clazz.getSimpleName(), () -> { + final LogicalAnnotation annotation = AnnotationUtil.getLogicalAnnotation(clazz, LogicalAnnotation.class); + assertNotNull(annotation); + })); + } + + interface AnnotatedMethod { + void method(); + } + + static class FirstMethod implements AnnotatedMethod { + @Override + @LogicalAnnotation + public void method() { + } + } + + static class SecondMethod implements AnnotatedMethod { + @Override + @AliasedAnnotation + public void method() { + } + } + + static class ThirdMethod implements AnnotatedMethod { + @Override + @MetaAliasedAnnotation + public void method() { + } + } + + @TestFactory + Stream<DynamicTest> getLogicalAnnotationOnMethod() { + return Stream.of(FirstMethod.class, SecondMethod.class, ThirdMethod.class) + .map(clazz -> DynamicTest.dynamicTest(clazz.getSimpleName(), () -> { + final Method method = assertDoesNotThrow(() -> clazz.getMethod("method")); + final LogicalAnnotation annotation = AnnotationUtil.getLogicalAnnotation(method, LogicalAnnotation.class); + assertNotNull(annotation); + })); + } +} diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/AnnotationUtil.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/AnnotationUtil.java index ee7bdb4107..ada462c5bc 100644 --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/AnnotationUtil.java +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/AnnotationUtil.java @@ -19,17 +19,63 @@ package org.apache.logging.log4j.plugins.util; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; +import java.util.HashSet; +import java.util.Set; public final class AnnotationUtil { - public static boolean isMetaAnnotationPresent(final AnnotatedElement element, final Class<? extends Annotation> metaAnnotation) { + public static boolean isMetaAnnotationPresent( + final AnnotatedElement element, final Class<? extends Annotation> metaAnnotation) { return getMetaAnnotation(element, metaAnnotation) != null; } - public static Annotation getMetaAnnotation(final AnnotatedElement element, final Class<? extends Annotation> metaAnnotation) { + public static Annotation getMetaAnnotation( + final AnnotatedElement element, final Class<? extends Annotation> metaAnnotation) { + return findMetaAnnotation(element, metaAnnotation, new HashSet<>()); + } + + private static Annotation findMetaAnnotation( + final AnnotatedElement element, final Class<? extends Annotation> metaAnnotation, + final Set<Class<? extends Annotation>> seen) { + for (final Annotation annotation : element.getAnnotations()) { + final Class<? extends Annotation> annotationType = annotation.annotationType(); + if (seen.add(annotationType)) { + if (annotationType.isAnnotationPresent(metaAnnotation)) { + return annotation; + } + final Annotation original = findMetaAnnotation(annotationType, metaAnnotation, seen); + if (original != null) { + return original; + } + } + } + return null; + } + + /** + * Gets an annotation from an annotated element where said annotation may be indirectly present by means of a stereotype + * annotation or directly present by using the annotation directly. For example, if {@code Foo} is an annotation and + * {@code Bar} is another annotation that is annotated with {@code Foo}, then if an element is annotated with + * {@code Bar}, then calling {@code getLogicalAnnotation(element, Foo.class)} will return the {@code Foo} annotation + * present on the {@code Bar} annotation. + */ + public static <A extends Annotation> A getLogicalAnnotation(final AnnotatedElement element, final Class<A> annotationType) { + return findLogicalAnnotation(element, annotationType, new HashSet<>()); + } + + private static <A extends Annotation> A findLogicalAnnotation( + final AnnotatedElement element, final Class<A> annotationType, final Set<Class<? extends Annotation>> seen) { + final A elementAnnotation = element.getAnnotation(annotationType); + if (elementAnnotation != null) { + return elementAnnotation; + } for (final Annotation annotation : element.getAnnotations()) { - if (annotation.annotationType().isAnnotationPresent(metaAnnotation)) { - return annotation; + final Class<? extends Annotation> metaAnnotationType = annotation.annotationType(); + if (seen.add(metaAnnotationType)) { + final A ann = findLogicalAnnotation(metaAnnotationType, annotationType, seen); + if (ann != null) { + return ann; + } } } return null;
