diff --git a/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedNaming.java b/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedNaming.java index 67dc24037f..698000927c 100644 --- a/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedNaming.java +++ b/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedNaming.java @@ -19,20 +19,20 @@ private Creator(String className, String simpleName) { } public ExpectedMessage notStartingWith(String prefix) { - return expectedSimpleName(String.format("does not start with '%s'", prefix)); + return expectedClassViolation(String.format("does not have simple name starting with '%s'", prefix)); } public ExpectedMessage notEndingWith(String suffix) { - return expectedSimpleName(String.format("does not end with '%s'", suffix)); + return expectedClassViolation(String.format("does not have simple name ending with '%s'", suffix)); } public ExpectedMessage containing(String infix) { - return expectedSimpleName(String.format("contains '%s'", infix)); + return expectedClassViolation(String.format("has simple name containing '%s'", infix)); } - private ExpectedMessage expectedSimpleName(String suffix) { - return new ExpectedMessage(String.format("simple name of %s %s in (%s.java:0)", - className, suffix, simpleName)); + private ExpectedMessage expectedClassViolation(String description) { + return new ExpectedMessage(String.format("Class <%s> %s in (%s.java:0)", + className, description, simpleName)); } } } diff --git a/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedViolation.java b/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedViolation.java index 1aac60b37a..0b46dd2a11 100644 --- a/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedViolation.java +++ b/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedViolation.java @@ -137,12 +137,12 @@ private ClassAssertionCreator(Class clazz) { } public MessageAssertionChain.Link havingNameMatching(String regex) { - return MessageAssertionChain.containsLine("Class <%s> matches '%s' in (%s.java:0)", + return MessageAssertionChain.containsLine("Class <%s> has name matching '%s' in (%s.java:0)", clazz.getName(), regex, clazz.getSimpleName()); } public MessageAssertionChain.Link havingSimpleNameContaining(String infix) { - return MessageAssertionChain.containsLine("simple name of %s contains '%s' in (%s.java:0)", + return MessageAssertionChain.containsLine("Class <%s> has simple name containing '%s' in (%s.java:0)", clazz.getName(), infix, clazz.getSimpleName()); } diff --git a/archunit/src/main/java/com/tngtech/archunit/base/DescribedPredicate.java b/archunit/src/main/java/com/tngtech/archunit/base/DescribedPredicate.java index 99b6169c71..2e56c7cea2 100644 --- a/archunit/src/main/java/com/tngtech/archunit/base/DescribedPredicate.java +++ b/archunit/src/main/java/com/tngtech/archunit/base/DescribedPredicate.java @@ -51,6 +51,19 @@ public String getDescription() { return description; } + /** + * Overwrites the description of this {@link DescribedPredicate}. E.g. + * + *

+     * classes().that(predicate.as("some customized description with '%s'", "parameter")).should().bePublic()
+     * 
+ * + * would then yield {@code classes that some customized description with 'parameter' should be public}. + * + * @param description The new description of this {@link DescribedPredicate} + * @param params Optional arguments to fill into the description via {@link String#format(String, Object...)} + * @return An {@link DescribedPredicate} with adjusted {@link #getDescription() description}. + */ public DescribedPredicate as(String description, Object... params) { return new AsPredicate<>(this, description, params); } @@ -68,7 +81,16 @@ public DescribedPredicate onResultOf(final Function} Can't specify this contravariant type at the language level + * Convenience method to downcast the predicate. {@link DescribedPredicate DescribedPredicates} are contravariant by nature, + * i.e. an {@code DescribedPredicate} is an instance of {@code DescribedPredicate}, if and only if {@code V} is an instance of {@code T}. + *
+ * Take for example {@code Object > String}. Obviously a {@code DescribedPredicate} is also a {@code DescribedPredicate}. + *
+ * Unfortunately, the Java type system does not allow us to express this property of the type parameter of {@code DescribedPredicate}. + * So to avoid forcing users to cast everywhere it is possible to use this method which also documents the intention and reasoning. + * + * @return A {@link DescribedPredicate} accepting a subtype of the predicate's actual type parameter {@code T} + * @param A subtype of the {@link DescribedPredicate DescribedPredicate's} type parameter {@code T} */ @SuppressWarnings("unchecked") // DescribedPredicate is contravariant public final DescribedPredicate forSubtype() { diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/ArchCondition.java b/archunit/src/main/java/com/tngtech/archunit/lang/ArchCondition.java index 07cfa69e23..07cdee02ea 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/ArchCondition.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/ArchCondition.java @@ -18,9 +18,15 @@ import java.util.Collection; import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.base.HasDescription; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.properties.HasSourceCodeLocation; import com.tngtech.archunit.lang.conditions.ArchConditions; +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; import static com.tngtech.archunit.PublicAPI.Usage.INHERITANCE; +import static com.tngtech.archunit.lang.ConditionEvent.createMessage; @PublicAPI(usage = INHERITANCE) public abstract class ArchCondition { @@ -63,6 +69,19 @@ public String getDescription() { return description; } + /** + * Overwrites the description of this {@link ArchCondition}. E.g. + * + *

+     * classes().should(condition.as("some customized description with '%s'", "parameter"))
+     * 
+ * + * would then yield {@code classes should some customized description with 'parameter'}. + * + * @param description The new description of this {@link ArchCondition} + * @param args Optional arguments to fill into the description via {@link String#format(String, Object...)} + * @return An {@link ArchCondition} with adjusted {@link #getDescription() description}. + */ public ArchCondition as(String description, Object... args) { return new ArchCondition(description, args) { @Override @@ -87,8 +106,125 @@ public String toString() { return getDescription(); } + /** + * Convenience method to downcast the condition. {@link ArchCondition ArchConditions} are contravariant by nature, + * i.e. an {@code ArchCondition} is an instance of {@code ArchCondition}, if and only if {@code V} is an instance of {@code T}. + *
+ * Take for example {@code Object > String}. Obviously an {@code ArchCondition} is also an {@code ArchCondition}. + *
+ * Unfortunately, the Java type system does not allow us to express this property of the type parameter of {@code ArchCondition}. + * So to avoid forcing users to cast everywhere it is possible to use this method which also documents the intention and reasoning. + * + * @return An {@link ArchCondition} accepting a subtype of the condition's actual type parameter {@code T} + * @param A subtype of the {@link ArchCondition ArchCondition's} type parameter {@code T} + */ @SuppressWarnings("unchecked") // Cast is safe since input parameter is contravariant public ArchCondition forSubtype() { return (ArchCondition) this; } + + /** + * Creates an {@link ArchCondition} from a {@link DescribedPredicate}. + * For more information see {@link ConditionByPredicate ConditionByPredicate}. + * For more convenient versions of this method compare {@link ArchConditions#have(DescribedPredicate)} and {@link ArchConditions#be(DescribedPredicate)}. + * + * @param predicate Specifies which objects satisfy the condition. + * @return A {@link ConditionByPredicate ConditionByPredicate} derived from the supplied {@link DescribedPredicate predicate} + * @param The type of object the {@link ArchCondition condition} will check + * + * @see ArchConditions#have(DescribedPredicate) + * @see ArchConditions#be(DescribedPredicate) + */ + @PublicAPI(usage = ACCESS) + public static ConditionByPredicate from(DescribedPredicate predicate) { + return new ConditionByPredicate<>(predicate); + } + + /** + * An {@link ArchCondition} that derives which objects satisfy/violate the condition from a {@link DescribedPredicate}. + * The description is taken from the defining {@link DescribedPredicate predicate} but can be overridden via {@link #as(String, Object...)}. + * How the message of each single {@link ConditionEvent event} is derived can be customized by {@link #describeEventsBy(EventDescriber)}. + * + * @param The type of object the condition will test + */ + @PublicAPI(usage = ACCESS) + public static final class ConditionByPredicate extends ArchCondition { + private final DescribedPredicate predicate; + private final EventDescriber eventDescriber; + + private ConditionByPredicate(DescribedPredicate predicate) { + this(predicate, predicate.getDescription(), ((predicateDescription, satisfied) -> (satisfied ? "satisfies " : "does not satisfy ") + predicateDescription)); + } + + private ConditionByPredicate( + DescribedPredicate predicate, + String description, + EventDescriber eventDescriber + ) { + super(description); + this.predicate = predicate.forSubtype(); + this.eventDescriber = eventDescriber; + } + + /** + * Adjusts how this {@link ConditionByPredicate condition} will create the description of the {@link ConditionEvent events}. + * E.g. assume the {@link DescribedPredicate predicate} of this condition is {@link JavaClass.Predicates#simpleName(String) simpleName(name)}, + * then this method could be used to adjust the event description as + * + *

+         * condition.describeEventsBy((predicateDescription, satisfied) ->
+         *     (satisfied ? "has " : "does not have ") + predicateDescription
+         * )
+ * + * @param eventDescriber Specifies how to create the description of the {@link ConditionEvent} + * whenever the predicate is evaluated against an object. + * @return A {@link ConditionByPredicate ConditionByPredicate} that describes its {@link ConditionEvent events} with the given {@link EventDescriber EventDescriber} + */ + @PublicAPI(usage = ACCESS) + public ConditionByPredicate describeEventsBy(EventDescriber eventDescriber) { + return new ConditionByPredicate<>( + predicate, + getDescription(), + eventDescriber + ); + } + + @Override + public ConditionByPredicate as(String description, Object... args) { + return new ConditionByPredicate<>(predicate, String.format(description, args), eventDescriber); + } + + @Override + @SuppressWarnings("unchecked") // Cast is safe since input parameter is contravariant + public ConditionByPredicate forSubtype() { + return (ConditionByPredicate) this; + } + + @Override + public void check(T object, ConditionEvents events) { + boolean satisfied = predicate.test(object); + String message = createMessage(object, eventDescriber.describe(predicate.getDescription(), satisfied)); + events.add(new SimpleConditionEvent(object, satisfied, message)); + } + + /** + * Defines how to describe a single {@link ConditionEvent}. E.g. how to describe the concrete violation of some class + * {@code com.Example} that violates the {@link ConditionByPredicate}. + */ + @FunctionalInterface + @PublicAPI(usage = INHERITANCE) + public interface EventDescriber { + /** + * Describes a {@link ConditionEvent} created by {@link ConditionByPredicate ConditionByPredicate}, + * given the description of the defining predicate and whether the predicate was satisfied.
+ * For example, if the defining {@link DescribedPredicate} would be {@link JavaClass.Predicates#simpleName(String)}, then + * the created description could be {@code (satisfied ? "has " : "does not have ") + predicateDescription}. + * + * @param predicateDescription The description of the {@link DescribedPredicate} defining the {@link ConditionByPredicate ConditionByPredicate} + * @param satisfied Whether the object tested by the {@link ConditionByPredicate ConditionByPredicate} satisfied the condition + * @return The description of the {@link ConditionEvent} to be created + */ + String describe(String predicateDescription, boolean satisfied); + } + } } 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 81424d4e83..0f982a56c8 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 @@ -60,6 +60,7 @@ import com.tngtech.archunit.core.domain.properties.HasSourceCodeLocation; import com.tngtech.archunit.core.domain.properties.HasThrowsClause; import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ArchCondition.ConditionByPredicate; import com.tngtech.archunit.lang.ConditionEvents; import com.tngtech.archunit.lang.SimpleConditionEvent; import com.tngtech.archunit.lang.conditions.ClassAccessesFieldCondition.ClassGetsFieldCondition; @@ -86,6 +87,15 @@ import static com.tngtech.archunit.core.domain.JavaClass.Functions.GET_FIELD_ACCESSES_FROM_SELF; import static com.tngtech.archunit.core.domain.JavaClass.Functions.GET_METHOD_CALLS_FROM_SELF; import static com.tngtech.archunit.core.domain.JavaClass.Functions.GET_PACKAGE_NAME; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.ANONYMOUS_CLASSES; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.ENUMS; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.INNER_CLASSES; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.INTERFACES; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.LOCAL_CLASSES; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.MEMBER_CLASSES; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.NESTED_CLASSES; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.RECORDS; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.TOP_LEVEL_CLASSES; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.assignableFrom; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.assignableTo; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.equivalentTo; @@ -117,8 +127,6 @@ import static com.tngtech.archunit.core.domain.properties.HasReturnType.Predicates.rawReturnType; import static com.tngtech.archunit.core.domain.properties.HasThrowsClause.Predicates.throwsClauseContainingType; import static com.tngtech.archunit.core.domain.properties.HasType.Predicates.rawType; -import static com.tngtech.archunit.lang.ConditionEvent.createMessage; -import static com.tngtech.archunit.lang.conditions.ArchPredicates.have; import static java.util.Arrays.asList; public final class ArchConditions { @@ -459,7 +467,9 @@ public static ArchCondition notBe(final Class clazz) { @PublicAPI(usage = ACCESS) public static ArchCondition be(final String className) { - return new BeClassCondition(className); + return be(fullyQualifiedName(className).as(className)) + .describeEventsBy((__, satisfied) -> (satisfied ? "is " : "is not ") + className) + .forSubtype(); } @PublicAPI(usage = ACCESS) @@ -469,29 +479,29 @@ public static ArchCondition notBe(final String className) { @PublicAPI(usage = ACCESS) public static ArchCondition haveName(final String name) { - return new HaveConditionByPredicate<>(name(name)); + return have(name(name)); } @PublicAPI(usage = ACCESS) public static ArchCondition notHaveName(String name) { - return not(ArchConditions.haveName(name)); + return not(haveName(name)); } @PublicAPI(usage = ACCESS) public static ArchCondition haveFullName(String fullName) { - return new HaveConditionByPredicate<>(fullName(fullName)); + return have(fullName(fullName)); } @PublicAPI(usage = ACCESS) public static ArchCondition notHaveFullName(String fullName) { - return not(new HaveConditionByPredicate<>(fullName(fullName))); + return not(haveFullName(fullName)); } @PublicAPI(usage = ACCESS) public static ArchCondition haveFullyQualifiedName(final String name) { - return new HaveConditionByPredicate<>(fullyQualifiedName(name)); + return have(fullyQualifiedName(name)); } @Internal @@ -507,8 +517,7 @@ public static ArchCondition notHaveFullyQualifiedName(String name) { @PublicAPI(usage = ACCESS) public static ArchCondition haveSimpleName(final String name) { - final DescribedPredicate haveSimpleName = have(simpleName(name)); - return new SimpleNameCondition(haveSimpleName, name); + return have(simpleName(name)); } @PublicAPI(usage = ACCESS) @@ -518,9 +527,7 @@ public static ArchCondition notHaveSimpleName(String name) { @PublicAPI(usage = ACCESS) public static ArchCondition haveSimpleNameStartingWith(final String prefix) { - final DescribedPredicate predicate = have(simpleNameStartingWith(prefix)); - - return new SimpleNameStartingWithCondition(predicate, prefix); + return have(simpleNameStartingWith(prefix)); } @PublicAPI(usage = ACCESS) @@ -530,9 +537,7 @@ public static ArchCondition haveSimpleNameNotStartingWith(String pref @PublicAPI(usage = ACCESS) public static ArchCondition haveSimpleNameContaining(final String infix) { - final DescribedPredicate predicate = have(simpleNameContaining(infix)); - - return new SimpleNameContainingCondition(predicate, infix); + return have(simpleNameContaining(infix)); } @PublicAPI(usage = ACCESS) @@ -542,9 +547,7 @@ public static ArchCondition haveSimpleNameNotContaining(final String @PublicAPI(usage = ACCESS) public static ArchCondition haveSimpleNameEndingWith(final String suffix) { - final DescribedPredicate predicate = have(simpleNameEndingWith(suffix)); - - return new SimpleNameEndingWithCondition(predicate, suffix); + return have(simpleNameEndingWith(suffix)); } @PublicAPI(usage = ACCESS) @@ -554,8 +557,7 @@ public static ArchCondition haveSimpleNameNotEndingWith(String suffix @PublicAPI(usage = ACCESS) public static ArchCondition haveNameMatching(final String regex) { - final DescribedPredicate haveNameMatching = have(nameMatching(regex)).forSubtype(); - return new MatchingCondition<>(haveNameMatching, regex); + return have(nameMatching(regex)); } @PublicAPI(usage = ACCESS) @@ -566,8 +568,7 @@ public static ArchCondition haveFullNameMatching(String regex) { - final DescribedPredicate haveFullNameMatching = have(fullNameMatching(regex)).forSubtype(); - return new MatchingCondition<>(haveFullNameMatching, regex); + return have(fullNameMatching(regex)); } @PublicAPI(usage = ACCESS) @@ -577,71 +578,59 @@ ArchCondition haveFullNameMatching(String regex) { } @PublicAPI(usage = ACCESS) - public static ArchCondition - haveNameStartingWith(String prefix) { - final DescribedPredicate haveNameStartingWith = have(nameStartingWith(prefix)).forSubtype(); - return new StartingCondition<>(haveNameStartingWith, prefix); + public static ArchCondition haveNameStartingWith(String prefix) { + return have(nameStartingWith(prefix)); } @PublicAPI(usage = ACCESS) - public static ArchCondition - haveNameNotStartingWith(String prefix) { - final DescribedPredicate haveNameStartingWith = have(nameStartingWith(prefix)).forSubtype(); - return not(new StartingCondition<>(haveNameStartingWith, prefix)).as("have name not starting with '%s'", prefix); + public static ArchCondition haveNameNotStartingWith(String prefix) { + return not(haveNameStartingWith(prefix)).forSubtype().as("have name not starting with '%s'", prefix); } @PublicAPI(usage = ACCESS) - public static ArchCondition - haveNameContaining(String infix) { - final DescribedPredicate haveNameContaining = have(nameContaining(infix)).forSubtype(); - return new ContainingCondition<>(haveNameContaining, infix); + public static ArchCondition haveNameContaining(String infix) { + return have(nameContaining(infix)); } @PublicAPI(usage = ACCESS) - public static ArchCondition - haveNameNotContaining(String infix) { - final DescribedPredicate haveNameContaining = have(nameContaining(infix)).forSubtype(); - return not(new ContainingCondition<>(haveNameContaining, infix)).as("have name not containing '%s'", infix); + public static ArchCondition haveNameNotContaining(String infix) { + return not(haveNameContaining(infix)).forSubtype().as("have name not containing '%s'", infix); } @PublicAPI(usage = ACCESS) - public static ArchCondition - haveNameEndingWith(String suffix) { - final DescribedPredicate haveNameEndingWith = have(nameEndingWith(suffix)).forSubtype(); - return new EndingCondition<>(haveNameEndingWith, suffix); + public static ArchCondition haveNameEndingWith(String suffix) { + return have(nameEndingWith(suffix)); } @PublicAPI(usage = ACCESS) - public static ArchCondition - haveNameNotEndingWith(String suffix) { - final DescribedPredicate haveNameEndingWith = have(nameEndingWith(suffix)).forSubtype(); - return not(new EndingCondition<>(haveNameEndingWith, suffix)).as("have name not ending with '%s'", suffix); + public static ArchCondition haveNameNotEndingWith(String suffix) { + return not(haveNameEndingWith(suffix)).forSubtype().as("have name not ending with '%s'", suffix); } @PublicAPI(usage = ACCESS) public static ArchCondition resideInAPackage(final String packageIdentifier) { - return new DoesConditionByPredicate<>(JavaClass.Predicates.resideInAPackage(packageIdentifier)); + return does(JavaClass.Predicates.resideInAPackage(packageIdentifier)); } @PublicAPI(usage = ACCESS) public static ArchCondition resideInAnyPackage(String... packageIdentifiers) { - return new DoesConditionByPredicate<>(JavaClass.Predicates.resideInAnyPackage(packageIdentifiers)); + return does(JavaClass.Predicates.resideInAnyPackage(packageIdentifiers)); } @PublicAPI(usage = ACCESS) public static ArchCondition resideOutsideOfPackage(String packageIdentifier) { - return new DoesConditionByPredicate<>(JavaClass.Predicates.resideOutsideOfPackage(packageIdentifier)); + return does(JavaClass.Predicates.resideOutsideOfPackage(packageIdentifier)); } @PublicAPI(usage = ACCESS) public static ArchCondition resideOutsideOfPackages(String... packageIdentifiers) { - return new DoesConditionByPredicate<>(JavaClass.Predicates.resideOutsideOfPackages(packageIdentifiers)); + return does(JavaClass.Predicates.resideOutsideOfPackages(packageIdentifiers)); } @PublicAPI(usage = ACCESS) public static ArchCondition haveModifier( final JavaModifier modifier) { - return new HaveConditionByPredicate<>(modifier(modifier)); + return have(modifier(modifier)); } @PublicAPI(usage = ACCESS) @@ -729,7 +718,7 @@ public static ArchCondition haveOnlyPrivateConstructors() { @PublicAPI(usage = ACCESS) public static & HasDescription & HasSourceCodeLocation> ArchCondition beAnnotatedWith( Class type) { - return new IsConditionByPredicate<>(annotatedWith(type)); + return be(annotatedWith(type)); } /** @@ -747,7 +736,7 @@ public static & HasDescription & HasS @PublicAPI(usage = ACCESS) public static & HasDescription & HasSourceCodeLocation> ArchCondition beAnnotatedWith( String typeName) { - return new IsConditionByPredicate<>(annotatedWith(typeName)); + return be(annotatedWith(typeName)); } /** @@ -765,7 +754,7 @@ public static & HasDescription & HasS @PublicAPI(usage = ACCESS) public static & HasDescription & HasSourceCodeLocation> ArchCondition beAnnotatedWith( final DescribedPredicate> predicate) { - return new IsConditionByPredicate<>(annotatedWith(predicate)); + return be(annotatedWith(predicate)); } /** @@ -783,7 +772,7 @@ public static & HasDescription & HasS @PublicAPI(usage = ACCESS) public static & HasDescription & HasSourceCodeLocation> ArchCondition beMetaAnnotatedWith( Class type) { - return new IsConditionByPredicate<>(metaAnnotatedWith(type)); + return be(metaAnnotatedWith(type)); } /** @@ -801,7 +790,7 @@ public static & HasDescription & HasS @PublicAPI(usage = ACCESS) public static & HasDescription & HasSourceCodeLocation> ArchCondition beMetaAnnotatedWith( String typeName) { - return new IsConditionByPredicate<>(metaAnnotatedWith(typeName)); + return be(metaAnnotatedWith(typeName)); } /** @@ -819,7 +808,7 @@ public static & HasDescription & HasS @PublicAPI(usage = ACCESS) public static & HasDescription & HasSourceCodeLocation> ArchCondition beMetaAnnotatedWith( final DescribedPredicate> predicate) { - return new IsConditionByPredicate<>(metaAnnotatedWith(predicate)); + return be(metaAnnotatedWith(predicate)); } /** @@ -836,7 +825,7 @@ public static & HasDescription & HasS */ @PublicAPI(usage = ACCESS) public static ArchCondition implement(Class interfaceType) { - return new ImplementsCondition(JavaClass.Predicates.implement(interfaceType)); + return does(JavaClass.Predicates.implement(interfaceType)); } /** @@ -852,7 +841,7 @@ public static ArchCondition notImplement(Class interfaceType) { */ @PublicAPI(usage = ACCESS) public static ArchCondition implement(String interfaceTypeName) { - return new ImplementsCondition(JavaClass.Predicates.implement(interfaceTypeName)); + return does(JavaClass.Predicates.implement(interfaceTypeName)); } /** @@ -868,7 +857,7 @@ public static ArchCondition notImplement(String interfaceTypeName) { */ @PublicAPI(usage = ACCESS) public static ArchCondition implement(DescribedPredicate predicate) { - return new ImplementsCondition(JavaClass.Predicates.implement(predicate)); + return does(JavaClass.Predicates.implement(predicate)); } /** @@ -884,7 +873,7 @@ public static ArchCondition notImplement(DescribedPredicate beAssignableTo(Class type) { - return new IsConditionByPredicate<>(assignableTo(type)); + return be(assignableTo(type)); } /** @@ -900,7 +889,7 @@ public static ArchCondition notBeAssignableTo(Class type) { */ @PublicAPI(usage = ACCESS) public static ArchCondition beAssignableTo(String typeName) { - return new IsConditionByPredicate<>(assignableTo(typeName)); + return be(assignableTo(typeName)); } /** @@ -916,7 +905,7 @@ public static ArchCondition notBeAssignableTo(String typeName) { */ @PublicAPI(usage = ACCESS) public static ArchCondition beAssignableTo(DescribedPredicate predicate) { - return new IsConditionByPredicate<>(assignableTo(predicate)); + return be(assignableTo(predicate)); } /** @@ -932,7 +921,7 @@ public static ArchCondition notBeAssignableTo(DescribedPredicate beAssignableFrom(Class type) { - return new IsConditionByPredicate<>(assignableFrom(type)); + return be(assignableFrom(type)); } /** @@ -948,7 +937,7 @@ public static ArchCondition notBeAssignableFrom(Class type) { */ @PublicAPI(usage = ACCESS) public static ArchCondition beAssignableFrom(String typeName) { - return new IsConditionByPredicate<>(assignableFrom(typeName)); + return be(assignableFrom(typeName)); } /** @@ -964,7 +953,7 @@ public static ArchCondition notBeAssignableFrom(String typeName) { */ @PublicAPI(usage = ACCESS) public static ArchCondition beAssignableFrom(DescribedPredicate predicate) { - return new IsConditionByPredicate<>(assignableFrom(predicate)); + return be(assignableFrom(predicate)); } /** @@ -981,7 +970,7 @@ public static ArchCondition notBeAssignableFrom(DescribedPredicate beInterfaces() { - return InterfacesCondition.BE_INTERFACES; + return be(INTERFACES).describeEventsBy((__, satisfied) -> (satisfied ? "is an" : "is no") + " interface"); } /** @@ -989,7 +978,7 @@ public static ArchCondition beInterfaces() { */ @PublicAPI(usage = ACCESS) public static ArchCondition notBeInterfaces() { - return not(InterfacesCondition.BE_INTERFACES); + return not(beInterfaces()); } /** @@ -998,7 +987,7 @@ public static ArchCondition notBeInterfaces() { */ @PublicAPI(usage = ACCESS) public static ArchCondition beEnums() { - return EnumsCondition.BE_ENUMS; + return be(ENUMS).describeEventsBy((__, satisfied) -> (satisfied ? "is an" : "is no") + " enum"); } /** @@ -1006,7 +995,7 @@ public static ArchCondition beEnums() { */ @PublicAPI(usage = ACCESS) public static ArchCondition notBeEnums() { - return not(EnumsCondition.BE_ENUMS); + return not(beEnums()); } /** @@ -1016,7 +1005,7 @@ public static ArchCondition notBeEnums() { */ @PublicAPI(usage = ACCESS) public static ArchCondition beRecords() { - return RecordsCondition.BE_RECORDS; + return be(RECORDS).describeEventsBy((__, satisfied) -> (satisfied ? "is a" : "is no") + " record"); } /** @@ -1024,7 +1013,7 @@ public static ArchCondition beRecords() { */ @PublicAPI(usage = ACCESS) public static ArchCondition notBeRecords() { - return not(RecordsCondition.BE_RECORDS); + return not(beRecords()); } /** @@ -1142,7 +1131,7 @@ public static ArchCondition containNumberOfEl */ @PublicAPI(usage = ACCESS) public static ArchCondition beDeclaredIn(Class owner) { - return new IsConditionByPredicate<>(declaredIn(owner)); + return be(declaredIn(owner)); } /** @@ -1159,7 +1148,7 @@ public static ArchCondition notBeDeclaredIn(Class owner) { */ @PublicAPI(usage = ACCESS) public static ArchCondition beDeclaredIn(String ownerTypeName) { - return new IsConditionByPredicate<>(declaredIn(ownerTypeName)); + return be(declaredIn(ownerTypeName)); } /** @@ -1177,7 +1166,7 @@ public static ArchCondition notBeDeclaredIn(String ownerTypeName) { public static ArchCondition beDeclaredInClassesThat(DescribedPredicate predicate) { DescribedPredicate declaredIn = declaredIn( predicate.as("classes that " + predicate.getDescription())); - return new IsConditionByPredicate<>(declaredIn); + return be(declaredIn); } /** @@ -1187,7 +1176,7 @@ public static ArchCondition beDeclaredInClassesThat(DescribedPredica */ @PublicAPI(usage = ACCESS) public static ArchCondition haveRawType(Class type) { - return new HaveConditionByPredicate<>(rawType(type)); + return have(rawType(type)); } /** @@ -1195,7 +1184,7 @@ public static ArchCondition haveRawType(Class type) { */ @PublicAPI(usage = ACCESS) public static ArchCondition haveRawType(String typeName) { - return new HaveConditionByPredicate<>(rawType(typeName)); + return have(rawType(typeName)); } /** @@ -1203,37 +1192,37 @@ public static ArchCondition haveRawType(String typeName) { */ @PublicAPI(usage = ACCESS) public static ArchCondition haveRawType(DescribedPredicate predicate) { - return new HaveConditionByPredicate<>(rawType(predicate)); + return have(rawType(predicate)); } @PublicAPI(usage = ACCESS) public static ArchCondition haveRawParameterTypes(Class... parameterTypes) { - return new HaveConditionByPredicate<>(rawParameterTypes(parameterTypes)); + return have(rawParameterTypes(parameterTypes)); } @PublicAPI(usage = ACCESS) public static ArchCondition haveRawParameterTypes(String... parameterTypeNames) { - return new HaveConditionByPredicate<>(rawParameterTypes(parameterTypeNames)); + return have(rawParameterTypes(parameterTypeNames)); } @PublicAPI(usage = ACCESS) public static ArchCondition haveRawParameterTypes(DescribedPredicate> predicate) { - return new HaveConditionByPredicate<>(rawParameterTypes(predicate)); + return have(rawParameterTypes(predicate)); } @PublicAPI(usage = ACCESS) public static ArchCondition haveRawReturnType(Class type) { - return new HaveConditionByPredicate<>(rawReturnType(type)); + return have(rawReturnType(type)); } @PublicAPI(usage = ACCESS) public static ArchCondition haveRawReturnType(String typeName) { - return new HaveConditionByPredicate<>(rawReturnType(typeName)); + return have(rawReturnType(typeName)); } @PublicAPI(usage = ACCESS) public static ArchCondition haveRawReturnType(DescribedPredicate predicate) { - return new HaveConditionByPredicate<>(rawReturnType(predicate)); + return have(rawReturnType(predicate)); } @PublicAPI(usage = ACCESS) @@ -1250,7 +1239,7 @@ public static ArchCondition declareThrowableOfType(String typeName public static ArchCondition declareThrowableOfType(DescribedPredicate predicate) { DescribedPredicate> declareThrowableOfType = throwsClauseContainingType(predicate) .as("declare throwable of type " + predicate.getDescription()); - return new DoesConditionByPredicate<>(declareThrowableOfType); + return does(declareThrowableOfType); } @PublicAPI(usage = ACCESS) @@ -1282,18 +1271,56 @@ public static ArchCondition onlyBeCalledByConstructorsThat(Describ origin.is(constructor().and(predicate)), GET_CALLS_OF_SELF); } - private static final IsConditionByPredicate BE_TOP_LEVEL_CLASSES = - new IsConditionByPredicate<>("a top level class", JavaClass.Predicates.TOP_LEVEL_CLASSES); - private static final IsConditionByPredicate BE_NESTED_CLASSES = - new IsConditionByPredicate<>("a nested class", JavaClass.Predicates.NESTED_CLASSES); - private static final IsConditionByPredicate BE_MEMBER_CLASSES = - new IsConditionByPredicate<>("a member class", JavaClass.Predicates.MEMBER_CLASSES); - private static final IsConditionByPredicate BE_INNER_CLASSES = - new IsConditionByPredicate<>("an inner class", JavaClass.Predicates.INNER_CLASSES); - private static final IsConditionByPredicate BE_ANONYMOUS_CLASSES = - new IsConditionByPredicate<>("an anonymous class", JavaClass.Predicates.ANONYMOUS_CLASSES); - private static final IsConditionByPredicate BE_LOCAL_CLASSES = - new IsConditionByPredicate<>("a local class", JavaClass.Predicates.LOCAL_CLASSES); + /** + * Derives an {@link ArchCondition} from a {@link DescribedPredicate}. Similar to {@link ArchCondition#from(DescribedPredicate)}, + * but more conveniently creates a message to be used within a 'have'-sentence. + *
+ * Take e.g. {@code have(simpleName("Demo"))}, then the condition description would be {@code "have simple name 'Demo'"}, + * each satisfied event would be described as {@code "Class has simple name 'Demo' in (Example.java:0)"} + * and each violated one as {@code "Class does not have simple name 'Demo' in (Example.java:0)"}. + * + * @param predicate The predicate determining which objects satisfy/violate the condition + * @return A {@link ConditionByPredicate ConditionByPredicate} derived from the predicate and with 'have'-descriptions + * @param The type of object the condition will test, e.g. a {@link JavaClass} + */ + @PublicAPI(usage = ACCESS) + public static ConditionByPredicate have(DescribedPredicate predicate) { + return ArchCondition.from(predicate.forSubtype()) + .as(ArchPredicates.have(predicate).getDescription()) + .describeEventsBy((predicateDescription, satisfied) -> (satisfied ? "has " : "does not have ") + predicateDescription); + } + + /** + * Derives an {@link ArchCondition} from a {@link DescribedPredicate}. Similar to {@link ArchCondition#from(DescribedPredicate)}, + * but more conveniently creates a message to be used within a 'be'-sentence. + *
+ * Take e.g. {@code be(assignableTo(Demo.class))}, then the condition description would be {@code "be assignable to com.example.Demo"}, + * each satisfied event would be described as {@code "Class is assignable to com.example.Demo in (Example.java:0)"} + * and each violated one as {@code "Class is not assignable to com.example.Demo in (Example.java:0)"}. + * + * @param predicate The predicate determining which objects satisfy/violate the condition + * @return A {@link ConditionByPredicate ConditionByPredicate} derived from the predicate and with 'be'-descriptions + * @param The type of object the condition will test, e.g. a {@link JavaClass} + */ + @PublicAPI(usage = ACCESS) + public static ConditionByPredicate be(DescribedPredicate predicate) { + return ArchCondition.from(predicate.forSubtype()) + .as(ArchPredicates.be(predicate).getDescription()) + .describeEventsBy((predicateDescription, satisfied) -> (satisfied ? "is " : "is not ") + predicateDescription); + } + + private static final ArchCondition BE_TOP_LEVEL_CLASSES = + be(TOP_LEVEL_CLASSES).describeEventsBy((__, satisfied) -> (satisfied ? "is a" : "is no") + " top level class"); + private static final ArchCondition BE_NESTED_CLASSES = + be(NESTED_CLASSES).describeEventsBy((__, satisfied) -> (satisfied ? "is a" : "is no") + " nested class"); + private static final ArchCondition BE_MEMBER_CLASSES = + be(MEMBER_CLASSES).describeEventsBy((__, satisfied) -> (satisfied ? "is a" : "is no") + " member class"); + private static final ArchCondition BE_INNER_CLASSES = + be(INNER_CLASSES).describeEventsBy((__, satisfied) -> (satisfied ? "is an" : "is no") + " inner class"); + private static final ArchCondition BE_ANONYMOUS_CLASSES = + be(ANONYMOUS_CLASSES).describeEventsBy((__, satisfied) -> (satisfied ? "is an" : "is no") + " anonymous class"); + private static final ArchCondition BE_LOCAL_CLASSES = + be(LOCAL_CLASSES).describeEventsBy((__, satisfied) -> (satisfied ? "is a" : "is no") + " local class"); private static class HaveOnlyModifiersCondition extends AllAttributesMatchCondition { @@ -1301,7 +1328,7 @@ private static class HaveOnlyModifiersCondition> getHasModifiers; HaveOnlyModifiersCondition(String description, final JavaModifier modifier, Function> getHasModifiers) { - super("have only " + description, new ModifierCondition<>(modifier)); + super("have only " + description, be(modifier(modifier).as(modifier.toString().toLowerCase()))); this.getHasModifiers = getHasModifiers; } @@ -1311,89 +1338,6 @@ Collection relevantAttributes(JavaClass javaClass) { } } - private static class ModifierCondition extends ArchCondition { - private final JavaModifier modifier; - - ModifierCondition(JavaModifier modifier) { - super("modifier " + modifier); - this.modifier = modifier; - } - - @Override - public void check(T hasModifiers, ConditionEvents events) { - boolean satisfied = hasModifiers.getModifiers().contains(modifier); - String infix = (satisfied ? "is " : "is not ") + modifier.toString().toLowerCase(); - events.add(new SimpleConditionEvent(hasModifiers, satisfied, createMessage(hasModifiers, infix))); - } - } - - private static class ImplementsCondition extends ArchCondition { - private final DescribedPredicate implement; - - ImplementsCondition(DescribedPredicate implement) { - super(implement.getDescription()); - this.implement = implement; - } - - @Override - public void check(JavaClass javaClass, ConditionEvents events) { - boolean satisfied = implement.test(javaClass); - String description = satisfied - ? implement.getDescription().replace("implement", "implements") - : implement.getDescription().replace("implement", "does not implement"); - String message = createMessage(javaClass, description); - events.add(new SimpleConditionEvent(javaClass, satisfied, message)); - } - } - - private static class InterfacesCondition extends ArchCondition { - private static final InterfacesCondition BE_INTERFACES = new InterfacesCondition(); - - InterfacesCondition() { - super("be interfaces"); - } - - @Override - public void check(JavaClass javaClass, ConditionEvents events) { - boolean isInterface = javaClass.isInterface(); - String message = createMessage(javaClass, - (isInterface ? "is an" : "is not an") + " interface"); - events.add(new SimpleConditionEvent(javaClass, isInterface, message)); - } - } - - private static class EnumsCondition extends ArchCondition { - private static final EnumsCondition BE_ENUMS = new EnumsCondition(); - - EnumsCondition() { - super("be enums"); - } - - @Override - public void check(JavaClass javaClass, ConditionEvents events) { - boolean isEnum = javaClass.isEnum(); - String message = createMessage(javaClass, - (isEnum ? "is an" : "is not an") + " enum"); - events.add(new SimpleConditionEvent(javaClass, isEnum, message)); - } - } - - private static class RecordsCondition extends ArchCondition { - private static final RecordsCondition BE_RECORDS = new RecordsCondition(); - - RecordsCondition() { - super("be records"); - } - - @Override - public void check(JavaClass javaClass, ConditionEvents events) { - boolean isRecord = javaClass.isRecord(); - String message = createMessage(javaClass, - (isRecord ? "is a" : "is not a") + " record"); - events.add(new SimpleConditionEvent(javaClass, isRecord, message)); - } - } - private static class NumberOfElementsCondition extends ArchCondition { private final DescribedPredicate predicate; private final SortedSet allElementNames = new TreeSet<>(); @@ -1421,238 +1365,8 @@ private String join(SortedSet strings) { } } - private static class BeClassCondition extends ArchCondition { - private final String className; - - BeClassCondition(String className) { - super("be " + className); - this.className = className; - } - - @Override - public void check(JavaClass javaClass, ConditionEvents events) { - boolean itemEquivalentToClazz = javaClass.getName().equals(className); - String message = createMessage(javaClass, - (itemEquivalentToClazz ? "is " : "is not ") + className); - events.add(new SimpleConditionEvent(javaClass, itemEquivalentToClazz, message)); - } - } - - private static class SimpleNameCondition extends ArchCondition { - private final DescribedPredicate haveSimpleName; - private final String name; - - SimpleNameCondition(DescribedPredicate haveSimpleName, String name) { - super(haveSimpleName.getDescription()); - this.haveSimpleName = haveSimpleName; - this.name = name; - } - - @Override - public void check(JavaClass javaClass, ConditionEvents events) { - boolean satisfied = haveSimpleName.test(javaClass); - String message = createMessage(javaClass, - String.format("%s simple name '%s'", satisfied ? "has" : "does not have", name)); - events.add(new SimpleConditionEvent(javaClass, satisfied, message)); - } - } - - private static class SimpleNameStartingWithCondition extends ArchCondition { - private final DescribedPredicate predicate; - private final String prefix; - - SimpleNameStartingWithCondition(DescribedPredicate predicate, String prefix) { - super(predicate.getDescription()); - this.predicate = predicate; - this.prefix = prefix; - } - - @Override - public void check(JavaClass javaClass, ConditionEvents events) { - boolean satisfied = predicate.test(javaClass); - String message = String.format("simple name of %s %s with '%s' in %s", - javaClass.getName(), - satisfied ? "starts" : "does not start", - prefix, - javaClass.getSourceCodeLocation()); - events.add(new SimpleConditionEvent(javaClass, satisfied, message)); - } - } - - private static class SimpleNameContainingCondition extends ArchCondition { - private final DescribedPredicate predicate; - private final String infix; - - SimpleNameContainingCondition(DescribedPredicate predicate, String infix) { - super(predicate.getDescription()); - this.predicate = predicate; - this.infix = infix; - } - - @Override - public void check(JavaClass javaClass, ConditionEvents events) { - boolean satisfied = predicate.test(javaClass); - String message = String.format("simple name of %s %s '%s' in %s", - javaClass.getName(), - satisfied ? "contains" : "does not contain", - infix, - javaClass.getSourceCodeLocation()); - events.add(new SimpleConditionEvent(javaClass, satisfied, message)); - } - } - - private static class SimpleNameEndingWithCondition extends ArchCondition { - private final DescribedPredicate predicate; - private final String suffix; - - SimpleNameEndingWithCondition(DescribedPredicate predicate, String suffix) { - super(predicate.getDescription()); - this.predicate = predicate; - this.suffix = suffix; - } - - @Override - public void check(JavaClass javaClass, ConditionEvents events) { - boolean satisfied = predicate.test(javaClass); - String message = String.format("simple name of %s %s with '%s' in %s", - javaClass.getName(), - satisfied ? "ends" : "does not end", - suffix, - javaClass.getSourceCodeLocation()); - events.add(new SimpleConditionEvent(javaClass, satisfied, message)); - } - } - - private static class MatchingCondition extends ArchCondition { - private final DescribedPredicate matcher; - private final String regex; - - MatchingCondition(DescribedPredicate matcher, String regex) { - super(matcher.getDescription()); - this.matcher = matcher; - this.regex = regex; - } - - @Override - public void check(T item, ConditionEvents events) { - boolean satisfied = matcher.test(item); - String message = createMessage(item, - String.format("%s '%s'", satisfied ? "matches" : "does not match", regex)); - events.add(new SimpleConditionEvent(item, satisfied, message)); - } - } - - private static class StartingCondition extends ArchCondition { - private final DescribedPredicate startingWith; - private final String prefix; - - StartingCondition(DescribedPredicate startingWith, String prefix) { - super(startingWith.getDescription()); - this.startingWith = startingWith; - this.prefix = prefix; - } - - @Override - public void check(T item, ConditionEvents events) { - boolean satisfied = startingWith.test(item); - String message = createMessage(item, - String.format("name %s '%s'", satisfied ? "starts with" : "does not start with", prefix)); - events.add(new SimpleConditionEvent(item, satisfied, message)); - } - } - - private static class ContainingCondition extends ArchCondition { - private final DescribedPredicate containing; - private final String infix; - - ContainingCondition(DescribedPredicate containing, String infix) { - super(containing.getDescription()); - this.containing = containing; - this.infix = infix; - } - - @Override - public void check(T item, ConditionEvents events) { - boolean satisfied = containing.test(item); - String message = createMessage(item, - String.format("name %s '%s'", satisfied ? "contains" : "does not contain", infix)); - events.add(new SimpleConditionEvent(item, satisfied, message)); - } - } - - private static class EndingCondition extends ArchCondition { - private final DescribedPredicate endingWith; - private final String suffix; - - EndingCondition(DescribedPredicate endingWith, String suffix) { - super(endingWith.getDescription()); - this.endingWith = endingWith; - this.suffix = suffix; - } - - @Override - public void check(T item, ConditionEvents events) { - boolean satisfied = endingWith.test(item); - String message = createMessage(item, - String.format("name %s '%s'", satisfied ? "ends with" : "does not end with", suffix)); - events.add(new SimpleConditionEvent(item, satisfied, message)); - } - } - - private static class DoesConditionByPredicate - extends ArchCondition { - private final DescribedPredicate predicate; - - DoesConditionByPredicate(DescribedPredicate predicate) { - super(predicate.getDescription()); - this.predicate = predicate; - } - - @Override - public void check(T item, ConditionEvents events) { - boolean satisfied = predicate.test(item); - String message = createMessage(item, - (satisfied ? "does " : "does not ") + predicate.getDescription()); - events.add(new SimpleConditionEvent(item, satisfied, message)); - } - } - - private static class IsConditionByPredicate extends ArchCondition { - private final String eventDescription; - private final DescribedPredicate predicate; - - IsConditionByPredicate(DescribedPredicate predicate) { - this(predicate.getDescription(), predicate); - } - - IsConditionByPredicate(String eventDescription, DescribedPredicate predicate) { - super(ArchPredicates.be(predicate).getDescription()); - this.eventDescription = eventDescription; - this.predicate = predicate.forSubtype(); - } - - @Override - public void check(T member, ConditionEvents events) { - boolean satisfied = predicate.test(member); - String message = createMessage(member, - (satisfied ? "is " : "is not ") + eventDescription); - events.add(new SimpleConditionEvent(member, satisfied, message)); - } - } - - private static class HaveConditionByPredicate extends ArchCondition { - private final DescribedPredicate rawType; - - HaveConditionByPredicate(DescribedPredicate rawType) { - super(ArchPredicates.have(rawType).getDescription()); - this.rawType = rawType.forSubtype(); - } - - @Override - public void check(T object, ConditionEvents events) { - boolean satisfied = rawType.test(object); - String message = createMessage(object, (satisfied ? "has " : "does not have ") + rawType.getDescription()); - events.add(new SimpleConditionEvent(object, satisfied, message)); - } + private static ArchCondition does(DescribedPredicate predicate) { + return ArchCondition.from(predicate.forSubtype()) + .describeEventsBy((predicateDescription, satisfied) -> (satisfied ? "does " : "does not ") + predicateDescription); } } diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/ArchConditionTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/ArchConditionTest.java index 2a005128a7..d36611f9de 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/ArchConditionTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/ArchConditionTest.java @@ -5,17 +5,25 @@ import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.function.Function; import com.google.common.collect.HashMultiset; import com.google.common.collect.Multiset; +import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.importer.ClassFileImporter; +import com.tngtech.archunit.lang.ArchCondition.ConditionByPredicate; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; import org.junit.Test; import org.junit.runner.RunWith; +import static com.tngtech.archunit.base.DescribedPredicate.alwaysFalse; +import static com.tngtech.archunit.base.DescribedPredicate.alwaysTrue; import static com.tngtech.archunit.lang.Priority.MEDIUM; import static com.tngtech.archunit.lang.conditions.ArchConditions.never; +import static com.tngtech.archunit.lang.conditions.ArchConditions.not; import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.java.junit.dataprovider.DataProviders.$; import static com.tngtech.java.junit.dataprovider.DataProviders.$$; @@ -226,6 +234,41 @@ public void double_never_or() { assertThat(events.containViolation()).as("Events contain violation").isFalse(); } + @Test + public void from_predicate() { + JavaClass object = new ClassFileImporter().importClass(Object.class); + + assertThat(ArchCondition.from(alwaysFalse().as("some description"))) + .hasDescription("some description") + .checking(object) + .containViolations(String.format("Class <%s> does not satisfy some description in (%s.java:0)", Object.class.getName(), Object.class.getSimpleName())); + + assertThat(ArchCondition.from(alwaysTrue())) + .checking(object).containNoViolation(); + + assertThat(not(ArchCondition.from(alwaysTrue().as("some description")).as("overwritten"))) + .hasDescription("not overwritten") + .checking(object) + .containViolations(String.format("Class <%s> satisfies some description in (%s.java:0)", Object.class.getName(), Object.class.getSimpleName())); + } + + @Test + public void from_predicate_describing_events() { + JavaClass object = new ClassFileImporter().importClass(Object.class); + + Function, ConditionByPredicate> conditionFromPredicate = + predicate -> ArchCondition.from(predicate.as("some description")) + .describeEventsBy((predicateDescription, satisfied) -> satisfied + " " + predicateDescription).forSubtype(); + + assertThat(conditionFromPredicate.apply(alwaysFalse())) + .checking(object) + .haveOneViolationMessageContaining(String.format("Class <%s> false some description", Object.class.getName())); + + assertThat(not(conditionFromPredicate.apply(alwaysTrue()))) + .checking(object) + .haveOneViolationMessageContaining(String.format("Class <%s> true some description", Object.class.getName())); + } + private ArchCondition greaterThan(final int... numbers) { return new ArchCondition("greater than " + Arrays.toString(numbers)) { @Override diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ArchConditionsTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ArchConditionsTest.java index ba65039759..145eda2cb3 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ArchConditionsTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/conditions/ArchConditionsTest.java @@ -15,6 +15,7 @@ import org.junit.Test; import static com.tngtech.archunit.base.DescribedPredicate.alwaysFalse; +import static com.tngtech.archunit.base.DescribedPredicate.alwaysTrue; import static com.tngtech.archunit.core.domain.JavaCall.Predicates.target; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.assignableTo; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.type; @@ -29,12 +30,15 @@ import static com.tngtech.archunit.lang.conditions.ArchConditions.accessClassesThat; import static com.tngtech.archunit.lang.conditions.ArchConditions.accessClassesThatResideIn; import static com.tngtech.archunit.lang.conditions.ArchConditions.accessClassesThatResideInAnyPackage; +import static com.tngtech.archunit.lang.conditions.ArchConditions.be; import static com.tngtech.archunit.lang.conditions.ArchConditions.callCodeUnitWhere; import static com.tngtech.archunit.lang.conditions.ArchConditions.callMethodWhere; import static com.tngtech.archunit.lang.conditions.ArchConditions.containAnyElementThat; import static com.tngtech.archunit.lang.conditions.ArchConditions.containOnlyElementsThat; import static com.tngtech.archunit.lang.conditions.ArchConditions.declareThrowableOfType; +import static com.tngtech.archunit.lang.conditions.ArchConditions.have; import static com.tngtech.archunit.lang.conditions.ArchConditions.never; +import static com.tngtech.archunit.lang.conditions.ArchConditions.not; import static com.tngtech.archunit.lang.conditions.ArchConditions.onlyBeAccessedByAnyPackage; import static com.tngtech.archunit.lang.conditions.ArchConditions.onlyHaveDependentsInAnyPackage; import static com.tngtech.archunit.lang.conditions.ArchConditions.onlyHaveDependentsWhere; @@ -102,6 +106,48 @@ public void descriptions() { .hasDescription("contain only elements that something"); } + @Test + public void have_predicate() { + JavaClass object = importClasses(Object.class).get(Object.class); + + assertThat(have(alwaysTrue().as("some description"))).hasDescription("have some description"); + + assertThat(have(DescribedPredicate.alwaysFalse().as("some description"))) + .checking(object) + .haveOneViolationMessageContaining("Class <" + Object.class.getName() + "> does not have some description"); + + assertThat(have(DescribedPredicate.alwaysFalse()).describeEventsBy((_1, _2) -> "overwritten")) + .checking(object) + .haveOneViolationMessageContaining("Class <" + Object.class.getName() + "> overwritten"); + + assertThat(have(DescribedPredicate.alwaysTrue())).checking(object).containNoViolation(); + + assertThat(not(have(DescribedPredicate.alwaysTrue().as("some description")))) + .checking(object) + .haveOneViolationMessageContaining("Class <" + Object.class.getName() + "> has some description"); + } + + @Test + public void be_predicate() { + JavaClass object = importClasses(Object.class).get(Object.class); + + assertThat(be(alwaysTrue().as("some description"))).hasDescription("be some description"); + + assertThat(be(DescribedPredicate.alwaysFalse().as("some description"))) + .checking(object) + .haveOneViolationMessageContaining("Class <" + Object.class.getName() + "> is not some description"); + + assertThat(be(DescribedPredicate.alwaysFalse()).describeEventsBy((_1, _2) -> "overwritten")) + .checking(object) + .haveOneViolationMessageContaining("Class <" + Object.class.getName() + "> overwritten"); + + assertThat(be(DescribedPredicate.alwaysTrue())).checking(object).containNoViolation(); + + assertThat(not(be(DescribedPredicate.alwaysTrue().as("some description")))) + .checking(object) + .haveOneViolationMessageContaining("Class <" + Object.class.getName() + "> is some description"); + } + @Test public void only_have_dependents_where() { JavaClasses classes = importClasses(CallingClass.class, SomeClass.class); diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ClassesShouldTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ClassesShouldTest.java index f968c52a71..314725f663 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ClassesShouldTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ClassesShouldTest.java @@ -195,7 +195,7 @@ public void haveNameMatching(ArchRule rule, String regex) { assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should have name matching '%s'", regex)) - .containsPattern(String.format("Class <%s> does not match '%s' in %s", + .containsPattern(String.format("Class <%s> does not have name matching '%s' in %s", quote(WrongNamedClass.class.getName()), quote(regex), locationPattern(WrongNamedClass.class))) @@ -219,7 +219,7 @@ public void haveNameNotMatching(ArchRule rule, String regex) { assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should have name not matching '%s'", regex)) - .containsPattern(String.format("Class <%s> matches '%s' in %s", + .containsPattern(String.format("Class <%s> has name matching '%s' in %s", quote(WrongNamedClass.class.getName()), quote(regex), locationPattern(WrongNamedClass.class))) @@ -244,7 +244,7 @@ public void haveSimpleNameStartingWith(ArchRule rule, String prefix) { assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should have simple name starting with '%s'", prefix)) - .containsPattern(String.format("simple name of %s does not start with '%s' in %s", + .containsPattern(String.format("Class <%s> does not have simple name starting with '%s' in %s", quote(WrongNamedClass.class.getName()), quote(prefix), locationPattern(WrongNamedClass.class))) @@ -269,7 +269,7 @@ public void haveSimpleNameNotStartingWith(ArchRule rule, String prefix) { assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should have simple name not starting with '%s'", prefix)) - .containsPattern(String.format("simple name of %s starts with '%s' in %s", + .containsPattern(String.format("Class <%s> has simple name starting with '%s' in %s", quote(WrongNamedClass.class.getName()), quote(prefix), locationPattern(WrongNamedClass.class))) @@ -294,7 +294,7 @@ public void haveSimpleNameContaining(ArchRule rule, String infix) { assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should have simple name containing '%s'", infix)) - .containsPattern(String.format("simple name of %s does not contain '%s' in %s", + .containsPattern(String.format("Class <%s> does not have simple name containing '%s' in %s", quote(WrongNamedClass.class.getName()), quote(infix), locationPattern(WrongNamedClass.class))) @@ -319,7 +319,7 @@ public void haveSimpleNameNotContaining(ArchRule rule, String infix) { assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should have simple name not containing '%s'", infix)) - .containsPattern(String.format("simple name of %s contains '%s' in %s", + .containsPattern(String.format("Class <%s> has simple name containing '%s' in %s", quote(WrongNamedClass.class.getName()), quote(infix), locationPattern(WrongNamedClass.class))) @@ -344,7 +344,7 @@ public void haveSimpleNameEndingWith(ArchRule rule, String suffix) { assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should have simple name ending with '%s'", suffix)) - .containsPattern(String.format("simple name of %s does not end with '%s' in %s", + .containsPattern(String.format("Class <%s> does not have simple name ending with '%s' in %s", quote(WrongNamedClass.class.getName()), quote(suffix), locationPattern(WrongNamedClass.class))) @@ -369,7 +369,7 @@ public void haveSimpleNameNotEndingWith(ArchRule rule, String suffix) { assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should have simple name not ending with '%s'", suffix)) - .containsPattern(String.format("simple name of %s ends with '%s' in %s", + .containsPattern(String.format("Class <%s> has simple name ending with '%s' in %s", quote(WrongNamedClass.class.getName()), quote(suffix), locationPattern(WrongNamedClass.class))) @@ -835,7 +835,7 @@ public void notImplement(ArchRule rule, Class satisfied, Class violated) { assertThat(singleLineFailureReportOf(result)) .contains(String.format("classes should not implement %s", Collection.class.getName())) - .containsPattern(String.format("Class <%s> implements %s in %s", + .containsPattern(String.format("Class <%s> does implement %s in %s", quote(violated.getName()), quote(Collection.class.getName()), locationPattern(violated))) @@ -1359,7 +1359,7 @@ public void beInterfaces(ArchRule rule, Class satisfied, Class violated) { assertThat(singleLineFailureReportOf(result)) .contains("classes should be interfaces") - .containsPattern(String.format("Class <%s> is not an interface in %s", + .containsPattern(String.format("Class <%s> is no interface in %s", quote(violated.getName()), locationPattern(violated))) .doesNotMatch(String.format(".*%s.* interface.*", quote(satisfied.getName()))); @@ -1399,7 +1399,7 @@ public void beEnums(ArchRule rule, Class satisfied, Class violated) { assertThat(singleLineFailureReportOf(result)) .contains("classes should be enums") - .containsPattern(String.format("Class <%s> is not an enum in %s", + .containsPattern(String.format("Class <%s> is no enum in %s", quote(violated.getName()), locationPattern(violated))) .doesNotMatch(String.format(".*%s.* enum.*", quote(satisfied.getName()))); @@ -1443,7 +1443,7 @@ public void beTopLevelClasses(ArchRule rule, Class satisfied, Class violat assertThat(singleLineFailureReportOf(result)) .contains("classes should be top level classes") - .containsPattern(String.format("Class <%s> is not a top level class", quote(violated.getName()))) + .containsPattern(String.format("Class <%s> is no top level class", quote(violated.getName()))) .doesNotMatch(String.format(".*%s.* top level class.*", quote(satisfied.getName()))); } @@ -1487,7 +1487,7 @@ public void beNestedClasses(ArchRule rule, Class satisfied, Class violated assertThat(singleLineFailureReportOf(result)) .contains("classes should be nested classes") - .containsPattern(String.format("Class <%s> is not a nested class", quote(violated.getName()))) + .containsPattern(String.format("Class <%s> is no nested class", quote(violated.getName()))) .doesNotMatch(String.format(".*%s.* nested class.*", quote(satisfied.getName()))); } @@ -1531,7 +1531,7 @@ public void beMemberClasses(ArchRule rule, Class satisfied, Class violated assertThat(singleLineFailureReportOf(result)) .contains("classes should be member classes") - .containsPattern(String.format("Class <%s> is not a member class", quote(violated.getName()))) + .containsPattern(String.format("Class <%s> is no member class", quote(violated.getName()))) .doesNotMatch(String.format(".*%s.* member class.*", quote(satisfied.getName()))); } @@ -1575,7 +1575,7 @@ public void beInnerClasses(ArchRule rule, Class satisfied, Class violated) assertThat(singleLineFailureReportOf(result)) .contains("classes should be inner classes") - .containsPattern(String.format("Class <%s> is not an inner class", quote(violated.getName()))) + .containsPattern(String.format("Class <%s> is no inner class", quote(violated.getName()))) .doesNotMatch(String.format(".*%s.* inner class.*", quote(satisfied.getName()))); } @@ -1619,7 +1619,7 @@ public void beAnonymousClasses(ArchRule rule, Class satisfied, Class viola assertThat(singleLineFailureReportOf(result)) .contains("classes should be anonymous classes") - .containsPattern(String.format("Class <%s> is not an anonymous class", quote(violated.getName()))) + .containsPattern(String.format("Class <%s> is no anonymous class", quote(violated.getName()))) .doesNotMatch(String.format(".*%s.* anonymous class.*", quote(satisfied.getName()))); } @@ -1663,7 +1663,7 @@ public void beLocalClasses(ArchRule rule, Class satisfied, Class violated) assertThat(singleLineFailureReportOf(result)) .contains("classes should be local classes") - .containsPattern(String.format("Class <%s> is not a local class", quote(violated.getName()))) + .containsPattern(String.format("Class <%s> is no local class", quote(violated.getName()))) .doesNotMatch(String.format(".*%s.* local class.*", quote(satisfied.getName()))); } diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenClassShouldTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenClassShouldTest.java index 91fc2308c6..dd5b5f5f1e 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenClassShouldTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenClassShouldTest.java @@ -190,7 +190,7 @@ public void theClass_should_haveNameMatching(ArchRule satisfiedRule, ArchRule un SomeClass.class.getName(), regex) .haveFailingRuleText("the class %s should have name not matching '%s'", SomeClass.class.getName(), regex) - .containFailureDetail(String.format("Class <%s> matches '%s' in %s", + .containFailureDetail(String.format("Class <%s> has name matching '%s' in %s", quote(SomeClass.class.getName()), quote(regex), locationPattern(SomeClass.class))) @@ -222,7 +222,7 @@ public void noClass_should_haveNameMatching(ArchRule satisfiedRule, ArchRule uns SomeClass.class.getName(), regex) .haveFailingRuleText("no class %s should have name matching '%s'", SomeClass.class.getName(), regex) - .containFailureDetail(String.format("Class <%s> matches '%s' in %s", + .containFailureDetail(String.format("Class <%s> has name matching '%s' in %s", quote(SomeClass.class.getName()), quote(regex), locationPattern(SomeClass.class))) @@ -255,7 +255,7 @@ public void theClass_should_haveSimpleNameStartingWith(ArchRule satisfiedRule, A SomeClass.class.getName(), prefix) .haveFailingRuleText("the class %s should have simple name not starting with '%s'", SomeClass.class.getName(), prefix) - .containFailureDetail(String.format("simple name of %s starts with '%s' in %s", + .containFailureDetail(String.format("Class <%s> has simple name starting with '%s' in %s", quote(SomeClass.class.getName()), quote(prefix), locationPattern(SomeClass.class))) @@ -289,7 +289,7 @@ public void noClass_should_haveSimpleNameStartingWith(ArchRule satisfiedRule, Ar SomeClass.class.getName(), prefix) .haveFailingRuleText("no class %s should have simple name starting with '%s'", SomeClass.class.getName(), prefix) - .containFailureDetail(String.format("simple name of %s starts with '%s' in %s", + .containFailureDetail(String.format("Class <%s> has simple name starting with '%s' in %s", quote(SomeClass.class.getName()), quote(prefix), locationPattern(SomeClass.class))) @@ -323,7 +323,7 @@ public void theClass_should_haveSimpleNameContaining(ArchRule satisfiedRule, Arc SomeClass.class.getName(), infix) .haveFailingRuleText("the class %s should have simple name not containing '%s'", SomeClass.class.getName(), infix) - .containFailureDetail(String.format("simple name of %s contains '%s' in %s", + .containFailureDetail(String.format("Class <%s> has simple name containing '%s' in %s", quote(SomeClass.class.getName()), quote(infix), locationPattern(SomeClass.class))) @@ -357,7 +357,7 @@ public void noClass_should_haveSimpleNameContaining(ArchRule satisfiedRule, Arch SomeClass.class.getName(), infix) .haveFailingRuleText("no class %s should have simple name containing '%s'", SomeClass.class.getName(), infix) - .containFailureDetail(String.format("simple name of %s contains '%s' in %s", + .containFailureDetail(String.format("Class <%s> has simple name containing '%s' in %s", quote(SomeClass.class.getName()), quote(infix), locationPattern(SomeClass.class))) @@ -391,7 +391,7 @@ public void theClass_should_haveSimpleNameEndingWith(ArchRule satisfiedRule, Arc SomeClass.class.getName(), suffix) .haveFailingRuleText("the class %s should have simple name not ending with '%s'", SomeClass.class.getName(), suffix) - .containFailureDetail(String.format("simple name of %s ends with '%s' in %s", + .containFailureDetail(String.format("Class <%s> has simple name ending with '%s' in %s", quote(SomeClass.class.getName()), quote(suffix), locationPattern(SomeClass.class))) @@ -425,7 +425,7 @@ public void noClass_should_haveSimpleNameEndingWith(ArchRule satisfiedRule, Arch SomeClass.class.getName(), suffix) .haveFailingRuleText("no class %s should have simple name ending with '%s'", SomeClass.class.getName(), suffix) - .containFailureDetail(String.format("simple name of %s ends with '%s' in %s", + .containFailureDetail(String.format("Class <%s> has simple name ending with '%s' in %s", quote(SomeClass.class.getName()), quote(suffix), locationPattern(SomeClass.class))) @@ -1035,7 +1035,7 @@ public void theClass_should_implement(ArchRule satisfiedRule, ArchRule unsatisfi ArrayList.class.getName(), Collection.class.getName()) .haveFailingRuleText("the class %s should not implement %s", ArrayList.class.getName(), Collection.class.getName()) - .containFailureDetail(String.format("Class <%s> implements %s in %s", + .containFailureDetail(String.format("Class <%s> does implement %s in %s", quote(ArrayList.class.getName()), quote(Collection.class.getName()), locationPattern(ArrayList.class))) @@ -1064,7 +1064,7 @@ public void noClass_should_implement(ArchRule satisfiedRule, ArchRule unsatisfie ArrayList.class.getName(), Collection.class.getName()) .haveFailingRuleText("no class %s should implement %s", ArrayList.class.getName(), Collection.class.getName()) - .containFailureDetail(String.format("Class <%s> implements %s in %s", + .containFailureDetail(String.format("Class <%s> does implement %s in %s", quote(ArrayList.class.getName()), quote(Collection.class.getName()), locationPattern(ArrayList.class))) diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MembersShouldTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MembersShouldTest.java index e676465b2c..4f1103a4a3 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MembersShouldTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MembersShouldTest.java @@ -342,7 +342,7 @@ public void property_predicates(MembersShouldConjunction conjunction, Set actualMembers = parseMembers(result.getFailureReport().getDetails()); - assertThat(actualMembers).containsOnlyElementsOf(expectedMembers); + assertThat(actualMembers).hasSameElementsAs(expectedMembers); } @DataProvider @@ -394,7 +394,7 @@ public void declaration_predicates(MembersShouldConjunction conjunction, Set< .evaluate(importClasses(ClassWithVariousMembers.class, OtherClassWithMembers.class)); Set actualMembers = parseMembers(result.getFailureReport().getDetails()); - assertThat(actualMembers).containsOnlyElementsOf(expectedMessages); + assertThat(actualMembers).hasSameElementsAs(expectedMessages); } @DataProvider @@ -419,7 +419,7 @@ public void haveNameStartingWith(ArchRule rule, String prefix, String violatingM EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); assertThat(singleLineFailureReportOf(result)) - .containsPattern(String.format(".*%s.* name does not start with '%s' in %s", + .containsPattern(String.format(".*%s.* does not have name starting with '%s' in %s", quote(violatingMember), quote(prefix), locationPattern(SimpleFieldAndMethod.class))); @@ -447,7 +447,7 @@ public void haveNameNotStartingWith(ArchRule rule, String prefix) { EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); assertThat(singleLineFailureReportOf(result)) - .containsPattern(String.format(".*%s.* name starts with '%s' in %s", + .containsPattern(String.format(".*%s.* has name starting with '%s' in %s", quote(prefix), quote(prefix), locationPattern(SimpleFieldAndMethod.class))); @@ -475,7 +475,7 @@ public void haveNameContaining(ArchRule rule, String infix, String violatingMemb EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); assertThat(singleLineFailureReportOf(result)) - .containsPattern(String.format(".*%s.* name does not contain '%s' in %s", + .containsPattern(String.format(".*%s.* does not have name containing '%s' in %s", quote(violatingMember), quote(infix), locationPattern(SimpleFieldAndMethod.class))); @@ -503,7 +503,7 @@ public void haveNameNotContaining(ArchRule rule, String infix) { EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); assertThat(singleLineFailureReportOf(result)) - .containsPattern(String.format(".*%s.* name contains '%s' in %s", + .containsPattern(String.format(".*%s.* has name containing '%s' in %s", quote(infix), quote(infix), locationPattern(SimpleFieldAndMethod.class))); @@ -531,7 +531,7 @@ public void haveNameEndingWith(ArchRule rule, String suffix, String violatingMem EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); assertThat(singleLineFailureReportOf(result)) - .containsPattern(String.format(".*%s.* name does not end with '%s' in %s", + .containsPattern(String.format(".*%s.* does not have name ending with '%s' in %s", quote(violatingMember), quote(suffix), locationPattern(SimpleFieldAndMethod.class))); @@ -559,7 +559,7 @@ public void haveNameNotEndingWith(ArchRule rule, String suffix) { EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); assertThat(singleLineFailureReportOf(result)) - .containsPattern(String.format(".*%s.* name ends with '%s' in %s", + .containsPattern(String.format(".*%s.* has name ending with '%s' in %s", quote(suffix), quote(suffix), locationPattern(SimpleFieldAndMethod.class)));