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 481a370977..796db3d7b4 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 @@ -30,6 +30,52 @@ public interface HasName { interface AndFullName extends HasName { @PublicAPI(usage = ACCESS) String getFullName(); + + final class Predicates { + private Predicates() { + } + + @PublicAPI(usage = ACCESS) + public static DescribedPredicate fullName(String fullName) { + return new FullNameEqualsPredicate(fullName); + } + + /** + * Matches full names against a regular expression. + */ + @PublicAPI(usage = ACCESS) + public static DescribedPredicate fullNameMatching(String regex) { + return new FullNameMatchingPredicate(regex); + } + + private static class FullNameEqualsPredicate extends DescribedPredicate { + private final String fullName; + + FullNameEqualsPredicate(String fullName) { + super(String.format("full name '%s'", fullName)); + this.fullName = fullName; + } + + @Override + public boolean apply(HasName.AndFullName input) { + return input.getFullName().equals(fullName); + } + } + + private static class FullNameMatchingPredicate extends DescribedPredicate { + private final Pattern pattern; + + FullNameMatchingPredicate(String regex) { + super(String.format("full name matching '%s'", regex)); + this.pattern = Pattern.compile(regex); + } + + @Override + public boolean apply(HasName.AndFullName input) { + return pattern.matcher(input.getFullName()).matches(); + } + } + } } final class Predicates { diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersThatInternal.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersThatInternal.java index d4a35062f7..68368ec679 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersThatInternal.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersThatInternal.java @@ -31,6 +31,8 @@ import static com.tngtech.archunit.core.domain.JavaMember.Predicates.declaredIn; import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.annotatedWith; import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.metaAnnotatedWith; +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.nameMatching; import static com.tngtech.archunit.lang.conditions.ArchPredicates.are; @@ -71,6 +73,26 @@ public CONJUNCTION haveNameNotMatching(String regex) { return givenWith(SyntaxPredicates.haveNameNotMatching(regex)); } + @Override + public CONJUNCTION haveFullName(String fullName) { + return givenWith(have(fullName(fullName))); + } + + @Override + public CONJUNCTION doNotHaveFullName(String fullName) { + return givenWith(doNot(have(fullName(fullName)))); + } + + @Override + public CONJUNCTION haveFullNameMatching(String regex) { + return givenWith(have(fullNameMatching(regex))); + } + + @Override + public CONJUNCTION haveFullNameNotMatching(String regex) { + return givenWith(have(not(fullNameMatching(regex)).as("full name not matching '%s'", regex))); + } + @Override public CONJUNCTION arePublic() { return givenWith(SyntaxPredicates.arePublic()); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java index 70e3d60cb1..6cbfe2a35d 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java @@ -67,6 +67,42 @@ public interface MembersThat> { @PublicAPI(usage = ACCESS) CONJUNCTION haveNameNotMatching(String regex); + /** + * Matches members by their full name. + * + * @param fullName The member's full name + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveFullName(String fullName); + + /** + * Matches members that do not have a certain full name. + * + * @param fullName The member's full name + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION doNotHaveFullName(String fullName); + + /** + * Matches members with a full name matching a given regular expression. + * + * @param regex A regular expression + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveFullNameMatching(String regex); + + /** + * Matches members with a full name not matching a given regular expression. + * + * @param regex A regular expression + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveFullNameNotMatching(String regex); + /** * Matches public members. * diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasNameTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasNameTest.java index 0c8aa8209c..4d13d23671 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasNameTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasNameTest.java @@ -3,28 +3,30 @@ import org.assertj.core.api.AbstractBooleanAssert; import org.junit.Test; +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.nameMatching; import static com.tngtech.archunit.testutil.Assertions.assertThat; public class HasNameTest { @Test - public void match_against_regex() { - assertMatches("Some.foobar", ".*foo.*").isTrue(); - assertMatches("Some.fobar", ".*foo.*").isFalse(); - assertMatches("Some.foobar", ".*fob?o*.*").isTrue(); - assertMatches("com.tngtech.SomeClass", ".*W.*").isFalse(); - assertMatches("com.tngtech.SomeClass", "com.*").isTrue(); - assertMatches("com.tngtech.SomeClass", "co\\..*").isFalse(); - assertMatches("com.tngtech.SomeClass", ".*Class").isTrue(); - assertMatches("com.tngtech.SomeClass", ".*Clas").isFalse(); - assertMatches("com.tngtech.SomeClass", ".*\\.S.*s").isTrue(); + public void nameMatching_predicate() { + assertNameMatches("Some.foobar", ".*foo.*").isTrue(); + assertNameMatches("Some.fobar", ".*foo.*").isFalse(); + assertNameMatches("Some.foobar", ".*fob?o*.*").isTrue(); + assertNameMatches("com.tngtech.SomeClass", ".*W.*").isFalse(); + assertNameMatches("com.tngtech.SomeClass", "com.*").isTrue(); + assertNameMatches("com.tngtech.SomeClass", "co\\..*").isFalse(); + assertNameMatches("com.tngtech.SomeClass", ".*Class").isTrue(); + assertNameMatches("com.tngtech.SomeClass", ".*Clas").isFalse(); + assertNameMatches("com.tngtech.SomeClass", ".*\\.S.*s").isTrue(); assertThat(nameMatching(".*foo")).hasDescription("name matching '.*foo'"); } @Test - public void match_against_name() { + public void name_predicate() { assertThat(name("some.Foo")) .accepts(newHasName("some.Foo")) .rejects(newHasName("some.Fo")) @@ -32,17 +34,45 @@ public void match_against_name() { assertThat(name("Foo")).rejects(newHasName("some.Foo")); } - private AbstractBooleanAssert assertMatches(String input, String regex) { + @Test + public void fullName_predicate() { + assertThat(fullName("some.Foo.field1")) + .accepts(newHasNameAndFullName("field1", "some.Foo.field1")) + .rejects(newHasNameAndFullName("field", "some.Foo.field")) + .rejects(newHasNameAndFullName("field12", "some.Foo.field12")) + .rejects(newHasNameAndFullName("some.Foo.field1", "some.Foo.field1.property")) + .hasDescription("full name 'some.Foo.field1'"); + } + + @Test + public void fullNameMatching_predicate() { + assertThat(fullNameMatching(".*method\\(.*\\)")) + .accepts(newHasNameAndFullName("method", "some.Foo.method(int)")) + .accepts(newHasNameAndFullName("method", "some.Foo.method()")) + .rejects(newHasNameAndFullName("method", "some.Foo.method")) + .hasDescription("full name matching '.*method\\(.*\\)'"); + } + + private AbstractBooleanAssert assertNameMatches(String input, String regex) { return assertThat(nameMatching(regex).apply(newHasName(input))) .as(input + " =~ " + regex); } private HasName newHasName(final String name) { - return new HasName() { + return newHasNameAndFullName(name, "full " + name); + } + + private HasName.AndFullName newHasNameAndFullName(final String name, final String fullName) { + return new HasName.AndFullName() { @Override public String getName() { return name; } + + @Override + public String getFullName() { + return fullName; + } }; } } \ No newline at end of file diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersTest.java index 3d1c76e1a5..42db09e141 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersTest.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Pattern; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; @@ -52,6 +53,7 @@ import static com.tngtech.java.junit.dataprovider.DataProviders.$$; import static java.util.Collections.emptySet; import static java.util.Collections.singleton; +import static java.util.regex.Pattern.quote; import static org.assertj.core.api.Assertions.assertThat; @RunWith(DataProviderRunner.class) @@ -147,6 +149,7 @@ public void complex_members_syntax() { @DataProvider public static Object[][] restricted_property_rule_starts() { + String classNameDot = ClassWithVariousMembers.class.getName() + "."; ImmutableList.Builder data = ImmutableList.builder().add( $(described(members().that().haveName(FIELD_A)), ImmutableSet.of(FIELD_A)), $(described(codeUnits().that().haveName(FIELD_A)), ImmutableSet.of()), @@ -186,6 +189,30 @@ public static Object[][] restricted_property_rule_starts() { $(described(codeUnits().that().haveNameNotMatching(".*init.*")), ALL_METHOD_DESCRIPTIONS), $(described(constructors().that().haveNameNotMatching(".*init.*")), emptySet()), + $(described(members().that().haveFullName(classNameDot + FIELD_A)), ImmutableSet.of(FIELD_A)), + $(described(codeUnits().that().haveFullName(classNameDot + METHOD_A)), ImmutableSet.of(METHOD_A)), + $(described(methods().that().haveFullName(classNameDot + METHOD_A)), ImmutableSet.of(METHOD_A)), + $(described(constructors().that().haveFullName(classNameDot + CONSTRUCTOR_ONE_ARG)), ImmutableSet.of(CONSTRUCTOR_ONE_ARG)), + $(described(fields().that().haveFullName(classNameDot + FIELD_A)), ImmutableSet.of(FIELD_A)), + $(described(members().that().doNotHaveFullName(classNameDot + FIELD_A)), union(allFieldsExcept(FIELD_A), ALL_CODE_UNIT_DESCRIPTIONS)), + $(described(codeUnits().that().doNotHaveFullName(classNameDot + FIELD_A)), ALL_CODE_UNIT_DESCRIPTIONS), + $(described(methods().that().doNotHaveFullName(classNameDot + METHOD_A)), allMethodsExcept(METHOD_A)), + $(described(constructors().that().doNotHaveFullName(classNameDot + CONSTRUCTOR_ONE_ARG)), allConstructorsExcept(CONSTRUCTOR_ONE_ARG)), + $(described(fields().that().doNotHaveFullName(classNameDot + FIELD_A)), allFieldsExcept(FIELD_A)), + + $(described(members().that().haveFullNameMatching(quote(classNameDot) + ".*A.*")), ImmutableSet.of(FIELD_A, METHOD_A)), + $(described(codeUnits().that().haveFullNameMatching(quote(classNameDot) + ".*A.*")), ImmutableSet.of(METHOD_A)), + $(described(methods().that().haveFullNameMatching(quote(classNameDot) + ".*A.*")), ImmutableSet.of(METHOD_A)), + $(described(constructors().that().haveFullNameMatching(".*init.*")), ALL_CONSTRUCTOR_DESCRIPTIONS), + $(described(fields().that().haveFullNameMatching(quote(classNameDot) + ".*A.*")), ImmutableSet.of(FIELD_A)), + $(described(members().that().haveFullNameNotMatching(quote(classNameDot) + "f.*A.*")), union( + allFieldsExcept(FIELD_A), + ALL_CODE_UNIT_DESCRIPTIONS)), + $(described(codeUnits().that().haveFullNameNotMatching(quote(classNameDot) + "f.*A.*")), ALL_CODE_UNIT_DESCRIPTIONS), + $(described(methods().that().haveFullNameNotMatching(quote(classNameDot) + ".*A.*")), allMethodsExcept(METHOD_A)), + $(described(constructors().that().haveFullNameNotMatching(".*init.*")), emptySet()), + $(described(fields().that().haveFullNameNotMatching(quote(classNameDot) + ".*A.*")), allFieldsExcept(FIELD_A)), + $(described(members().that().arePublic()), ImmutableSet.of( FIELD_PUBLIC, METHOD_PUBLIC, CONSTRUCTOR_PUBLIC)), $(described(fields().that().arePublic()), ImmutableSet.of(FIELD_C)),