Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LOGBACK-1583] Search for @NoAutoStart annotations in ancestor hierarchy, implemented interfaces and meta-annotations #531

Merged
merged 2 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I can see the purpose of looking at super classes. Is there a use for looking at other annotations to search for a given annotation?

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 {
}
}