Skip to content

Commit

Permalink
Make meta annotated match direct annotations as well #540
Browse files Browse the repository at this point in the history
So far the most common use case for checking rules for meta-annotations seems to be annotation composition, where users want to test that certain classes/members are either directly annotated with some annotation, or with some annotation that is meta-annotated with the respective annotation. Thus it makes sense to cover this use case directly with the `metaAnnotated(..)` syntax. For example

```
@interface A{}

@A
@interface B{}

class Foo {
    @A Object directlyAnnotated;
    @b Object indirectlyAnnotated;
}
```

Previously only `indirectlyAnnotated` would have counted as meta-annotated with `@A`. Now we count both those methods as meta-annotated with `@A`. The old behavior of really being annotated only through the hierarchy, but not directly can still be expressed as `metaAnnotatedWith(A.class).and(not(annotatedWith(A.class)))`, which seems to be good enough still to cover the old behavior. Besides that the only use case I could imagine is to enforce the use of a custom composite annotation, which could also be achieved by simply forbidding the original annotation altogether.

Resolves: #527

Signed-off-by: Peter Gafert <[email protected]>
  • Loading branch information
codecholeric authored Feb 21, 2021
2 parents d37e2d0 + 71835ef commit ad30fa5
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,12 @@ private static boolean isMetaAnnotatedWith(
return false;
}

if (predicate.apply(annotation)) {
return true;
}

