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;

Reply via email to