Skip to content

Commit

Permalink
Search for @NoAutoStart annotations in ancestor hierarchy, implemente…
Browse files Browse the repository at this point in the history
…d interfaces and meta-annotations

Adresses issue LOGBACK-1583

Signed-off-by: Bertrand Renuart <brenuart@gmail.com>
  • Loading branch information
brenuart committed Oct 13, 2021
1 parent 7c3a118 commit f66c0cf
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
*/
package ch.qos.logback.core.joran.spi;

import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Set;

public class NoAutoStartUtil {

/**
Expand All @@ -22,13 +26,79 @@ public class NoAutoStartUtil {
* @param o
* @return true for classes not marked with the NoAutoStart annotation
*/
static public boolean notMarkedWithNoAutoStart(Object o) {
public static boolean notMarkedWithNoAutoStart(Object o) {
if (o == null) {
return false;
}
Class<?> clazz = o.getClass();
NoAutoStart a = clazz.getAnnotation(NoAutoStart.class);
NoAutoStart a = findAnnotation(clazz, NoAutoStart.class);
return a == null;
}

/**
* Find a single {@link Annotation} of {@code annotationType} on the
* supplied {@link Class}, traversing its interfaces, annotations, and
* superclasses if the annotation is not <em>directly present</em> on
* the given class itself.
* <p>This method explicitly handles class-level annotations which are not
* declared as {@link java.lang.annotation.Inherited inherited} <em>as well
* as meta-annotations and annotations on interfaces</em>.
* <p>The algorithm operates as follows:
* <ol>
* <li>Search for the annotation on the given class and return it if found.
* <li>Recursively search through all annotations that the given class declares.
* <li>Recursively search through all interfaces that the given class declares.
* <li>Recursively search through the superclass hierarchy of the given class.
* </ol>
* <p>Note: in this context, the term <em>recursively</em> means that the search
* process continues by returning to step #1 with the current interface,
* annotation, or superclass as the class to look for annotations on.
* @param clazz the class to look for annotations on
* @param annotationType the type of annotation to look for
* @return the first matching annotation, or {@code null} if not found
*/
private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
return findAnnotation(clazz, annotationType, new HashSet<>());
}

/**
* Perform the search algorithm for {@link #findAnnotation(Class, Class)},
* avoiding endless recursion by tracking which annotations have already
* been <em>visited</em>.
* @param clazz the class to look for annotations on
* @param annotationType the type of annotation to look for
* @param visited the set of annotations that have already been visited
* @return the first matching annotation, or {@code null} if not found
*/
@SuppressWarnings("unchecked")
private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType, Set<Annotation> visited) {

Annotation[] anns = clazz.getDeclaredAnnotations();
for (Annotation ann : anns) {
if (ann.annotationType() == annotationType) {
return (A) ann;
}
}
for (Annotation ann : anns) {
if (visited.add(ann)) {
A annotation = findAnnotation(ann.annotationType(), annotationType, visited);
if (annotation != null) {
return annotation;
}
}
}

for (Class<?> ifc : clazz.getInterfaces()) {
A annotation = findAnnotation(ifc, annotationType, visited);
if (annotation != null) {
return annotation;
}
}

Class<?> superclass = clazz.getSuperclass();
if (superclass == null || Object.class == superclass) {
return null;
}
return findAnnotation(superclass, annotationType, visited);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.junit.Test;

public class NoAutoStartUtilTest {
Expand All @@ -31,4 +36,71 @@ public void markedWithNoAutoStart() {
DoNotAutoStart o = new DoNotAutoStart();
assertFalse(NoAutoStartUtil.notMarkedWithNoAutoStart(o));
}



/*
* Annotation declared on implemented interface
*/
@Test
public void noAutoStartOnInterface() {
ComponentWithNoAutoStartOnInterface o = new ComponentWithNoAutoStartOnInterface();
assertFalse(NoAutoStartUtil.notMarkedWithNoAutoStart(o));
}

@NoAutoStart
public interface NoAutoStartInterface {
}

private static class ComponentWithNoAutoStartOnInterface implements NoAutoStartInterface {
}



/*
* Annotation declared on ancestor
*/
@Test
public void noAutoStartOnAncestor() {
ComponentWithNoAutoStartOnAncestor o = new ComponentWithNoAutoStartOnAncestor();
assertFalse(NoAutoStartUtil.notMarkedWithNoAutoStart(o));
}

private static class ComponentWithNoAutoStartOnAncestor extends DoNotAutoStart {
}



/*
* Annotation declared on interface implemented by an ancestor
*/
@Test
public void noAutoStartOnInterfaceImplementedByAncestor() {
ComponentWithAncestorImplementingInterfaceWithNoAutoStart o = new ComponentWithAncestorImplementingInterfaceWithNoAutoStart();
assertFalse(NoAutoStartUtil.notMarkedWithNoAutoStart(o));
}

private static class ComponentWithAncestorImplementingInterfaceWithNoAutoStart extends ComponentWithNoAutoStartOnInterface {
}



/*
* Custom annotation annotated with @NoAutoStart
*/
@Test
public void noAutoStartAsMetaAnnotation() {
ComponentWithMetaAnnotation o = new ComponentWithMetaAnnotation();
assertFalse(NoAutoStartUtil.notMarkedWithNoAutoStart(o));
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@NoAutoStart
public @interface MetaNoAutoStart {
}

@MetaNoAutoStart
private static class ComponentWithMetaAnnotation {
}
}

0 comments on commit f66c0cf

Please sign in to comment.