for (JavaAnnotation<?> metaAnnotation : annotation.getRawType().getAnnotations()) {
if (predicate.apply(metaAnnotation) || isMetaAnnotatedWith(metaAnnotation, predicate, visitedAnnotations)) {
if (isMetaAnnotatedWith(metaAnnotation, predicate, visitedAnnotations)) {
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,13 @@ public void isMetaAnnotatedWith_type_on_resolved_target() {

assertThat(call.getTarget().isMetaAnnotatedWith(QueriedAnnotation.class))
.as("target is meta-annotated with @" + QueriedAnnotation.class.getSimpleName())
.isFalse();
.isTrue();
assertThat(call.getTarget().isMetaAnnotatedWith(Retention.class))
.as("target is meta-annotated with @" + Retention.class.getSimpleName())
.isTrue();
assertThat(call.getTarget().isMetaAnnotatedWith(Deprecated.class))
.as("target is meta-annotated with @" + Deprecated.class.getSimpleName())
.isFalse();
}

@Test
Expand All @@ -88,10 +91,13 @@ public void isMetaAnnotatedWith_typeName_on_resolved_target() {

assertThat(call.getTarget().isMetaAnnotatedWith(QueriedAnnotation.class.getName()))
.as("target is meta-annotated with @" + QueriedAnnotation.class.getSimpleName())
.isFalse();
.isTrue();
assertThat(call.getTarget().isMetaAnnotatedWith(Retention.class.getName()))
.as("target is meta-annotated with @" + Retention.class.getSimpleName())
.isTrue();
assertThat(call.getTarget().isMetaAnnotatedWith(Deprecated.class))
.as("target is meta-annotated with @" + Deprecated.class.getSimpleName())
.isFalse();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,19 +320,23 @@ public void isMetaAnnotatedWith_type() {
JavaClass clazz = importClassesWithContext(Parent.class, SomeAnnotation.class).get(Parent.class);

assertThat(clazz.isMetaAnnotatedWith(SomeAnnotation.class))
.as("Parent is meta-annotated with @" + SomeAnnotation.class.getSimpleName()).isFalse();
.as("Parent is meta-annotated with @" + SomeAnnotation.class.getSimpleName()).isTrue();
assertThat(clazz.isMetaAnnotatedWith(Retention.class))
.as("Parent is meta-annotated with @" + Retention.class.getSimpleName()).isTrue();
assertThat(clazz.isMetaAnnotatedWith(Deprecated.class))
.as("Parent is meta-annotated with @" + Deprecated.class.getSimpleName()).isFalse();
}

@Test
public void isMetaAnnotatedWith_typeName() {
JavaClass clazz = importClassesWithContext(Parent.class, SomeAnnotation.class).get(Parent.class);

assertThat(clazz.isMetaAnnotatedWith(SomeAnnotation.class.getName()))
.as("Parent is meta-annotated with @" + SomeAnnotation.class.getSimpleName()).isFalse();
.as("Parent is meta-annotated with @" + SomeAnnotation.class.getSimpleName()).isTrue();
assertThat(clazz.isMetaAnnotatedWith(Retention.class.getName()))
.as("Parent is meta-annotated with @" + Retention.class.getSimpleName()).isTrue();
assertThat(clazz.isMetaAnnotatedWith(Deprecated.class.getName()))
.as("Parent is meta-annotated with @" + Deprecated.class.getSimpleName()).isFalse();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,20 @@
import static com.tngtech.archunit.core.domain.TestUtils.importClassWithContext;
import static com.tngtech.archunit.core.domain.TestUtils.importClassesWithContext;
import static com.tngtech.archunit.testutil.Assertions.assertThat;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

public class JavaMemberTest {
@Test
public void isAnnotatedWith_type() {
assertThat(importField(SomeClass.class, "someField").isAnnotatedWith(Deprecated.class))
assertThat(importField(SomeClass.class, "someField").isAnnotatedWith(SomeAnnotation.class))
.as("field is annotated with @Deprecated").isTrue();
assertThat(importField(SomeClass.class, "someField").isAnnotatedWith(Retention.class))
.as("field is annotated with @Retention").isFalse();
}

@Test
public void isAnnotatedWith_typeName() {
assertThat(importField(SomeClass.class, "someField").isAnnotatedWith(Deprecated.class.getName()))
assertThat(importField(SomeClass.class, "someField").isAnnotatedWith(SomeAnnotation.class.getName()))
.as("field is annotated with @Deprecated").isTrue();
assertThat(importField(SomeClass.class, "someField").isAnnotatedWith(Retention.class.getName()))
.as("field is annotated with @Retention").isFalse();
Expand All @@ -42,20 +43,24 @@ public void isAnnotatedWith_predicate() {
public void isMetaAnnotatedWith_type() {
JavaClass clazz = importClassesWithContext(SomeClass.class, Deprecated.class).get(SomeClass.class);

assertThat(clazz.getField("someField").isMetaAnnotatedWith(Deprecated.class))
.as("field is meta-annotated with @Deprecated").isFalse();
assertThat(clazz.getField("someField").isMetaAnnotatedWith(SomeAnnotation.class))
.as("field is meta-annotated with @SomeAnnotation").isTrue();
assertThat(clazz.getField("someField").isMetaAnnotatedWith(Retention.class))
.as("field is meta-annotated with @Retention").isTrue();
assertThat(clazz.getField("someField").isMetaAnnotatedWith(Deprecated.class))
.as("field is meta-annotated with @Deprecated").isFalse();
}

@Test
public void isMetaAnnotatedWith_typeName() {
JavaClass clazz = importClassesWithContext(SomeClass.class, Deprecated.class).get(SomeClass.class);

assertThat(clazz.getField("someField").isMetaAnnotatedWith(Deprecated.class.getName()))
.as("field is meta-annotated with @Deprecated").isFalse();
assertThat(clazz.getField("someField").isMetaAnnotatedWith(SomeAnnotation.class.getName()))
.as("field is meta-annotated with @SomeAnnotation").isTrue();
assertThat(clazz.getField("someField").isMetaAnnotatedWith(Retention.class.getName()))
.as("field is meta-annotated with @Retention").isTrue();
assertThat(clazz.getField("someField").isMetaAnnotatedWith(Deprecated.class.getName()))
.as("field is meta-annotated with @Deprecated").isFalse();
}

@Test
Expand Down Expand Up @@ -93,8 +98,13 @@ private static JavaField importField(Class<?> owner, String name) {
return importClassWithContext(owner).getField(name);
}

@Retention(RUNTIME)
private @interface SomeAnnotation {
}

@SuppressWarnings("unused")
private static class SomeClass {
@Deprecated
@SomeAnnotation
private String someField;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,68 @@ public void notAnnotatedWith(ArchRule rule, Class<?> correctClass, Class<?> wron
.doesNotMatch(String.format(".*<%s>.*annotated.*", quote(correctClass.getName())));
}

@DataProvider
public static Object[][] metaAnnotated_rules() {
return $$(
$(classes().should().beMetaAnnotatedWith(SomeMetaAnnotation.class),
SomeAnnotatedClass.class, String.class),
$(classes().should(ArchConditions.beMetaAnnotatedWith(SomeMetaAnnotation.class)),
SomeAnnotatedClass.class, String.class),
$(classes().should().beMetaAnnotatedWith(SomeMetaAnnotation.class.getName()),
SomeAnnotatedClass.class, String.class),
$(classes().should(ArchConditions.beMetaAnnotatedWith(SomeMetaAnnotation.class.getName())),
SomeAnnotatedClass.class, String.class),
$(classes().should().beMetaAnnotatedWith(annotation(SomeMetaAnnotation.class)),
SomeAnnotatedClass.class, String.class),
$(classes().should(ArchConditions.beMetaAnnotatedWith(annotation(SomeMetaAnnotation.class))),
SomeAnnotatedClass.class, String.class));
}

@Test
@UseDataProvider("metaAnnotated_rules")
public void metaAnnotatedWith(ArchRule rule, Class<?> correctClass, Class<?> wrongClass) {
EvaluationResult result = rule.evaluate(importClasses(correctClass, wrongClass));

assertThat(singleLineFailureReportOf(result))
.contains(String.format("classes should be meta-annotated with @%s", SomeMetaAnnotation.class.getSimpleName()))
.containsPattern(String.format("Class <%s> is not meta-annotated with @%s in %s",
quote(wrongClass.getName()),
SomeMetaAnnotation.class.getSimpleName(),
locationPattern(String.class)))
.doesNotMatch(String.format(".*<%s>.*meta-annotated.*", quote(correctClass.getName())));
}

@DataProvider
public static Object[][] notMetaAnnotated_rules() {
return $$(
$(classes().should().notBeMetaAnnotatedWith(SomeMetaAnnotation.class),
String.class, SomeAnnotatedClass.class),
$(classes().should(ArchConditions.notBeMetaAnnotatedWith(SomeMetaAnnotation.class)),
String.class, SomeAnnotatedClass.class),
$(classes().should().notBeMetaAnnotatedWith(SomeMetaAnnotation.class.getName()),
String.class, SomeAnnotatedClass.class),
$(classes().should(ArchConditions.notBeMetaAnnotatedWith(SomeMetaAnnotation.class.getName())),
String.class, SomeAnnotatedClass.class),
$(classes().should().notBeMetaAnnotatedWith(annotation(SomeMetaAnnotation.class)),
String.class, SomeAnnotatedClass.class),
$(classes().should(ArchConditions.notBeMetaAnnotatedWith(annotation(SomeMetaAnnotation.class))),
String.class, SomeAnnotatedClass.class));
}

@Test
@UseDataProvider("notMetaAnnotated_rules")
public void notMetaAnnotatedWith(ArchRule rule, Class<?> correctClass, Class<?> wrongClass) {
EvaluationResult result = rule.evaluate(importClasses(correctClass, wrongClass));

assertThat(singleLineFailureReportOf(result))
.contains("classes should not be meta-annotated with @" + SomeMetaAnnotation.class.getSimpleName())
.containsPattern(String.format("Class <%s> is meta-annotated with @%s in %s",
quote(wrongClass.getName()),
SomeMetaAnnotation.class.getSimpleName(),
locationPattern(getClass())))
.doesNotMatch(String.format(".*<%s>.*meta-annotated.*", quote(correctClass.getName())));
}

/**
* Compare {@link CanBeAnnotatedTest#annotatedWith_Retention_Source_is_rejected}
*/
Expand Down Expand Up @@ -1971,10 +2033,18 @@ static class PackagePrivateClass {
private static class PrivateClass {
}

@SomeAnnotation
@RuntimeRetentionAnnotation
private static class SomeAnnotatedClass {
}

@interface SomeMetaAnnotation {
}

@SomeMetaAnnotation
@interface SomeAnnotation {
}

private static class NestedClassWithSomeMoreClasses {

static class StaticNestedClass {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,31 +340,31 @@ public void areMetaAnnotatedWith_type() {
List<JavaClass> classes = filterResultOf(classes().that().areMetaAnnotatedWith(SomeAnnotation.class))
.on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class);

assertThatType(getOnlyElement(classes)).matches(MetaAnnotatedClass.class);
assertThatTypes(classes).matchInAnyOrder(MetaAnnotatedClass.class, AnnotatedClass.class, MetaAnnotatedAnnotation.class);
}

@Test
public void areNotMetaAnnotatedWith_type() {
List<JavaClass> classes = filterResultOf(classes().that().areNotMetaAnnotatedWith(SomeAnnotation.class))
.on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class);

assertThatTypes(classes).matchInAnyOrder(AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class);
assertThatTypes(classes).matchInAnyOrder(SimpleClass.class);
}

@Test
public void areMetaAnnotatedWith_typeName() {
List<JavaClass> classes = filterResultOf(classes().that().areMetaAnnotatedWith(SomeAnnotation.class.getName()))
.on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class);

assertThatType(getOnlyElement(classes)).matches(MetaAnnotatedClass.class);
assertThatTypes(classes).matchInAnyOrder(MetaAnnotatedClass.class, AnnotatedClass.class, MetaAnnotatedAnnotation.class);
}

@Test
public void areNotMetaAnnotatedWith_typeName() {
List<JavaClass> classes = filterResultOf(classes().that().areNotMetaAnnotatedWith(SomeAnnotation.class.getName()))
.on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class);

assertThatTypes(classes).matchInAnyOrder(AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class);
assertThatTypes(classes).matchInAnyOrder(SimpleClass.class);
}

@Test
Expand All @@ -373,7 +373,7 @@ public void areMetaAnnotatedWith_predicate() {
List<JavaClass> classes = filterResultOf(classes().that().areMetaAnnotatedWith(hasNamePredicate))
.on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class);

assertThatType(getOnlyElement(classes)).matches(MetaAnnotatedClass.class);
assertThatTypes(classes).matchInAnyOrder(MetaAnnotatedClass.class, AnnotatedClass.class, MetaAnnotatedAnnotation.class);
}

@Test
Expand All @@ -382,7 +382,7 @@ public void areNotMetaAnnotatedWith_predicate() {
List<JavaClass> classes = filterResultOf(classes().that().areNotMetaAnnotatedWith(hasNamePredicate))
.on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class);

assertThatTypes(classes).matchInAnyOrder(AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class);
assertThatTypes(classes).matchInAnyOrder(SimpleClass.class);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,31 +336,31 @@ public void areMetaAnnotatedWith_type() {
List<JavaMember> members = filterResultOf(members().that().areDeclaredInClassesThat().areMetaAnnotatedWith(SomeAnnotation.class))
.on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class);

assertThatMembers(members).matchInAnyOrderMembersOf(MetaAnnotatedClass.class);
assertThatMembers(members).matchInAnyOrderMembersOf(MetaAnnotatedClass.class, AnnotatedClass.class);
}

@Test
public void areNotMetaAnnotatedWith_type() {
List<JavaMember> members = filterResultOf(members().that().areDeclaredInClassesThat().areNotMetaAnnotatedWith(SomeAnnotation.class))
.on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class);

assertThatMembers(members).matchInAnyOrderMembersOf(AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class);
assertThatMembers(members).matchInAnyOrderMembersOf(SimpleClass.class, MetaAnnotatedAnnotation.class);
}

@Test
public void areMetaAnnotatedWith_typeName() {
List<JavaMember> members = filterResultOf(members().that().areDeclaredInClassesThat().areMetaAnnotatedWith(SomeAnnotation.class.getName()))
.on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class);

assertThatMembers(members).matchInAnyOrderMembersOf(MetaAnnotatedClass.class);
assertThatMembers(members).matchInAnyOrderMembersOf(MetaAnnotatedClass.class, AnnotatedClass.class);
}

@Test
public void areNotMetaAnnotatedWith_typeName() {
List<JavaMember> members = filterResultOf(members().that().areDeclaredInClassesThat().areNotMetaAnnotatedWith(SomeAnnotation.class.getName()))
.on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class);

assertThatMembers(members).matchInAnyOrderMembersOf(AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class);
assertThatMembers(members).matchInAnyOrderMembersOf(SimpleClass.class, MetaAnnotatedAnnotation.class);
}

@Test
Expand All @@ -369,7 +369,7 @@ public void areMetaAnnotatedWith_predicate() {
List<JavaMember> members = filterResultOf(members().that().areDeclaredInClassesThat().areMetaAnnotatedWith(hasNamePredicate))
.on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class);

assertThatMembers(members).matchInAnyOrderMembersOf(MetaAnnotatedClass.class);
assertThatMembers(members).matchInAnyOrderMembersOf(MetaAnnotatedClass.class, AnnotatedClass.class);
}

@Test
Expand All @@ -378,7 +378,7 @@ public void areNotMetaAnnotatedWith_predicate() {
List<JavaMember> members = filterResultOf(members().that().areDeclaredInClassesThat().areNotMetaAnnotatedWith(hasNamePredicate))
.on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class);

assertThatMembers(members).matchInAnyOrderMembersOf(AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class);
assertThatMembers(members).matchInAnyOrderMembersOf(SimpleClass.class, MetaAnnotatedAnnotation.class);
}

@Test
Expand Down
Loading

0 comments on commit ad30fa5

Please sign in to comment.