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

Repeated meta-annotations not locatable on repeated annotation. [SPR-15723] #20279

Open
spring-projects-issues opened this issue Jul 1, 2017 · 2 comments
Labels
for: team-attention in: core status: waiting-for-triage

Comments

@spring-projects-issues
Copy link
Collaborator

@spring-projects-issues spring-projects-issues commented Jul 1, 2017

Liam Bryan opened SPR-15723 and commented

Meta annotations are not detected when present on Repeatable annotations.

Provided zip contains a quick example highlighting this, but for a contained example here (@Retention and @Target annotations omitted):

@Repeatable(A.List.class)
@interface A {

    int value() default 1;
    
    @interface List {
        A[] value();
    }

}
@Repeatable(B.List.class)
@A
@interface B {

    @AliasFor(annotation = A.class, attribute = "value")
    int value();
    
    @interface List {
        B[] value();
    }

}

None of the provided methods in AnnotationUtils or AnnotatedElementUtils can locate the meta-@A annotations for an element with repeated @B annotations.


Affects: 4.3.8

Attachments:

@spring-projects-issues spring-projects-issues added type: bug status: waiting-for-triage in: core and removed type: bug labels Jan 11, 2019
@sbrannen
Copy link
Member

@sbrannen sbrannen commented Aug 18, 2020

Thanks for raising the issue, and I apologize that we took so long to triage it.

I was able to reproduce this against master with the following all-in-one test class.

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;

import org.junit.jupiter.api.Test;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;

@SuppressWarnings("unused")
class AnnotationRetrievalTest {

	@Test
	void findAnnotationsSingle() throws Exception {
		Method singleAnnotatedMethod = getClass().getDeclaredMethod("singleAnnotatedMethod");

		// Passes.
		performTest(singleAnnotatedMethod, 1);
	}

	@Test
	void findAnnotationsMulti() throws Exception {
		Method multiAnnotatedMethod = getClass().getDeclaredMethod("multiAnnotatedMethod");

		// Fails (for all 3 sub-assertions).
		performTest(multiAnnotatedMethod, 2);
	}

	private void performTest(Method method, int expectedAnnotationCount) {
		Set<A> fromFindMergedRepeatable = AnnotatedElementUtils.findMergedRepeatableAnnotations(method, A.class);
		Set<A> fromFindMergedRepeatableWithContainer = AnnotatedElementUtils.findMergedRepeatableAnnotations(method,
				A.class, A.Container.class);
		Set<A> fromGetRepeatable = AnnotationUtils.getRepeatableAnnotations(method, A.class);
		List<A> fromJUnitFindRepeatable = org.junit.platform.commons.util.AnnotationUtils
				.findRepeatableAnnotations(method, A.class);

		assertAll(() -> assertEquals(expectedAnnotationCount, fromFindMergedRepeatable.size()),
				() -> assertEquals(expectedAnnotationCount, fromFindMergedRepeatableWithContainer.size()),
				() -> assertEquals(expectedAnnotationCount, fromGetRepeatable.size()),
				() -> assertEquals(expectedAnnotationCount, fromJUnitFindRepeatable.size()));
	}

	@Retention(RetentionPolicy.RUNTIME)
	@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
	@Repeatable(A.Container.class)
	public @interface A {

		int value() default 0;

		@Retention(RetentionPolicy.RUNTIME)
		@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
		@interface Container {
			A[] value();
		}
	}

	@Retention(RetentionPolicy.RUNTIME)
	@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
	@Repeatable(B.Container.class)
	@A
	public @interface B {

		@AliasFor(annotation = A.class)
		int value();

		@Retention(RetentionPolicy.RUNTIME)
		@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
		@interface Container {
			B[] value();
		}
	}

	@B(5)
	void singleAnnotatedMethod() {
	}

	@B(5)
	@B(10)
	void multiAnnotatedMethod() {
	}

}

Interestingly, the supplied example.zip was testing org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations from JUnit 5 instead of org.springframework.core.annotation.AnnotationUtils.getRepeatableAnnotations from Spring. In any case, the error is the same. Neither Spring nor JUnit 5 find the repeatable annotation for the "multi" scenario.

@philwebb and @jhoeller, do you think we should try to add support for this scenario?

@sbrannen sbrannen added for: team-attention and removed status: waiting-for-triage labels Aug 18, 2020
@sbrannen
Copy link
Member

@sbrannen sbrannen commented Aug 18, 2020

Actually, after having put further thought into it, the expectation for AnnotationUtils.getRepeatableAnnotations (as well as for JUnit's similar method) is invalid.

getRepeatableAnnotations does not merge annotation attributes. Thus, the algorithm may encounter @A twice, but each encounter is seen as the same instance. The @A annotation is therefore only found once. Consequently, the expectation should be 1 instead of 2 for the non-merging search algorithms.

@sbrannen sbrannen added the status: waiting-for-triage label Jan 18, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
for: team-attention in: core status: waiting-for-triage
Projects
None yet
Development

No branches or pull requests

2 participants