From 024ac9192837666f89331b907d0366189ee47ce8 Mon Sep 17 00:00:00 2001 From: Per Lundberg Date: Fri, 26 May 2023 13:55:13 +0300 Subject: [PATCH] Add (not)`haveFullyQualifiedNameAnyOf` to API --- .../core/domain/properties/HasName.java | 26 ++++++++++++++++++ .../lang/conditions/ArchConditions.java | 17 ++++++++++++ .../lang/syntax/ClassesThatInternal.java | 10 +++++++ .../syntax/MembersDeclaredInClassesThat.java | 10 +++++++ .../lang/syntax/SyntaxPredicates.java | 9 +++++++ .../lang/syntax/elements/ClassesThat.java | 18 +++++++++++++ .../syntax/elements/GivenClassesThatTest.java | 16 +++++++++++ ...GivenMembersDeclaredInClassesThatTest.java | 16 +++++++++++ .../elements/ShouldClassesThatTest.java | 22 ++++++++++++++- .../elements/ShouldOnlyByClassesThatTest.java | 27 +++++++++++++++++++ 10 files changed, 170 insertions(+), 1 deletion(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasName.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasName.java index b911e75dc0..c83617a7fe 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasName.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasName.java @@ -22,6 +22,7 @@ import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.base.ChainableFunction; import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.domain.Formatters; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; import static com.tngtech.archunit.core.domain.properties.HasName.Utils.namesOf; @@ -117,6 +118,11 @@ public static DescribedPredicate name(String name) { return new NameEqualsPredicate(name); } + @PublicAPI(usage = ACCESS) + public static DescribedPredicate nameAnyOf(String... classNames) { + return new NameEqualsAnyOfPredicate(classNames); + } + /** * Matches names against a regular expression. */ @@ -154,6 +160,26 @@ public boolean test(HasName input) { } } + private static class NameEqualsAnyOfPredicate extends DescribedPredicate { + private final String[] names; + + NameEqualsAnyOfPredicate(String[] names) { + super(String.format("name '%s'", Formatters.joinSingleQuoted(names))); + this.names = names; + } + + @Override + public boolean test(HasName input) { + for (String name : names) { + if (input.getName().equals(name)) { + return true; + } + } + + return false; + } + } + private static class NameMatchingPredicate extends DescribedPredicate { private final Pattern pattern; diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java index d59fe58788..8ccd0a86e2 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java @@ -116,6 +116,7 @@ import static com.tngtech.archunit.core.domain.properties.HasName.AndFullName.Predicates.fullName; import static com.tngtech.archunit.core.domain.properties.HasName.AndFullName.Predicates.fullNameMatching; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameAnyOf; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameContaining; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameEndingWith; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching; @@ -518,6 +519,22 @@ public static ArchCondition notHaveFullyQualifiedName(String name) { return not(haveFullyQualifiedName(name)); } + @PublicAPI(usage = ACCESS) + public static ArchCondition haveFullyQualifiedNameAnyOf(String... classNames) { + return have(fullyQualifiedNameAnyOf(classNames)); + } + + @Internal + public static DescribedPredicate fullyQualifiedNameAnyOf(String[] classNames) { + DescribedPredicate predicate = nameAnyOf(classNames); + return predicate.as("fully qualified " + predicate.getDescription()); + } + + @PublicAPI(usage = ACCESS) + public static ArchCondition notHaveFullyQualifiedNameAnyOf(String... classNames) { + return not(haveFullyQualifiedNameAnyOf(classNames)); + } + @PublicAPI(usage = ACCESS) public static ArchCondition haveSimpleName(String name) { return have(simpleName(name)); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/ClassesThatInternal.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/ClassesThatInternal.java index 67487baad2..35cdb4aa73 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/ClassesThatInternal.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/ClassesThatInternal.java @@ -430,6 +430,16 @@ public CONJUNCTION doNotHaveFullyQualifiedName(String name) { return givenWith(SyntaxPredicates.doNotHaveFullyQualifiedName(name)); } + @Override + public CONJUNCTION haveFullyQualifiedNameAnyOf(String... classNames) { + return givenWith(SyntaxPredicates.haveFullyQualifiedNameAnyOf(classNames)); + } + + @Override + public CONJUNCTION doNotHaveFullyQualifiedNameAnyOf(String... classNames) { + return givenWith(SyntaxPredicates.doNotHaveFullyQualifiedNameAnyOf(classNames)); + } + @Override public CONJUNCTION haveSimpleName(String name) { return givenWith(SyntaxPredicates.haveSimpleName(name)); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersDeclaredInClassesThat.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersDeclaredInClassesThat.java index 8abfded1f1..7a25e4138c 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersDeclaredInClassesThat.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersDeclaredInClassesThat.java @@ -72,6 +72,16 @@ public CONJUNCTION doNotHaveFullyQualifiedName(String name) { return givenWith(SyntaxPredicates.doNotHaveFullyQualifiedName(name)); } + @Override + public CONJUNCTION haveFullyQualifiedNameAnyOf(String... classNames) { + return givenWith(SyntaxPredicates.haveFullyQualifiedNameAnyOf(classNames)); + } + + @Override + public CONJUNCTION doNotHaveFullyQualifiedNameAnyOf(String... classNames) { + return givenWith(SyntaxPredicates.doNotHaveFullyQualifiedNameAnyOf(classNames)); + } + @Override public CONJUNCTION haveSimpleName(String name) { return givenWith(SyntaxPredicates.haveSimpleName(name)); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/SyntaxPredicates.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/SyntaxPredicates.java index 8b53b66b26..8558b2bfc2 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/SyntaxPredicates.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/SyntaxPredicates.java @@ -35,6 +35,7 @@ import static com.tngtech.archunit.core.domain.properties.HasModifiers.Predicates.modifier; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching; import static com.tngtech.archunit.lang.conditions.ArchConditions.fullyQualifiedName; +import static com.tngtech.archunit.lang.conditions.ArchConditions.fullyQualifiedNameAnyOf; import static com.tngtech.archunit.lang.conditions.ArchPredicates.have; class SyntaxPredicates { @@ -110,6 +111,14 @@ static DescribedPredicate doNotHaveFullyQualifiedName(String name) { return doNot(have(fullyQualifiedName(name))); } + static DescribedPredicate haveFullyQualifiedNameAnyOf(String[] classNames) { + return have(fullyQualifiedNameAnyOf(classNames)); + } + + static DescribedPredicate doNotHaveFullyQualifiedNameAnyOf(String[] classNames) { + return doNot(have(fullyQualifiedNameAnyOf(classNames))); + } + static DescribedPredicate haveSimpleName(String name) { return have(simpleName(name)); } diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/ClassesThat.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/ClassesThat.java index 6d495c6177..60991688cf 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/ClassesThat.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/ClassesThat.java @@ -55,6 +55,24 @@ public interface ClassesThat { @PublicAPI(usage = ACCESS) CONJUNCTION doNotHaveFullyQualifiedName(String name); + /** + * Matches classes by their fully qualified class name. + * + * @param classNames One or more fully qualified class names + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveFullyQualifiedNameAnyOf(String... classNames); + + /** + * Matches classes that do not have a certain fully qualified class name. + * + * @param classNames One or more fully qualified class names + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION doNotHaveFullyQualifiedNameAnyOf(String... classNames); + /** * Matches classes by their simple class name. * diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenClassesThatTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenClassesThatTest.java index 15b3f48ff8..8e3f589acc 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenClassesThatTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenClassesThatTest.java @@ -73,6 +73,22 @@ public void doNotHaveFullyQualifiedName() { assertThatTypes(classes).matchInAnyOrder(String.class, Iterable.class); } + @Test + public void haveFullyQualifiedNameAnyOf() { + List classes = filterResultOf(classes().that().haveFullyQualifiedNameAnyOf(List.class.getName())) + .on(List.class, String.class, Iterable.class); + + assertThatType(getOnlyElement(classes)).matches(List.class); + } + + @Test + public void doNotHaveFullyQualifiedNameAnyOf() { + List classes = filterResultOf(classes().that().doNotHaveFullyQualifiedNameAnyOf(List.class.getName())) + .on(List.class, String.class, Iterable.class); + + assertThatTypes(classes).matchInAnyOrder(String.class, Iterable.class); + } + @Test public void haveSimpleName() { List classes = filterResultOf(classes().that().haveSimpleName(List.class.getSimpleName())) diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersDeclaredInClassesThatTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersDeclaredInClassesThatTest.java index d75f125330..1d424650b9 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersDeclaredInClassesThatTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersDeclaredInClassesThatTest.java @@ -68,6 +68,22 @@ public void doNotHaveFullyQualifiedName() { assertThatMembers(members).matchInAnyOrderMembersOf(String.class, Iterable.class); } + @Test + public void haveFullyQualifiedNameAnyOf() { + List members = filterResultOf(members().that().areDeclaredInClassesThat().haveFullyQualifiedNameAnyOf(List.class.getName())) + .on(List.class, String.class, Iterable.class); + + assertThatMembers(members).matchInAnyOrderMembersOf(List.class); + } + + @Test + public void doNotHaveFullyQualifiedNameAnyOf() { + List members = filterResultOf(members().that().areDeclaredInClassesThat().doNotHaveFullyQualifiedNameAnyOf(List.class.getName())) + .on(List.class, String.class, Iterable.class); + + assertThatMembers(members).matchInAnyOrderMembersOf(String.class, Iterable.class); + } + @Test public void haveSimpleName() { List members = filterResultOf(members().that().areDeclaredInClassesThat().haveSimpleName(List.class.getSimpleName())) diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ShouldClassesThatTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ShouldClassesThatTest.java index 18e510fa70..38b7d2ec7b 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ShouldClassesThatTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ShouldClassesThatTest.java @@ -89,6 +89,26 @@ public void doNotHaveFullyQualifiedName(ClassesThat no assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } + @Test + @UseDataProvider("no_classes_should_that_rule_starts") + public void haveFullyQualifiedNameAnyOf(ClassesThat noClassesShouldThatRuleStart) { + Set classes = filterClassesAppearingInFailureReport( + noClassesShouldThatRuleStart.haveFullyQualifiedNameAnyOf(List.class.getName())) + .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); + + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); + } + + @Test + @UseDataProvider("no_classes_should_that_rule_starts") + public void doNotHaveFullyQualifiedNameAnyOf(ClassesThat noClassesShouldThatRuleStart) { + Set classes = filterClassesAppearingInFailureReport( + noClassesShouldThatRuleStart.doNotHaveFullyQualifiedNameAnyOf(List.class.getName())) + .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); + + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + } + @Test @UseDataProvider("no_classes_should_that_rule_starts") public void haveSimpleName(ClassesThat noClassesShouldThatRuleStart) { @@ -1735,7 +1755,7 @@ static class DirectlyDependentClass3 { @SuppressWarnings("unused") static class Level1TransitivelyDependentClass1 { - Level2TransitivelyDependentClass1 transitiveDependency1; + Level2TransitivelyDependentClass1 transitiveDependency1; } static class Level2TransitivelyDependentClass1 { diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ShouldOnlyByClassesThatTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ShouldOnlyByClassesThatTest.java index e9e952f610..1430ae2822 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ShouldOnlyByClassesThatTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ShouldOnlyByClassesThatTest.java @@ -84,6 +84,32 @@ public void doNotHaveFullyQualifiedName(ClassesThat cl assertThatTypes(classes).matchInAnyOrder(ClassAccessedByFoo.class, Foo.class); } + @Test + @UseDataProvider("should_only_be_by_rule_starts") + public void haveFullyQualifiedNameAnyOf(ClassesThat classesShouldOnlyBeBy) { + Set classes = filterClassesAppearingInFailureReport( + classesShouldOnlyBeBy.haveFullyQualifiedNameAnyOf(Foo.class.getName())) + .on(ClassAccessedByFoo.class, Foo.class, + ClassAccessedByBar.class, Bar.class, + ClassAccessedByBaz.class, Baz.class); + + assertThatTypes(classes).matchInAnyOrder( + ClassAccessedByBar.class, Bar.class, + ClassAccessedByBaz.class, Baz.class); + } + + @Test + @UseDataProvider("should_only_be_by_rule_starts") + public void doNotHaveFullyQualifiedNameAnyOf(ClassesThat classesShouldOnlyBeBy) { + Set classes = filterClassesAppearingInFailureReport( + classesShouldOnlyBeBy.doNotHaveFullyQualifiedNameAnyOf(Foo.class.getName())) + .on(ClassAccessedByFoo.class, Foo.class, + ClassAccessedByBar.class, Bar.class, + ClassAccessedByBaz.class, Baz.class); + + assertThatTypes(classes).matchInAnyOrder(ClassAccessedByFoo.class, Foo.class); + } + @Test @UseDataProvider("should_only_be_by_rule_starts") public void haveSimpleName(ClassesThat classesShouldOnlyBeBy) { @@ -1422,6 +1448,7 @@ private static class StaticNestedClassBeingAccessed { // This must be loaded via Reflection, otherwise the test will be tainted by the dependency on the class object private static final Class ClassAccessingAnonymousClass_Reference = classForName("com.tngtech.archunit.lang.syntax.elements.ShouldOnlyByClassesThatTest$ClassAccessingAnonymousClass"); + private static class ClassAccessingAnonymousClass { @SuppressWarnings("unused") void access() {