From 68b12b086cdca6af418572d73789263d2bc1c7c2 Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Sun, 24 May 2020 01:45:23 +0200 Subject: [PATCH 001/115] set next SNAPSHOT version Signed-off-by: Peter Gafert --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cdcda0e8c9..416a2a2da0 100644 --- a/build.gradle +++ b/build.gradle @@ -123,7 +123,7 @@ ext { allprojects { group = 'com.tngtech.archunit' - version = '0.14.1' + version = '0.15.0-SNAPSHOT' repositories { mavenCentral() From eacee80749f97efb0663cac0eca5a4cdd13deaa5 Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Sun, 24 May 2020 02:29:05 +0200 Subject: [PATCH 002/115] execute GitHub build action for push on release branches Signed-off-by: Peter Gafert --- .github/workflows/build.yml | 1 + .github/workflows/gradle-wrapper-validation.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6931bc4f07..95b4f6453c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - release-* pull_request: jobs: diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index ce08c174ec..7151048d9e 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - release-* pull_request: jobs: From b49351837d824f87e8f66e9db0e0a976329e0501 Mon Sep 17 00:00:00 2001 From: Kamer Elciyar Date: Tue, 11 Feb 2020 20:16:43 +0300 Subject: [PATCH 003/115] Extend MembersThat and MembersShould to have starting, containing and ending functionality. (#239) Signed-off-by: Kamer Elciyar --- .../core/domain/properties/HasName.java | 58 +++++++++++++ .../lang/conditions/ArchConditions.java | 81 +++++++++++++++++++ .../syntax/AbstractMembersShouldInternal.java | 16 ++++ .../lang/syntax/MembersThatInternal.java | 18 +++++ .../lang/syntax/elements/MembersShould.java | 27 +++++++ .../lang/syntax/elements/MembersThat.java | 27 +++++++ .../syntax/elements/GivenMembersTest.java | 16 ++++ .../syntax/elements/MembersShouldTest.java | 20 +++++ 8 files changed, 263 insertions(+) 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 8c0ba9888e..1543cbeaaf 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 @@ -111,6 +111,21 @@ public static DescribedPredicate nameMatching(final String regex) { return new NameMatchingPredicate(regex); } + @PublicAPI(usage = ACCESS) + public static DescribedPredicate nameStartingWith(final String prefix) { + return new NameStartingWithPredicate(prefix); + } + + @PublicAPI(usage = ACCESS) + public static DescribedPredicate nameContaining(final String infix) { + return new NameContainingPredicate(infix); + } + + @PublicAPI(usage = ACCESS) + public static DescribedPredicate nameEndingWith(final String postfix) { + return new NameEndingWithPredicate(postfix); + } + private static class NameEqualsPredicate extends DescribedPredicate { private final String name; @@ -138,6 +153,49 @@ public boolean apply(HasName input) { return pattern.matcher(input.getName()).matches(); } } + + private static class NameStartingWithPredicate extends DescribedPredicate { + private final String prefix; + + NameStartingWithPredicate(String prefix) { + super(String.format("name starting with '%s'", prefix)); + this.prefix = prefix; + } + + @Override + public boolean apply(HasName input) { + return input.getName().startsWith(prefix); + } + + } + + private static class NameContainingPredicate extends DescribedPredicate { + private final String infix; + + NameContainingPredicate(String infix) { + super(String.format("name containing '%s'", infix)); + this.infix = infix; + } + + @Override + public boolean apply(HasName input) { + return input.getName().contains(infix); + } + } + + private static class NameEndingWithPredicate extends DescribedPredicate { + private final String suffix; + + NameEndingWithPredicate(String suffix) { + super(String.format("name ending with '%s'", suffix)); + this.suffix = suffix; + } + + @Override + public boolean apply(HasName input) { + return input.getName().endsWith(suffix); + } + } } final class Functions { 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 1ca9cdd525..a759105743 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 @@ -102,7 +102,10 @@ 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.nameContaining; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameEndingWith; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameStartingWith; import static com.tngtech.archunit.core.domain.properties.HasOwner.Predicates.With.owner; import static com.tngtech.archunit.core.domain.properties.HasParameterTypes.Predicates.rawParameterTypes; import static com.tngtech.archunit.core.domain.properties.HasReturnType.Predicates.rawReturnType; @@ -553,6 +556,27 @@ ArchCondition haveFullNameMatching(String regex) { return not(ArchConditions.haveFullNameMatching(regex)).as("have full name not matching '%s'", regex); } + @PublicAPI(usage = ACCESS) + public static ArchCondition + haveNameStartingWith(String prefix) { + final DescribedPredicate haveNameStartingWith = have(nameStartingWith(prefix)).forSubType(); + return new StartingCondition<>(haveNameStartingWith, prefix); + } + + @PublicAPI(usage = ACCESS) + public static ArchCondition + haveNameContaining(String infix) { + final DescribedPredicate haveNameContaining = have(nameContaining(infix)).forSubType(); + return new ContainingCondition<>(haveNameContaining, infix); + } + + @PublicAPI(usage = ACCESS) + public static ArchCondition + haveNameEndingWith(String suffix) { + final DescribedPredicate haveNameEndingWith = have(nameEndingWith(suffix)).forSubType(); + return new EndingCondition<>(haveNameEndingWith, suffix); + } + @PublicAPI(usage = ACCESS) public static ArchCondition resideInAPackage(final String packageIdentifier) { return new DoesConditionByPredicate<>(JavaClass.Predicates.resideInAPackage(packageIdentifier)); @@ -1243,6 +1267,63 @@ public void check(T item, ConditionEvents events) { } } + 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.apply(item); + String message = createMessage(item, + String.format("%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.apply(item); + String message = createMessage(item, + String.format("%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.apply(item); + String message = createMessage(item, + String.format("%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; diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/AbstractMembersShouldInternal.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/AbstractMembersShouldInternal.java index a6d385be53..0927e67fb1 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/AbstractMembersShouldInternal.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/AbstractMembersShouldInternal.java @@ -98,6 +98,22 @@ public SELF haveFullNameMatching(String regex) { public SELF haveFullNameNotMatching(String regex) { return addCondition(ArchConditions.haveFullNameNotMatching(regex)); } + + @Override + public SELF haveNameStartingWith(String prefix) { + return addCondition(ArchConditions.haveNameStartingWith(prefix)); + } + + @Override + public SELF haveNameContaining(String infix) { + return addCondition(ArchConditions.haveNameContaining(infix)); + } + + @Override + public SELF haveNameEndingWith(String suffix) { + return addCondition(ArchConditions.haveNameEndingWith(suffix)); + } + @Override public SELF bePublic() { return addCondition(ArchConditions.bePublic()); 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 e46fcb9a85..4b9e75a61e 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 @@ -34,7 +34,10 @@ 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.nameContaining; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameEndingWith; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameStartingWith; import static com.tngtech.archunit.lang.conditions.ArchPredicates.are; import static com.tngtech.archunit.lang.conditions.ArchPredicates.have; @@ -93,6 +96,21 @@ public CONJUNCTION haveFullNameNotMatching(String regex) { return givenWith(have(not(fullNameMatching(regex)).as("full name not matching '%s'", regex))); } + @Override + public CONJUNCTION haveNameStartingWith(String prefix) { + return givenWith(have(nameStartingWith(prefix))); + } + + @Override + public CONJUNCTION haveNameContaining(String infix) { + return givenWith(have(nameContaining(infix))); + } + + @Override + public CONJUNCTION haveNameEndingWith(String suffix) { + return givenWith(have(nameEndingWith(suffix))); + } + @Override public CONJUNCTION arePublic() { return givenWith(SyntaxPredicates.arePublic()); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersShould.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersShould.java index eaeb3542b1..4890632fa4 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersShould.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersShould.java @@ -107,6 +107,33 @@ public interface MembersShould> @PublicAPI(usage = ACCESS) CONJUNCTION haveFullNameNotMatching(String regex); + /** + * Asserts that members have a name starting with the specified prefix. + * + * @param prefix A prefix the member name should start with + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameStartingWith(String prefix); + + /** + * Asserts that members have a name containing the specified infix. + * + * @param infix An infix the member name should contain + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameContaining(String infix); + + /** + * Asserts that members have a name ending with the specified suffix. + * + * @param suffix A suffix the member name should end with + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameEndingWith(String suffix); + /** * Asserts that members are public. * 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 8f7b45c243..0c68542ee7 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 @@ -103,6 +103,33 @@ public interface MembersThat> { @PublicAPI(usage = ACCESS) CONJUNCTION haveFullNameNotMatching(String regex); + /** + * Matches members with a name starting with the specified prefix. + * + * @param prefix A prefix the member name should start with + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameStartingWith(String prefix); + + /** + * Matches members with a name containing the specified infix. + * + * @param infix An infix the member name should contain + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameContaining(String infix); + + /** + * Matches members with a name ending with the specified suffix. + * + * @param suffix A suffix the member name should end with + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameEndingWith(String suffix); + /** * Matches public members. * 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 e83e127d72..e46f9ad4b4 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 @@ -214,6 +214,22 @@ public static Object[][] restricted_property_rule_starts() { allConstructorsExcept(CONSTRUCTOR_ONE_ARG)), $(described(fields().that().haveFullNameNotMatching(quote(classNameDot) + ".*A.*")), allFieldsExcept(FIELD_A)), + $(described(members().that().haveNameStartingWith("fie")), ALL_FIELD_DESCRIPTIONS), + $(described(codeUnits().that().haveNameStartingWith("me")), ALL_METHOD_DESCRIPTIONS), + $(described(methods().that().haveNameStartingWith("m")), ALL_METHOD_DESCRIPTIONS), + $(described(constructors().that().haveNameStartingWith("<")), ALL_CONSTRUCTOR_DESCRIPTIONS), + $(described(fields().that().haveNameStartingWith("f")), ALL_FIELD_DESCRIPTIONS), + $(described(members().that().haveNameContaining("et")), ALL_METHOD_DESCRIPTIONS), + $(described(codeUnits().that().haveNameContaining("et")), ALL_METHOD_DESCRIPTIONS), + $(described(methods().that().haveNameContaining("dA")), ImmutableSet.of(METHOD_A)), + $(described(constructors().that().haveNameContaining("init")), ALL_CONSTRUCTOR_DESCRIPTIONS), + $(described(fields().that().haveNameContaining("dA")), ImmutableSet.of(FIELD_A)), + $(described(members().that().haveNameEndingWith("D")), ImmutableSet.of(FIELD_D, METHOD_D)), + $(described(codeUnits().that().haveNameEndingWith("A")), ImmutableSet.of(METHOD_A)), + $(described(methods().that().haveNameEndingWith("C")), ImmutableSet.of(METHOD_C)), + $(described(constructors().that().haveNameEndingWith("it>")), ALL_CONSTRUCTOR_DESCRIPTIONS), + $(described(fields().that().haveNameEndingWith("B")), ImmutableSet.of(FIELD_B)), + $(described(members().that().arePublic()), ImmutableSet.of( FIELD_PUBLIC, METHOD_PUBLIC, CONSTRUCTOR_PUBLIC)), $(described(fields().that().arePublic()), ImmutableSet.of(FIELD_C)), 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 3725b15664..32ac61d537 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 @@ -53,6 +53,7 @@ import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.CONSTRUCTOR_PUBLIC; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_A; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_ANNOTATED_WITH_A; +import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_B; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_C; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_D; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_PACKAGE_PRIVATE; @@ -61,6 +62,9 @@ import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_PUBLIC; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_A; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_ANNOTATED_WITH_A; +import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_B; +import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_C; +import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_D; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_PACKAGE_PRIVATE; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_PRIVATE; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_PROTECTED; @@ -160,6 +164,22 @@ public static Object[][] restricted_property_rule_ends() { $(codeUnits().should().haveFullNameNotMatching(quote(classNameDot) + ".*init.*"), ALL_CONSTRUCTOR_DESCRIPTIONS), $(constructors().should().haveFullNameNotMatching(quote(classNameDot) + ".*init.*String\\)"), ImmutableSet.of(CONSTRUCTOR_ONE_ARG)), + $(members().should().haveNameStartingWith("fi"), ALL_CODE_UNIT_DESCRIPTIONS), + $(fields().should().haveNameStartingWith("m"), ALL_FIELD_DESCRIPTIONS), + $(codeUnits().should().haveNameStartingWith(""), ALL_METHOD_DESCRIPTIONS), + $(methods().should().haveNameEndingWith("dC"), allMethodsExcept(METHOD_C)), + $(constructors().should().haveNameEndingWith(" Date: Sat, 11 Apr 2020 17:17:58 +0200 Subject: [PATCH 004/115] add negated version of `name{startingWith/containing/endingWith}` I have also added some tests for the failure details, since broken messages there would be undetected at the moment. The reason that some of these tests for members are missing for other methods is simply that we reuse those for classes, so they were not added in the past since those tests would have already caught it. If we have syntax methods explicitly for members (that are not also used for classes), then we should add a detailed test checking that the failure details indeed match. when I wrote those tests I also noticed that messages like `Constructor()> does not start with 'foo'` does also read slightly weird (because there is no focus on the "name" the "starts with" refers to) so I added "name" to the failure details, i.e. `Constructor<..> name does not start with...`. Signed-off-by: Peter Gafert --- .../lang/conditions/ArchConditions.java | 27 ++- .../syntax/AbstractMembersShouldInternal.java | 15 ++ .../lang/syntax/MembersThatInternal.java | 15 ++ .../lang/syntax/elements/MembersShould.java | 27 +++ .../lang/syntax/elements/MembersThat.java | 27 +++ .../syntax/elements/ClassesShouldTest.java | 52 ++--- .../syntax/elements/GivenMembersTest.java | 17 ++ .../syntax/elements/MembersShouldTest.java | 190 ++++++++++++++++++ .../testclasses/SimpleFieldAndMethod.java | 8 + 9 files changed, 349 insertions(+), 29 deletions(-) create mode 100644 archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/testclasses/SimpleFieldAndMethod.java 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 a759105743..44057d2654 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 @@ -563,6 +563,13 @@ ArchCondition haveFullNameMatching(String regex) { return new StartingCondition<>(haveNameStartingWith, 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); + } + @PublicAPI(usage = ACCESS) public static ArchCondition haveNameContaining(String infix) { @@ -570,6 +577,13 @@ ArchCondition haveFullNameMatching(String regex) { return new ContainingCondition<>(haveNameContaining, 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); + } + @PublicAPI(usage = ACCESS) public static ArchCondition haveNameEndingWith(String suffix) { @@ -577,6 +591,13 @@ ArchCondition haveFullNameMatching(String regex) { return new EndingCondition<>(haveNameEndingWith, 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); + } + @PublicAPI(usage = ACCESS) public static ArchCondition resideInAPackage(final String packageIdentifier) { return new DoesConditionByPredicate<>(JavaClass.Predicates.resideInAPackage(packageIdentifier)); @@ -1281,7 +1302,7 @@ private static class StartingCondition> @PublicAPI(usage = ACCESS) CONJUNCTION haveNameStartingWith(String prefix); + /** + * Asserts that members have a name not starting with the specified prefix. + * + * @param prefix A prefix the member name should not start with + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameNotStartingWith(String prefix); + /** * Asserts that members have a name containing the specified infix. * @@ -125,6 +134,15 @@ public interface MembersShould> @PublicAPI(usage = ACCESS) CONJUNCTION haveNameContaining(String infix); + /** + * Asserts that members have a name not containing the specified infix. + * + * @param infix An infix the member name should not contain + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameNotContaining(String infix); + /** * Asserts that members have a name ending with the specified suffix. * @@ -134,6 +152,15 @@ public interface MembersShould> @PublicAPI(usage = ACCESS) CONJUNCTION haveNameEndingWith(String suffix); + /** + * Asserts that members have a name not ending with the specified suffix. + * + * @param suffix A suffix the member name should not end with + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameNotEndingWith(String suffix); + /** * Asserts that members are public. * 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 0c68542ee7..56f99ac7eb 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 @@ -112,6 +112,15 @@ public interface MembersThat> { @PublicAPI(usage = ACCESS) CONJUNCTION haveNameStartingWith(String prefix); + /** + * Matches members with a name not starting with the specified prefix. + * + * @param prefix A prefix the member name should not start with + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameNotStartingWith(String prefix); + /** * Matches members with a name containing the specified infix. * @@ -121,6 +130,15 @@ public interface MembersThat> { @PublicAPI(usage = ACCESS) CONJUNCTION haveNameContaining(String infix); + /** + * Matches members with a name not containing the specified infix. + * + * @param infix An infix the member name should not contain + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameNotContaining(String infix); + /** * Matches members with a name ending with the specified suffix. * @@ -130,6 +148,15 @@ public interface MembersThat> { @PublicAPI(usage = ACCESS) CONJUNCTION haveNameEndingWith(String suffix); + /** + * Matches members with a name not ending with the specified suffix. + * + * @param suffix A suffix the member name should not end with + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameNotEndingWith(String suffix); + /** * Matches public members. * 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 c2f79c600d..ed45347374 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 @@ -248,6 +248,31 @@ public void haveSimpleNameStartingWith(ArchRule rule, String prefix) { .doesNotContain(SomeClass.class.getName()); } + @DataProvider + public static Object[][] haveSimpleNameNotStartingWith_rules() { + String simpleName = WrongNamedClass.class.getSimpleName(); + String prefix = simpleName.substring(0, simpleName.length() - 1); + return $$( + $(classes().should().haveSimpleNameNotStartingWith(prefix), prefix), + $(classes().should(ArchConditions.haveSimpleNameNotStartingWith(prefix)), prefix) + ); + } + + @Test + @UseDataProvider("haveSimpleNameNotStartingWith_rules") + public void haveSimpleNameNotStartingWith(ArchRule rule, String prefix) { + EvaluationResult result = rule.evaluate(importClasses( + SomeClass.class, WrongNamedClass.class)); + + 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", + quote(WrongNamedClass.class.getName()), + quote(prefix), + locationPattern(WrongNamedClass.class))) + .doesNotContain(SomeClass.class.getName()); + } + @DataProvider public static Object[][] haveSimpleNameContaining_rules() { String simpleName = SomeClass.class.getSimpleName(); @@ -298,31 +323,6 @@ public void haveSimpleNameNotContaining(ArchRule rule, String infix) { .doesNotContain(SomeClass.class.getName()); } - @DataProvider - public static Object[][] haveSimpleNameNotStartingWith_rules() { - String simpleName = WrongNamedClass.class.getSimpleName(); - String prefix = simpleName.substring(0, simpleName.length() - 1); - return $$( - $(classes().should().haveSimpleNameNotStartingWith(prefix), prefix), - $(classes().should(ArchConditions.haveSimpleNameNotStartingWith(prefix)), prefix) - ); - } - - @Test - @UseDataProvider("haveSimpleNameNotStartingWith_rules") - public void haveSimpleNameNotStartingWith(ArchRule rule, String prefix) { - EvaluationResult result = rule.evaluate(importClasses( - SomeClass.class, WrongNamedClass.class)); - - 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", - quote(WrongNamedClass.class.getName()), - quote(prefix), - locationPattern(WrongNamedClass.class))) - .doesNotContain(SomeClass.class.getName()); - } - @DataProvider public static Object[][] haveSimpleNameEndingWith_rules() { String simpleName = SomeClass.class.getSimpleName(); @@ -1722,7 +1722,7 @@ public void onlyCall_should_report_success_if_targets_are_non_resolvable(ArchRul } static String locationPattern(Class clazz) { - return String.format("\\(%s.java:0\\)", quote(clazz.getSimpleName())); + return String.format("\\(%s.java:\\d+\\)", quote(clazz.getSimpleName())); } static String singleLineFailureReportOf(EvaluationResult result) { 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 e46f9ad4b4..d7408a9b81 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 @@ -219,16 +219,33 @@ public static Object[][] restricted_property_rule_starts() { $(described(methods().that().haveNameStartingWith("m")), ALL_METHOD_DESCRIPTIONS), $(described(constructors().that().haveNameStartingWith("<")), ALL_CONSTRUCTOR_DESCRIPTIONS), $(described(fields().that().haveNameStartingWith("f")), ALL_FIELD_DESCRIPTIONS), + $(described(members().that().haveNameNotStartingWith("fie")), ALL_CODE_UNIT_DESCRIPTIONS), + $(described(codeUnits().that().haveNameNotStartingWith("me")), ALL_CONSTRUCTOR_DESCRIPTIONS), + $(described(methods().that().haveNameNotStartingWith("m")), emptySet()), + $(described(constructors().that().haveNameNotStartingWith("<")), emptySet()), + $(described(fields().that().haveNameNotStartingWith("f")), emptySet()), + $(described(members().that().haveNameContaining("et")), ALL_METHOD_DESCRIPTIONS), $(described(codeUnits().that().haveNameContaining("et")), ALL_METHOD_DESCRIPTIONS), $(described(methods().that().haveNameContaining("dA")), ImmutableSet.of(METHOD_A)), $(described(constructors().that().haveNameContaining("init")), ALL_CONSTRUCTOR_DESCRIPTIONS), $(described(fields().that().haveNameContaining("dA")), ImmutableSet.of(FIELD_A)), + $(described(members().that().haveNameNotContaining("et")), union(ALL_FIELD_DESCRIPTIONS, ALL_CONSTRUCTOR_DESCRIPTIONS)), + $(described(codeUnits().that().haveNameNotContaining("et")), ALL_CONSTRUCTOR_DESCRIPTIONS), + $(described(methods().that().haveNameNotContaining("dA")), allMethodsExcept(METHOD_A)), + $(described(constructors().that().haveNameNotContaining("init")), emptySet()), + $(described(fields().that().haveNameNotContaining("dA")), allFieldsExcept(FIELD_A)), + $(described(members().that().haveNameEndingWith("D")), ImmutableSet.of(FIELD_D, METHOD_D)), $(described(codeUnits().that().haveNameEndingWith("A")), ImmutableSet.of(METHOD_A)), $(described(methods().that().haveNameEndingWith("C")), ImmutableSet.of(METHOD_C)), $(described(constructors().that().haveNameEndingWith("it>")), ALL_CONSTRUCTOR_DESCRIPTIONS), $(described(fields().that().haveNameEndingWith("B")), ImmutableSet.of(FIELD_B)), + $(described(members().that().haveNameNotEndingWith("D")), allMembersExcept(FIELD_D, METHOD_D)), + $(described(codeUnits().that().haveNameNotEndingWith("A")), allCodeUnitsExcept(METHOD_A)), + $(described(methods().that().haveNameNotEndingWith("C")), allMethodsExcept(METHOD_C)), + $(described(constructors().that().haveNameNotEndingWith("it>")), emptySet()), + $(described(fields().that().haveNameNotEndingWith("B")), allFieldsExcept(FIELD_B)), $(described(members().that().arePublic()), ImmutableSet.of( FIELD_PUBLIC, METHOD_PUBLIC, CONSTRUCTOR_PUBLIC)), 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 32ac61d537..7683a63384 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 @@ -9,13 +9,16 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.base.Function; +import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.lang.EvaluationResult; +import com.tngtech.archunit.lang.conditions.ArchConditions; import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.A; import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.B; import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.C; import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.ClassWithVariousMembers; import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.MetaAnnotation; import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.OtherClassWithMembers; +import com.tngtech.archunit.lang.syntax.elements.testclasses.SimpleFieldAndMethod; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; @@ -35,6 +38,8 @@ import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.members; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods; +import static com.tngtech.archunit.lang.syntax.elements.ClassesShouldTest.locationPattern; +import static com.tngtech.archunit.lang.syntax.elements.ClassesShouldTest.singleLineFailureReportOf; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.ALL_CODE_UNIT_DESCRIPTIONS; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.ALL_CONSTRUCTOR_DESCRIPTIONS; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.ALL_FIELD_DESCRIPTIONS; @@ -169,16 +174,33 @@ public static Object[][] restricted_property_rule_ends() { $(codeUnits().should().haveNameStartingWith(""), ALL_METHOD_DESCRIPTIONS), $(methods().should().haveNameEndingWith("dC"), allMethodsExcept(METHOD_C)), $(constructors().should().haveNameEndingWith(""), ALL_CONSTRUCTOR_DESCRIPTIONS), + $(methods().should().haveNameNotEndingWith("dC"), ImmutableSet.of(METHOD_C)), + $(constructors().should().haveNameNotEndingWith(" conjunction, Set< assertThat(actualMembers).containsOnlyElementsOf(expectedMessages); } + @DataProvider + public static Object[][] haveNameStartingWith_rules() { + return $$( + $(members().should().haveNameStartingWith("field"), "field", "violated"), + $(members().should(ArchConditions.haveNameStartingWith("field")), "field", "violated"), + $(fields().should().haveNameStartingWith("field"), "field", "violated"), + $(fields().should(ArchConditions.haveNameStartingWith("field")), "field", "violated"), + $(codeUnits().should().haveNameStartingWith("method"), "method", "violated"), + $(codeUnits().should(ArchConditions.haveNameStartingWith("method")), "method", "violated"), + $(methods().should().haveNameStartingWith("method"), "method", "violated"), + $(methods().should(ArchConditions.haveNameStartingWith("method")), "method", "violated"), + $(constructors().should().haveNameStartingWith("constructor"), "constructor", ""), + $(constructors().should(ArchConditions.haveNameStartingWith("constructor")), "constructor", "") + ); + } + + @Test + @UseDataProvider("haveNameStartingWith_rules") + public void haveNameStartingWith(ArchRule rule, String prefix, String violatingMember) { + EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); + + assertThat(singleLineFailureReportOf(result)) + .containsPattern(String.format(".*%s.* name does not start with '%s' in %s", + quote(violatingMember), + quote(prefix), + locationPattern(SimpleFieldAndMethod.class))); + } + + @DataProvider + public static Object[][] haveNameNotStartingWith_rules() { + return $$( + $(members().should().haveNameNotStartingWith("violated"), "violated"), + $(members().should(ArchConditions.haveNameNotStartingWith("violated")), "violated"), + $(fields().should().haveNameNotStartingWith("violated"), "violated"), + $(fields().should(ArchConditions.haveNameNotStartingWith("violated")), "violated"), + $(codeUnits().should().haveNameNotStartingWith("violated"), "violated"), + $(codeUnits().should(ArchConditions.haveNameNotStartingWith("violated")), "violated"), + $(methods().should().haveNameNotStartingWith("violated"), "violated"), + $(methods().should(ArchConditions.haveNameNotStartingWith("violated")), "violated"), + $(constructors().should().haveNameNotStartingWith(""), ""), + $(constructors().should(ArchConditions.haveNameNotStartingWith("")), "") + ); + } + + @Test + @UseDataProvider("haveNameNotStartingWith_rules") + 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", + quote(prefix), + quote(prefix), + locationPattern(SimpleFieldAndMethod.class))); + } + + @DataProvider + public static Object[][] haveNameContaining_rules() { + return $$( + $(members().should().haveNameContaining("field"), "field", "violated"), + $(members().should(ArchConditions.haveNameContaining("field")), "field", "violated"), + $(fields().should().haveNameContaining("field"), "field", "violated"), + $(fields().should(ArchConditions.haveNameContaining("field")), "field", "violated"), + $(codeUnits().should().haveNameContaining("method"), "method", "violated"), + $(codeUnits().should(ArchConditions.haveNameContaining("method")), "method", "violated"), + $(methods().should().haveNameContaining("method"), "method", "violated"), + $(methods().should(ArchConditions.haveNameContaining("method")), "method", "violated"), + $(constructors().should().haveNameContaining("constructor"), "constructor", ""), + $(constructors().should(ArchConditions.haveNameContaining("constructor")), "constructor", "") + ); + } + + @Test + @UseDataProvider("haveNameContaining_rules") + public void haveNameContaining(ArchRule rule, String infix, String violatingMember) { + EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); + + assertThat(singleLineFailureReportOf(result)) + .containsPattern(String.format(".*%s.* name does not contain '%s' in %s", + quote(violatingMember), + quote(infix), + locationPattern(SimpleFieldAndMethod.class))); + } + + @DataProvider + public static Object[][] haveNameNotContaining_rules() { + return $$( + $(members().should().haveNameNotContaining("violated"), "violated"), + $(members().should(ArchConditions.haveNameNotContaining("violated")), "violated"), + $(fields().should().haveNameNotContaining("violated"), "violated"), + $(fields().should(ArchConditions.haveNameNotContaining("violated")), "violated"), + $(codeUnits().should().haveNameNotContaining("violated"), "violated"), + $(codeUnits().should(ArchConditions.haveNameNotContaining("violated")), "violated"), + $(methods().should().haveNameNotContaining("violated"), "violated"), + $(methods().should(ArchConditions.haveNameNotContaining("violated")), "violated"), + $(constructors().should().haveNameNotContaining(""), ""), + $(constructors().should(ArchConditions.haveNameNotContaining("")), "") + ); + } + + @Test + @UseDataProvider("haveNameNotContaining_rules") + 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", + quote(infix), + quote(infix), + locationPattern(SimpleFieldAndMethod.class))); + } + + @DataProvider + public static Object[][] haveNameEndingWith_rules() { + return $$( + $(members().should().haveNameEndingWith("field"), "field", "violated"), + $(members().should(ArchConditions.haveNameEndingWith("field")), "field", "violated"), + $(fields().should().haveNameEndingWith("field"), "field", "violated"), + $(fields().should(ArchConditions.haveNameEndingWith("field")), "field", "violated"), + $(codeUnits().should().haveNameEndingWith("method"), "method", "violated"), + $(codeUnits().should(ArchConditions.haveNameEndingWith("method")), "method", "violated"), + $(methods().should().haveNameEndingWith("method"), "method", "violated"), + $(methods().should(ArchConditions.haveNameEndingWith("method")), "method", "violated"), + $(constructors().should().haveNameEndingWith("constructor"), "constructor", ""), + $(constructors().should(ArchConditions.haveNameEndingWith("constructor")), "constructor", "") + ); + } + + @Test + @UseDataProvider("haveNameEndingWith_rules") + public void haveNameEndingWith(ArchRule rule, String suffix, String violatingMember) { + EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); + + assertThat(singleLineFailureReportOf(result)) + .containsPattern(String.format(".*%s.* name does not end with '%s' in %s", + quote(violatingMember), + quote(suffix), + locationPattern(SimpleFieldAndMethod.class))); + } + + @DataProvider + public static Object[][] haveNameNotEndingWith_rules() { + return $$( + $(members().should().haveNameNotEndingWith("violated"), "violated"), + $(members().should(ArchConditions.haveNameNotEndingWith("violated")), "violated"), + $(fields().should().haveNameNotEndingWith("violated"), "violated"), + $(fields().should(ArchConditions.haveNameNotEndingWith("violated")), "violated"), + $(codeUnits().should().haveNameNotEndingWith("violated"), "violated"), + $(codeUnits().should(ArchConditions.haveNameNotEndingWith("violated")), "violated"), + $(methods().should().haveNameNotEndingWith("violated"), "violated"), + $(methods().should(ArchConditions.haveNameNotEndingWith("violated")), "violated"), + $(constructors().should().haveNameNotEndingWith(""), ""), + $(constructors().should(ArchConditions.haveNameNotEndingWith("")), "") + ); + } + + @Test + @UseDataProvider("haveNameNotEndingWith_rules") + 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", + quote(suffix), + quote(suffix), + locationPattern(SimpleFieldAndMethod.class))); + } + private Set parseMembers(List details) { return parseMembers(ImmutableList.of(ClassWithVariousMembers.class, OtherClassWithMembers.class), details); } diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/testclasses/SimpleFieldAndMethod.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/testclasses/SimpleFieldAndMethod.java new file mode 100644 index 0000000000..a9f63dcc8c --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/testclasses/SimpleFieldAndMethod.java @@ -0,0 +1,8 @@ +package com.tngtech.archunit.lang.syntax.elements.testclasses; + +public class SimpleFieldAndMethod { + Object violated; + + void violated() { + } +} From 0d76ee22816f363858ddd214354d393fe5fe2604 Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Mon, 23 Mar 2020 22:18:45 +0100 Subject: [PATCH 005/115] make `Java{Class/Member}Assertion` only compare `@Retention(RUNTIME)` annotations In general with ArchUnit we will also import annotations of `@Retention(CLASS)`, but it never makes any sense to compare those to `Annotations` retrieved via Reflection, since those will always only be the ones with `@Retention(RUNTIME)`. Since this is a very generic assertion, it makes sense to make it robust against cases where a type is annotated with an `Annotation` with `@Retention(CLASS)`, i.e. we cannot ever check successfully for `@Retention(CLASS)`, so we might as well ignore them to make the assertion usable in those cases and still get some test coverage of the `@Retention(RUNTIME)` cases. Signed-off-by: Peter Gafert --- .../com/tngtech/archunit/testutil/Assertions.java | 3 ++- .../assertion/JavaAnnotationAssertion.java | 14 ++++++++++++-- .../testutil/assertion/JavaMembersAssertion.java | 3 ++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java b/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java index d7f5904fc0..1b47b47a5e 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java @@ -70,6 +70,7 @@ import static com.tngtech.archunit.core.domain.TestUtils.resolvedTargetFrom; import static com.tngtech.archunit.core.domain.TestUtils.targetFrom; import static com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion.propertiesOf; +import static com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion.runtimePropertiesOf; import static com.tngtech.archunit.testutil.assertion.JavaPackagesAssertion.sortByName; public class Assertions extends org.assertj.core.api.Assertions { @@ -327,7 +328,7 @@ public void matches(Class clazz) { assertThat(actual.getModifiers()).as("Modifiers of " + actual) .isEqualTo(JavaModifier.getModifiersForClass(clazz.getModifiers())); assertThat(actual.isArray()).as(actual + " is array").isEqualTo(clazz.isArray()); - assertThat(propertiesOf(actual.getAnnotations())).as("Annotations of " + actual) + assertThat(runtimePropertiesOf(actual.getAnnotations())).as("Annotations of " + actual) .isEqualTo(propertiesOf(clazz.getAnnotations())); if (clazz.isArray()) { diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaAnnotationAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaAnnotationAssertion.java index 50175d4e24..c2c171818d 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaAnnotationAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaAnnotationAssertion.java @@ -1,6 +1,7 @@ package com.tngtech.archunit.testutil.assertion; import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; @@ -15,17 +16,26 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.tngtech.archunit.testutil.TestUtils.invoke; +import static java.lang.annotation.RetentionPolicy.RUNTIME; public class JavaAnnotationAssertion { @SuppressWarnings("rawtypes") - public static Set> propertiesOf(Set> annotations) { + public static Set> runtimePropertiesOf(Set> annotations) { List converted = new ArrayList<>(); for (JavaAnnotation annotation : annotations) { - converted.add(annotation.as((Class) annotation.getRawType().reflect())); + Annotation reflectionAnnotation = annotation.as((Class) annotation.getRawType().reflect()); + if (isRetentionRuntime(reflectionAnnotation)) { + converted.add(reflectionAnnotation); + } } return propertiesOf(converted.toArray(new Annotation[0])); } + private static boolean isRetentionRuntime(Annotation annotation) { + return annotation.annotationType().isAnnotationPresent(Retention.class) + && annotation.annotationType().getAnnotation(Retention.class).value() == RUNTIME; + } + public static Set> propertiesOf(Annotation[] annotations) { Set> result = new HashSet<>(); for (Annotation annotation : annotations) { diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMembersAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMembersAssertion.java index 5374a2d082..ba8f233234 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMembersAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMembersAssertion.java @@ -30,6 +30,7 @@ import static com.tngtech.archunit.core.domain.JavaModifier.VOLATILE; import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion.propertiesOf; +import static com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion.runtimePropertiesOf; import static com.tngtech.archunit.testutil.assertion.JavaMemberAssertion.getExpectedFullNameOf; public class JavaMembersAssertion extends AbstractObjectAssert> { @@ -89,7 +90,7 @@ private void matchCasted(Set members) { static void assertEquivalent(JavaMember javaMember, T member) { assertThat(javaMember.getOwner().reflect()).isEqualTo(member.getDeclaringClass()); assertModifiersMatch(javaMember, member); - assertThat(propertiesOf(javaMember.getAnnotations())) + assertThat(runtimePropertiesOf(javaMember.getAnnotations())) .isEqualTo(propertiesOf(member.getAnnotations())); } From 1b3a73fd935032869642ca78a20304040da0340a Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Wed, 27 May 2020 01:10:15 +0200 Subject: [PATCH 006/115] update docs dependencies to fix security vulnerability Problem was `gem activesupport < 5.2.4.3`, but meanwhile many dependencies were pretty outdated so I have upgraded them all (and verified that the page still works, at least locally). Signed-off-by: Peter Gafert --- .gitignore | 3 +- docs/Gemfile.lock | 90 +++++++++++++++++++++++++---------------------- 2 files changed, 50 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index 928fcc5a99..2cb51f6eea 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,6 @@ out _site .sass-cache .jekyll-metadata +.jekyll-cache .asciidoctor -verification-results \ No newline at end of file +verification-results diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index edc439cc16..b9e7e03b27 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -1,92 +1,98 @@ GEM remote: https://rubygems.org/ specs: - activesupport (5.2.0) + activesupport (6.0.3.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) + zeitwerk (~> 2.2, >= 2.2.2) addressable (2.5.2) public_suffix (>= 2.0.2, < 4.0) colorator (1.1.0) - concurrent-ruby (1.0.5) + concurrent-ruby (1.1.6) em-websocket (0.5.1) eventmachine (>= 0.12.9) http_parser.rb (~> 0.6.0) eventmachine (1.2.7) faraday (0.15.2) multipart-post (>= 1.2, < 3) - ffi (1.9.25) + ffi (1.12.2) forwardable-extended (2.6.0) - gemoji (3.0.0) - html-pipeline (2.8.0) + gemoji (3.0.1) + html-pipeline (2.12.3) activesupport (>= 2) nokogiri (>= 1.4) http_parser.rb (0.6.0) - i18n (0.9.5) + i18n (1.8.2) concurrent-ruby (~> 1.0) - jekyll (3.8.4) + jekyll (4.0.1) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) - i18n (~> 0.7) - jekyll-sass-converter (~> 1.0) + i18n (>= 0.9.5, < 2) + jekyll-sass-converter (~> 2.0) jekyll-watch (~> 2.0) - kramdown (~> 1.14) + kramdown (~> 2.1) + kramdown-parser-gfm (~> 1.0) liquid (~> 4.0) mercenary (~> 0.3.3) pathutil (~> 0.9) - rouge (>= 1.7, < 4) + rouge (~> 3.0) safe_yaml (~> 1.0) - jekyll-feed (0.10.0) - jekyll (~> 3.3) + terminal-table (~> 1.8) + jekyll-feed (0.13.0) + jekyll (>= 3.7, < 5.0) jekyll-gist (1.5.0) octokit (~> 4.2) jekyll-paginate (1.1.0) - jekyll-sass-converter (1.5.2) - sass (~> 3.4) - jekyll-sitemap (1.2.0) - jekyll (~> 3.3) - jekyll-watch (2.0.0) + jekyll-sass-converter (2.1.0) + sassc (> 2.0.1, < 3.0) + jekyll-sitemap (1.4.0) + jekyll (>= 3.7, < 5.0) + jekyll-watch (2.2.1) listen (~> 3.0) - jemoji (0.10.0) + jemoji (0.12.0) gemoji (~> 3.0) html-pipeline (~> 2.2) - jekyll (~> 3.0) - kramdown (1.17.0) - liquid (4.0.0) - listen (3.1.5) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - ruby_dep (~> 1.2) + jekyll (>= 3.0, < 5.0) + kramdown (2.2.1) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.3) + listen (3.2.1) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) mercenary (0.3.6) mini_portile2 (2.4.0) - minitest (5.11.3) + minitest (5.13.0) multipart-post (2.0.0) - nokogiri (1.10.8) + nokogiri (1.10.9) mini_portile2 (~> 2.4.0) octokit (4.9.0) sawyer (~> 0.8.0, >= 0.5.3) - pathutil (0.16.1) + pathutil (0.16.2) forwardable-extended (~> 2.6) public_suffix (3.0.2) - rb-fsevent (0.10.3) - rb-inotify (0.9.10) - ffi (>= 0.5.0, < 2) - rouge (3.1.1) - ruby_dep (1.5.0) - safe_yaml (1.0.4) - sass (3.5.6) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) + rb-fsevent (0.10.4) + rb-inotify (0.10.1) + ffi (~> 1.0) + rexml (3.2.3) + rouge (3.19.0) + safe_yaml (1.0.5) + sassc (2.3.0) + ffi (~> 1.9) sawyer (0.8.1) addressable (>= 2.3.5, < 2.6) faraday (~> 0.8, < 1.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) thread_safe (0.3.6) - tzinfo (1.2.5) + tzinfo (1.2.7) thread_safe (~> 0.1) + unicode-display_width (1.7.0) + zeitwerk (2.3.0) PLATFORMS ruby @@ -100,4 +106,4 @@ DEPENDENCIES jemoji BUNDLED WITH - 1.17.3 + 2.1.4 From 085ddcd04e36338e24c468f9beefd0ce6542f1b8 Mon Sep 17 00:00:00 2001 From: Roland Weisleder Date: Thu, 7 May 2020 08:41:34 +0200 Subject: [PATCH 007/115] Refactor JavaVersion to simplify the addition of new versions Signed-off-by: Roland Weisleder --- .../tngtech/archunit/core/PluginLoader.java | 47 ++++++++++++------- .../archunit/core/PluginLoaderTest.java | 39 +++++++++++---- 2 files changed, 61 insertions(+), 25 deletions(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/PluginLoader.java b/archunit/src/main/java/com/tngtech/archunit/core/PluginLoader.java index 0f5fd42289..5a666cbbb9 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/PluginLoader.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/PluginLoader.java @@ -133,30 +133,45 @@ public Creator load(String pluginClassName) { @Internal public enum JavaVersion { - JAVA_9 { - @Override - public boolean isLessOrEqualThan(String version) { - // The new versioning scheme starting with JDK 9 is 9.x, before it was sth. like 1.8.0_122 - return parseFirstGroupOfJavaVersion(version) >= 9; - } - private int parseFirstGroupOfJavaVersion(String javaVersion) { - Matcher matcher = VERSION_PATTERN.matcher(javaVersion); - if (!matcher.matches()) { - throw new IllegalArgumentException("Can't parse Java version " + javaVersion); - } - return Integer.parseInt(matcher.group(1)); - } - }; + JAVA_9(9); + + private final int releaseVersion; + + JavaVersion(int releaseVersion) { + this.releaseVersion = releaseVersion; + } - private static final Ordering FROM_NEWEST_TO_OLDEST_ORDERING = Ordering.explicit(JAVA_9); + private static final Ordering FROM_NEWEST_TO_OLDEST_ORDERING = Ordering.natural().reverse(); private static final Pattern VERSION_PATTERN = Pattern.compile("^(\\d+).*"); - public abstract boolean isLessOrEqualThan(String version); + public boolean isLessOrEqualThan(String version) { + return this.releaseVersion <= parseJavaReleaseVersion(version); + } static List sortFromNewestToOldest(Set javaVersions) { return FROM_NEWEST_TO_OLDEST_ORDERING.sortedCopy(javaVersions); } + + private static int parseJavaReleaseVersion(String version) { + String versionToParse; + if (version.startsWith("1.")) { + // Up to JDK 8 the versioning scheme was sth. like 1.8.0_122 + versionToParse = version.substring(2); + } else { + // The new versioning scheme starting with JDK 9 is + // - for major releases: 9 + // - for patches: 9.x + // - for early access builds: 9-ea + versionToParse = version; + } + + Matcher matcher = VERSION_PATTERN.matcher(versionToParse); + if (!matcher.matches()) { + throw new IllegalArgumentException("Can't parse Java version " + version); + } + return Integer.parseInt(matcher.group(1)); + } } static class PluginLoadingFailedException extends RuntimeException { diff --git a/archunit/src/test/java/com/tngtech/archunit/core/PluginLoaderTest.java b/archunit/src/test/java/com/tngtech/archunit/core/PluginLoaderTest.java index 07f4c6c486..16851e3f1c 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/PluginLoaderTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/PluginLoaderTest.java @@ -1,41 +1,62 @@ package com.tngtech.archunit.core; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import com.tngtech.archunit.testutil.SystemPropertiesRule; import org.junit.Rule; import org.junit.Test; +import static com.tngtech.archunit.core.PluginLoader.JavaVersion.JAVA_14; import static com.tngtech.archunit.core.PluginLoader.JavaVersion.JAVA_9; import static org.assertj.core.api.Assertions.assertThat; public class PluginLoaderTest { + private static final Class pluginTypeBeforeJava9 = HashSet.class; + private static final Class pluginTypeBetweenJava9AndJava13 = ArrayList.class; + private static final Class pluginTypeAfterJava13 = HashMap.class; + @Rule public final SystemPropertiesRule systemPropertiesRule = new SystemPropertiesRule(); @Test public void loads_correct_plugin_for_version() { System.setProperty("java.version", "1.7.0_55"); - assertThat(loadsArrayListForJava9FallbackHashSet().load()).isInstanceOf(HashSet.class); + assertThat(createPluginLoader().load()).isInstanceOf(pluginTypeBeforeJava9); System.setProperty("java.version", "1.8.0_122"); - assertThat(loadsArrayListForJava9FallbackHashSet().load()).isInstanceOf(HashSet.class); + assertThat(createPluginLoader().load()).isInstanceOf(pluginTypeBeforeJava9); System.setProperty("java.version", "9"); - assertThat(loadsArrayListForJava9FallbackHashSet().load()).isInstanceOf(ArrayList.class); + assertThat(createPluginLoader().load()).isInstanceOf(pluginTypeBetweenJava9AndJava13); System.setProperty("java.version", "9.0.1"); - assertThat(loadsArrayListForJava9FallbackHashSet().load()).isInstanceOf(ArrayList.class); + assertThat(createPluginLoader().load()).isInstanceOf(pluginTypeBetweenJava9AndJava13); System.setProperty("java.version", "11-ea"); - assertThat(loadsArrayListForJava9FallbackHashSet().load()).isInstanceOf(ArrayList.class); + assertThat(createPluginLoader().load()).isInstanceOf(pluginTypeBetweenJava9AndJava13); + + System.setProperty("java.version", "13"); + assertThat(createPluginLoader().load()).isInstanceOf(pluginTypeBetweenJava9AndJava13); + + System.setProperty("java.version", "14"); + assertThat(createPluginLoader().load()).isInstanceOf(pluginTypeAfterJava13); } // PluginLoader memoizes the loaded plugin - private PluginLoader loadsArrayListForJava9FallbackHashSet() { + private PluginLoader createPluginLoader() { return PluginLoader.forType(Object.class) - .ifVersionGreaterOrEqualTo(JAVA_9).load(ArrayList.class.getName()) - .fallback(new HashSet<>()); + .ifVersionGreaterOrEqualTo(JAVA_9).load(pluginTypeBetweenJava9AndJava13.getName()) + .ifVersionGreaterOrEqualTo(JAVA_14).load(pluginTypeAfterJava13.getName()) + .fallback(newInstance(pluginTypeBeforeJava9)); + } + + private Object newInstance(Class clazz) { + try { + return clazz.getConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } } -} \ No newline at end of file +} From c8998e5567d0f598b4c5148c84744cbe7b077ee8 Mon Sep 17 00:00:00 2001 From: Roland Weisleder Date: Mon, 11 May 2020 18:11:33 +0200 Subject: [PATCH 008/115] Refactor AnnotationValueFormatter to format all annotation properties Starting with Java 14, we have to differentiate for Annotation.toString() not only the formatting of the individual values per Java version, but also how the entire list of properties is formatted. In addition, the use as AnnotationValueFormatter reads better as Function. Signed-off-by: Roland Weisleder --- .../core/domain/Java9DomainPlugin.java | 5 +- ...ava => AnnotationPropertiesFormatter.java} | 24 +++++++--- .../archunit/core/domain/AnnotationProxy.java | 31 ++++++------ .../archunit/core/domain/DomainPlugin.java | 7 ++- ...=> AnnotationPropertiesFormatterTest.java} | 36 +++++++------- .../core/domain/AnnotationProxyTest.java | 47 ++++++++++--------- 6 files changed, 81 insertions(+), 69 deletions(-) rename archunit/src/main/java/com/tngtech/archunit/core/domain/{AnnotationValueFormatter.java => AnnotationPropertiesFormatter.java} (83%) rename archunit/src/test/java/com/tngtech/archunit/core/domain/{AnnotationValueFormatterTest.java => AnnotationPropertiesFormatterTest.java} (57%) diff --git a/archunit/src/jdk9main/java/com/tngtech/archunit/core/domain/Java9DomainPlugin.java b/archunit/src/jdk9main/java/com/tngtech/archunit/core/domain/Java9DomainPlugin.java index 5ed77359a2..387fb66010 100644 --- a/archunit/src/jdk9main/java/com/tngtech/archunit/core/domain/Java9DomainPlugin.java +++ b/archunit/src/jdk9main/java/com/tngtech/archunit/core/domain/Java9DomainPlugin.java @@ -16,7 +16,6 @@ package com.tngtech.archunit.core.domain; import com.tngtech.archunit.Internal; -import com.tngtech.archunit.base.Function; import com.tngtech.archunit.core.InitialConfiguration; import com.tngtech.archunit.core.PluginLoader; @@ -27,8 +26,8 @@ @Internal public class Java9DomainPlugin implements DomainPlugin { @Override - public void plugInAnnotationValueFormatter(InitialConfiguration> valueFormatter) { - valueFormatter.set(AnnotationValueFormatter.configure() + public void plugInAnnotationPropertiesFormatter(InitialConfiguration propertiesFormatter) { + propertiesFormatter.set(AnnotationPropertiesFormatter.configure() .formattingArraysWithCurlyBrackets() .formattingTypesAsClassNames() .quotingStrings() diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationValueFormatter.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatter.java similarity index 83% rename from archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationValueFormatter.java rename to archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatter.java index 178b05174b..553230d1a6 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationValueFormatter.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatter.java @@ -17,7 +17,10 @@ import java.lang.reflect.Array; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import com.google.common.base.Joiner; import com.tngtech.archunit.base.Function; @@ -25,19 +28,26 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.tngtech.archunit.base.Function.Functions.identity; -class AnnotationValueFormatter implements Function { +class AnnotationPropertiesFormatter { private final Function, String> arrayFormatter; private final Function, String> typeFormatter; private final Function stringFormatter; - private AnnotationValueFormatter(Builder builder) { + private AnnotationPropertiesFormatter(Builder builder) { this.arrayFormatter = checkNotNull(builder.arrayFormatter); this.typeFormatter = checkNotNull(builder.typeFormatter); this.stringFormatter = checkNotNull(builder.stringFormatter); } - @Override - public String apply(Object input) { + String formatProperties(Map properties) { + Set formattedProperties = new HashSet<>(); + for (Map.Entry entry : properties.entrySet()) { + formattedProperties.add(entry.getKey() + "=" + formatValue(entry.getValue())); + } + return Joiner.on(", ").join(formattedProperties); + } + + String formatValue(Object input) { if (input instanceof Class) { return typeFormatter.apply((Class) input); } @@ -50,7 +60,7 @@ public String apply(Object input) { List elemToString = new ArrayList<>(); for (int i = 0; i < Array.getLength(input); i++) { - elemToString.add("" + apply(Array.get(input, i))); + elemToString.add(formatValue(Array.get(input, i))); } return arrayFormatter.apply(elemToString); } @@ -114,8 +124,8 @@ public String apply(String input) { return this; } - AnnotationValueFormatter build() { - return new AnnotationValueFormatter(this); + AnnotationPropertiesFormatter build() { + return new AnnotationPropertiesFormatter(this); } } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationProxy.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationProxy.java index 4401ddc152..5df3e59552 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationProxy.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationProxy.java @@ -20,17 +20,15 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.tngtech.archunit.base.Function; +import com.google.common.collect.Maps; import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.InitialConfiguration; import com.tngtech.archunit.core.MayResolveTypesViaReflection; @@ -39,10 +37,10 @@ @MayResolveTypesViaReflection(reason = "We depend on the classpath, if we proxy an annotation type") class AnnotationProxy { - private static final InitialConfiguration> valueFormatter = new InitialConfiguration<>(); + private static final InitialConfiguration propertiesFormatter = new InitialConfiguration<>(); static { - DomainPlugin.Loader.loadForCurrentPlatform().plugInAnnotationValueFormatter(valueFormatter); + DomainPlugin.Loader.loadForCurrentPlatform().plugInAnnotationPropertiesFormatter(propertiesFormatter); } public static A of(Class annotationType, JavaAnnotation toProxy) { @@ -279,17 +277,22 @@ private ToStringHandler(Class annotationType, JavaAnnotation toProxy, Conv @Override public Object handle(Object proxy, Method method, Object[] args) { - return String.format("@%s(%s)", toProxy.getRawType().getName(), propertyStrings()); + return String.format("@%s(%s)", toProxy.getRawType().getName(), propertiesString()); } - private String propertyStrings() { - Set properties = new HashSet<>(); - for (Map.Entry entry : toProxy.getProperties().entrySet()) { - Class returnType = getDeclaredMethod(entry.getKey()).getReturnType(); - String value = valueFormatter.get().apply(conversions.convertIfNecessary(entry.getValue(), returnType)); - properties.add(entry.getKey() + "=" + value); - } - return Joiner.on(", ").join(properties); + private String propertiesString() { + Map unwrappedProperties = unwrapProxiedProperties(); + return propertiesFormatter.get().formatProperties(unwrappedProperties); + } + + private Map unwrapProxiedProperties() { + return Maps.transformEntries(toProxy.getProperties(), new Maps.EntryTransformer() { + @Override + public Object transformEntry(String key, Object value) { + Class returnType = getDeclaredMethod(key).getReturnType(); + return conversions.convertIfNecessary(value, returnType); + } + }); } private Method getDeclaredMethod(String name) { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainPlugin.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainPlugin.java index 085f0d2dc5..262db15f05 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainPlugin.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainPlugin.java @@ -16,14 +16,13 @@ package com.tngtech.archunit.core.domain; import com.tngtech.archunit.Internal; -import com.tngtech.archunit.base.Function; import com.tngtech.archunit.core.InitialConfiguration; import com.tngtech.archunit.core.PluginLoader; import static com.tngtech.archunit.core.PluginLoader.JavaVersion.JAVA_9; interface DomainPlugin { - void plugInAnnotationValueFormatter(InitialConfiguration> valueFormatter); + void plugInAnnotationPropertiesFormatter(InitialConfiguration valueFormatter); @Internal class Loader { @@ -38,8 +37,8 @@ static DomainPlugin loadForCurrentPlatform() { private static class LegacyDomainPlugin implements DomainPlugin { @Override - public void plugInAnnotationValueFormatter(InitialConfiguration> valueFormatter) { - valueFormatter.set(AnnotationValueFormatter.configure() + public void plugInAnnotationPropertiesFormatter(InitialConfiguration valueFormatter) { + valueFormatter.set(AnnotationPropertiesFormatter.configure() .formattingArraysWithSquareBrackets() .formattingTypesToString() .build()); diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationValueFormatterTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatterTest.java similarity index 57% rename from archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationValueFormatterTest.java rename to archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatterTest.java index 50b40838e7..69f42383d4 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationValueFormatterTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatterTest.java @@ -7,78 +7,78 @@ import static org.assertj.core.api.Assertions.assertThat; -public class AnnotationValueFormatterTest { +public class AnnotationPropertiesFormatterTest { @Test public void formats_arrays_with_square_brackets() { - AnnotationValueFormatter formatter = AnnotationValueFormatter.configure() + AnnotationPropertiesFormatter formatter = AnnotationPropertiesFormatter.configure() .formattingArraysWithSquareBrackets() .formattingTypesAsClassNames() .build(); - assertThat(formatter.apply(new String[]{"one", "two"})) + assertThat(formatter.formatValue(new String[]{"one", "two"})) .isEqualTo("[one, two]"); - assertThat(formatter.apply(new int[]{5, 9})) + assertThat(formatter.formatValue(new int[]{5, 9})) .isEqualTo("[5, 9]"); - assertThat(formatter.apply(new List[]{ImmutableList.of(1, 2), ImmutableList.of(3, 4)})) + assertThat(formatter.formatValue(new List[]{ImmutableList.of(1, 2), ImmutableList.of(3, 4)})) .isEqualTo("[[1, 2], [3, 4]]"); } @Test public void formats_arrays_with_curly_brackets() { - AnnotationValueFormatter formatter = AnnotationValueFormatter.configure() + AnnotationPropertiesFormatter formatter = AnnotationPropertiesFormatter.configure() .formattingArraysWithCurlyBrackets() .formattingTypesAsClassNames() .quotingStrings() .build(); - assertThat(formatter.apply(new String[]{"one", "two"})) + assertThat(formatter.formatValue(new String[]{"one", "two"})) .isEqualTo("{\"one\", \"two\"}"); - assertThat(formatter.apply(new int[]{5, 9})) + assertThat(formatter.formatValue(new int[]{5, 9})) .isEqualTo("{5, 9}"); - assertThat(formatter.apply(new List[]{ImmutableList.of(1, 2), ImmutableList.of(3, 4)})) + assertThat(formatter.formatValue(new List[]{ImmutableList.of(1, 2), ImmutableList.of(3, 4)})) .isEqualTo("{[1, 2], [3, 4]}"); } @Test public void formats_types_to_string() { - AnnotationValueFormatter formatter = AnnotationValueFormatter.configure() + AnnotationPropertiesFormatter formatter = AnnotationPropertiesFormatter.configure() .formattingArraysWithCurlyBrackets() .formattingTypesToString() .quotingStrings() .build(); - assertThat(formatter.apply(Object.class)) + assertThat(formatter.formatValue(Object.class)) .isEqualTo("class java.lang.Object"); } @Test public void formats_types_as_classNames() { - AnnotationValueFormatter formatter = AnnotationValueFormatter.configure() + AnnotationPropertiesFormatter formatter = AnnotationPropertiesFormatter.configure() .formattingArraysWithCurlyBrackets() .formattingTypesAsClassNames() .quotingStrings() .build(); - assertThat(formatter.apply(Object.class)) + assertThat(formatter.formatValue(Object.class)) .isEqualTo("java.lang.Object.class"); } @Test public void quotes_strings() { - AnnotationValueFormatter.Builder builder = AnnotationValueFormatter.configure() + AnnotationPropertiesFormatter.Builder builder = AnnotationPropertiesFormatter.configure() .formattingArraysWithCurlyBrackets() .formattingTypesAsClassNames(); - AnnotationValueFormatter formatter = builder.build(); - assertThat(formatter.apply("string")) + AnnotationPropertiesFormatter formatter = builder.build(); + assertThat(formatter.formatValue("string")) .isEqualTo("string"); formatter = builder.quotingStrings().build(); - assertThat(formatter.apply("string")) + assertThat(formatter.formatValue("string")) .isEqualTo("\"string\""); } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationProxyTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationProxyTest.java index 656e415491..f244725221 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationProxyTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationProxyTest.java @@ -6,13 +6,13 @@ import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.collect.ImmutableMap; -import com.tngtech.archunit.base.Function; import com.tngtech.archunit.core.InitialConfiguration; import com.tngtech.archunit.core.importer.ImportTestUtils; import com.tngtech.archunit.core.importer.JavaAnnotationTestBuilder; @@ -254,34 +254,34 @@ public void array_is_converted_to_the_correct_type() { } private ImmutableMap propertiesOf(Class type) { - Function formatter = getAnnotationValueFormatterForCurrentPlatform(); + AnnotationPropertiesFormatter formatter = getAnnotationPropertiesFormatterForCurrentPlatform(); ImmutableMap result = ImmutableMap.builder() .put("primitive", "77") .put("primitiveWithDefault", "1") - .put("primitives", formatter.apply(new int[]{77, 88})) - .put("primitivesWithDefault", formatter.apply(new int[]{1, 2})) - .put("string", formatter.apply("foo")) - .put("stringWithDefault", formatter.apply("something")) - .put("strings", formatter.apply(new String[]{"one", "two"})) - .put("stringsWithDefault", formatter.apply(new String[]{"something", "more"})) - .put("type", formatter.apply(String.class)) - .put("typeWithDefault", formatter.apply(Serializable.class)) - .put("types", formatter.apply(new Class[]{Map.class, List.class})) - .put("typesWithDefault", formatter.apply(new Class[]{Serializable.class, String.class})) + .put("primitives", formatter.formatValue(new int[]{77, 88})) + .put("primitivesWithDefault", formatter.formatValue(new int[]{1, 2})) + .put("string", formatter.formatValue("foo")) + .put("stringWithDefault", formatter.formatValue("something")) + .put("strings", formatter.formatValue(new String[]{"one", "two"})) + .put("stringsWithDefault", formatter.formatValue(new String[]{"something", "more"})) + .put("type", formatter.formatValue(String.class)) + .put("typeWithDefault", formatter.formatValue(Serializable.class)) + .put("types", formatter.formatValue(new Class[]{Map.class, List.class})) + .put("typesWithDefault", formatter.formatValue(new Class[]{Serializable.class, String.class})) .put("enumConstant", String.valueOf(TestEnum.SECOND)) .put("enumConstantWithDefault", String.valueOf(TestEnum.FIRST)) - .put("enumConstants", formatter.apply(new TestEnum[]{TestEnum.SECOND, TestEnum.THIRD})) - .put("enumConstantsWithDefault", formatter.apply(new TestEnum[]{TestEnum.FIRST, TestEnum.SECOND})) + .put("enumConstants", formatter.formatValue(new TestEnum[]{TestEnum.SECOND, TestEnum.THIRD})) + .put("enumConstantsWithDefault", formatter.formatValue(new TestEnum[]{TestEnum.FIRST, TestEnum.SECOND})) .put("subAnnotation", formatSubAnnotation(formatter, "custom")) .put("subAnnotationWithDefault", formatSubAnnotation(formatter, "default")) .put("subAnnotations", - formatter.apply(new Object[]{ + formatter.formatValue(new Object[]{ subAnnotationFormatter(formatter, "customOne"), subAnnotationFormatter(formatter, "customTwo")})) .put("subAnnotationsWithDefault", - formatter.apply(new Object[]{ + formatter.formatValue(new Object[]{ subAnnotationFormatter(formatter, "defaultOne"), subAnnotationFormatter(formatter, "defaultTwo")})) .build(); @@ -289,19 +289,20 @@ private ImmutableMap propertiesOf(Class type) { return result; } - private Function getAnnotationValueFormatterForCurrentPlatform() { + private AnnotationPropertiesFormatter getAnnotationPropertiesFormatterForCurrentPlatform() { DomainPlugin domainPlugin = DomainPlugin.Loader.loadForCurrentPlatform(); - InitialConfiguration> valueFormatter = new InitialConfiguration<>(); - domainPlugin.plugInAnnotationValueFormatter(valueFormatter); - return valueFormatter.get(); + InitialConfiguration formatter = new InitialConfiguration<>(); + domainPlugin.plugInAnnotationPropertiesFormatter(formatter); + return formatter.get(); } - private String formatSubAnnotation(Function formatter, String value) { - return "@com.tngtech.archunit.core.domain.AnnotationProxyTest$SubAnnotation(value=" + formatter.apply(value) + ")"; + private String formatSubAnnotation(AnnotationPropertiesFormatter formatter, String value) { + Map properties = Collections.singletonMap("value", (Object) value); + return "@com.tngtech.archunit.core.domain.AnnotationProxyTest$SubAnnotation(" + formatter.formatProperties(properties) + ")"; } // NOTE: We do not want this value to be treated as a string by the formatter, and e.g. quoted -> Object - private Object subAnnotationFormatter(final Function formatter, final String value) { + private Object subAnnotationFormatter(final AnnotationPropertiesFormatter formatter, final String value) { return new Object() { @Override public String toString() { From 8705026d1a0d166b888190511f2277deb26b9e93 Mon Sep 17 00:00:00 2001 From: Roland Weisleder Date: Sun, 17 May 2020 12:42:53 +0200 Subject: [PATCH 009/115] Handle Annotation.toString() in Java 14 Annotation.toString() produces different results for single-element annotations on different JREs: * Java 7/8: @Copyright(value=the value) * Java 9-13: @Copyright(value="the value") * Java 14: @Copyright("the value") This commits implements the behaviour of Java 14. See also: * https://bugs.openjdk.java.net/browse/JDK-8164819 * https://github.com/google/guice/issues/1312 Signed-off-by: Roland Weisleder Signed-off-by: Peter Gafert --- .../tngtech/archunit/core/PluginLoader.java | 3 +- .../domain/AnnotationPropertiesFormatter.java | 21 +++++++++++ .../archunit/core/domain/DomainPlugin.java | 2 + .../core/domain/Java14DomainPlugin.java | 37 +++++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 archunit/src/main/java/com/tngtech/archunit/core/domain/Java14DomainPlugin.java diff --git a/archunit/src/main/java/com/tngtech/archunit/core/PluginLoader.java b/archunit/src/main/java/com/tngtech/archunit/core/PluginLoader.java index 5a666cbbb9..244240b9ce 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/PluginLoader.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/PluginLoader.java @@ -134,7 +134,8 @@ public Creator load(String pluginClassName) { @Internal public enum JavaVersion { - JAVA_9(9); + JAVA_9(9), + JAVA_14(14); private final int releaseVersion; diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatter.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatter.java index 553230d1a6..3ab49b7b1b 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatter.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatter.java @@ -32,14 +32,21 @@ class AnnotationPropertiesFormatter { private final Function, String> arrayFormatter; private final Function, String> typeFormatter; private final Function stringFormatter; + private final boolean omitOptionalIdentifierForSingleElementAnnotations; private AnnotationPropertiesFormatter(Builder builder) { this.arrayFormatter = checkNotNull(builder.arrayFormatter); this.typeFormatter = checkNotNull(builder.typeFormatter); this.stringFormatter = checkNotNull(builder.stringFormatter); + this.omitOptionalIdentifierForSingleElementAnnotations = builder.omitOptionalIdentifierForSingleElementAnnotations; } String formatProperties(Map properties) { + // see Builder#omitOptionalIdentifierForSingleElementAnnotations() for documentation + if (properties.size() == 1 && properties.containsKey("value") && omitOptionalIdentifierForSingleElementAnnotations) { + return formatValue(properties.get("value")); + } + Set formattedProperties = new HashSet<>(); for (Map.Entry entry : properties.entrySet()) { formattedProperties.add(entry.getKey() + "=" + formatValue(entry.getValue())); @@ -73,6 +80,7 @@ static class Builder { private Function, String> arrayFormatter; private Function, String> typeFormatter; private Function stringFormatter = identity(); + private boolean omitOptionalIdentifierForSingleElementAnnotations = false; Builder formattingArraysWithSquareBrackets() { arrayFormatter = new Function, String>() { @@ -124,6 +132,19 @@ public String apply(String input) { return this; } + /** + * Configures that the identifier is omitted if the annotation is a + * single-element annotation + * and the identifier of the only element is "value". + * + *
  • Example with this configuration: {@code @Copyright("2020 Acme Corporation")}
  • + *
  • Example without this configuration: {@code @Copyright(value="2020 Acme Corporation")}
+ */ + Builder omitOptionalIdentifierForSingleElementAnnotations() { + omitOptionalIdentifierForSingleElementAnnotations = true; + return this; + } + AnnotationPropertiesFormatter build() { return new AnnotationPropertiesFormatter(this); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainPlugin.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainPlugin.java index 262db15f05..dfb17e2caa 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainPlugin.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainPlugin.java @@ -19,6 +19,7 @@ import com.tngtech.archunit.core.InitialConfiguration; import com.tngtech.archunit.core.PluginLoader; +import static com.tngtech.archunit.core.PluginLoader.JavaVersion.JAVA_14; import static com.tngtech.archunit.core.PluginLoader.JavaVersion.JAVA_9; interface DomainPlugin { @@ -29,6 +30,7 @@ class Loader { private static final PluginLoader pluginLoader = PluginLoader .forType(DomainPlugin.class) .ifVersionGreaterOrEqualTo(JAVA_9).load("com.tngtech.archunit.core.domain.Java9DomainPlugin") + .ifVersionGreaterOrEqualTo(JAVA_14).load("com.tngtech.archunit.core.domain.Java14DomainPlugin") .fallback(new LegacyDomainPlugin()); static DomainPlugin loadForCurrentPlatform() { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/Java14DomainPlugin.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/Java14DomainPlugin.java new file mode 100644 index 0000000000..14992c62bc --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/Java14DomainPlugin.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014-2020 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.core.domain; + +import com.tngtech.archunit.Internal; +import com.tngtech.archunit.core.InitialConfiguration; +import com.tngtech.archunit.core.PluginLoader; + +/** + * Resolved via {@link PluginLoader} + */ +@SuppressWarnings("unused") +@Internal +public class Java14DomainPlugin implements DomainPlugin { + @Override + public void plugInAnnotationPropertiesFormatter(InitialConfiguration propertiesFormatter) { + propertiesFormatter.set(AnnotationPropertiesFormatter.configure() + .formattingArraysWithCurlyBrackets() + .formattingTypesAsClassNames() + .quotingStrings() + .omitOptionalIdentifierForSingleElementAnnotations() + .build()); + } +} From 44d8caf358daab21631f86c3b32859660b2b32bc Mon Sep 17 00:00:00 2001 From: Markus Spanier Date: Thu, 28 May 2020 20:47:19 +0200 Subject: [PATCH 010/115] Run integration tests for all JDKs in Github action for CI Signed-off-by: Markus Spanier --- .github/workflows/build.yml | 72 +++++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 95b4f6453c..2f63e74aa0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,26 +8,84 @@ on: pull_request: jobs: - build: + build-unix: strategy: matrix: os: - ubuntu-latest - macos-latest - - windows-latest java_version: + - 7 + - 8 + - 9 + - 10 - 11 - + - 12 + - 13 + - 14 runs-on: ${{ matrix.os }} - steps: - name: Checkout uses: actions/checkout@v2 - - name: Set up Java + - name: Set up Java for build + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Build + run: ./gradlew -PallTests build + - name: Prepare integration test + run: ./gradlew publishToMavenLocal + - name: Set up Java for integration test uses: actions/setup-java@v1 with: java-version: ${{ matrix.java_version }} + - name: Integration test + env: + JAVA_VERSION: ${{ matrix.java_version }} + run: | + echo ${JAVA_VERSION} + JAVA_HOME_MAVEN=${JAVA_HOME} + echo ${JAVA_HOME_MAVEN} + JAVA_HOME=${JAVA_HOME_11_X64} + echo ${JAVA_HOME} + ./gradlew runMavenTest -Pjava${JAVA_VERSION}Home=${JAVA_HOME_MAVEN} + build-windows: + strategy: + matrix: + os: + - windows-latest + java_version: + - 7 + - 8 + - 9 + - 10 + - 11 + - 12 + - 13 + - 14 + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Java for build + uses: actions/setup-java@v1 + with: + java-version: 11 - name: Build - uses: eskatos/gradle-command-action@v1 + run: .\gradlew.bat -PallTests build + - name: Prepare integration test + run: .\gradlew.bat publishToMavenLocal + - name: Set up Java for integration test + uses: actions/setup-java@v1 with: - arguments: build -PallTests + java-version: ${{ matrix.java_version }} + - name: Integration test + env: + JAVA_VERSION: ${{ matrix.java_version }} + run: | + $env:JAVA_VERSION + $env:JAVA_HOME_MAVEN = $env:JAVA_HOME + $env:JAVA_HOME_MAVEN + $env:JAVA_HOME = $env:JAVA_HOME_11_X64 + $env:JAVA_HOME + .\gradlew.bat runMavenTest "-Pjava$($env:JAVA_VERSION)Home=$($env:JAVA_HOME_MAVEN)" From 77ab7f8adf98d5d6f79f53f4170bda2258ede8ec Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Tue, 9 Jun 2020 21:03:31 +0200 Subject: [PATCH 011/115] prepare Maven integration tests for unification with multi-JDK tests We will have multi-JDK tests for the normal unit tests (to be added in a separate PR) which will be composed in a similar way as I have now. The problem of quoting I decided to handle within the Gradle file, because I got sick of the strange quotation handling ;-) I added the `eskatos/gradle-command-action` again to be platform independent. Other than that I added a little ugly script to sift through the installed JDKs and provide the `JAVA_HOMEs` in a reasonable format that causes little problems downstream. Unfortunately this was the only way I found to reliably identify the JDK homes no matter which version combination was chosen. The new `testing.gradle` file also is a preparation so we can reuse those JDKs for the multi-JDK unit tests. Signed-off-by: Peter Gafert --- .github/workflows/build.yml | 87 +++++++------------ build-steps/build-steps.gradle | 3 +- .../maven-integration-test.gradle | 28 ++---- build-steps/testing/testing.gradle | 19 ++++ 4 files changed, 57 insertions(+), 80 deletions(-) create mode 100644 build-steps/testing/testing.gradle diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2f63e74aa0..7cd66089e1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,57 +7,21 @@ on: - release-* pull_request: +env: + build_java_version: 11 + jobs: - build-unix: + integration-test: strategy: matrix: os: - ubuntu-latest - macos-latest - java_version: - - 7 - - 8 - - 9 - - 10 - - 11 - - 12 - - 13 - - 14 - runs-on: ${{ matrix.os }} - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Set up Java for build - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Build - run: ./gradlew -PallTests build - - name: Prepare integration test - run: ./gradlew publishToMavenLocal - - name: Set up Java for integration test - uses: actions/setup-java@v1 - with: - java-version: ${{ matrix.java_version }} - - name: Integration test - env: - JAVA_VERSION: ${{ matrix.java_version }} - run: | - echo ${JAVA_VERSION} - JAVA_HOME_MAVEN=${JAVA_HOME} - echo ${JAVA_HOME_MAVEN} - JAVA_HOME=${JAVA_HOME_11_X64} - echo ${JAVA_HOME} - ./gradlew runMavenTest -Pjava${JAVA_VERSION}Home=${JAVA_HOME_MAVEN} - build-windows: - strategy: - matrix: - os: - windows-latest java_version: - - 7 - - 8 - - 9 + - 7 + - 8 + - 9 - 10 - 11 - 12 @@ -67,25 +31,32 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 - - name: Set up Java for build + - name: Set up Build JDK uses: actions/setup-java@v1 with: - java-version: 11 - - name: Build - run: .\gradlew.bat -PallTests build - - name: Prepare integration test - run: .\gradlew.bat publishToMavenLocal - - name: Set up Java for integration test + java-version: ${{ env.build_java_version }} + - name: Set up Test JDK uses: actions/setup-java@v1 with: java-version: ${{ matrix.java_version }} + - name: Provide installed JDKs + uses: actions/github-script@v1 + id: provideJdkPaths + with: + script: | + for ( let envVarName in process.env ) { + if (/JAVA_HOME_\d.*64/.test(envVarName)) { + const version = envVarName.match(/JAVA_HOME_(\d+).*64/)[1]; + if (version === "${{ matrix.test_java_version }}") { + core.exportVariable('test_jdk_path', process.env[envVarName]); + } else if (version === "${{ env.build_java_version }}") { + core.exportVariable('build_jdk_path', process.env[envVarName]); + } + } + } - name: Integration test + uses: eskatos/gradle-command-action@v1 env: - JAVA_VERSION: ${{ matrix.java_version }} - run: | - $env:JAVA_VERSION - $env:JAVA_HOME_MAVEN = $env:JAVA_HOME - $env:JAVA_HOME_MAVEN - $env:JAVA_HOME = $env:JAVA_HOME_11_X64 - $env:JAVA_HOME - .\gradlew.bat runMavenTest "-Pjava$($env:JAVA_VERSION)Home=$($env:JAVA_HOME_MAVEN)" + JAVA_HOME: ${{ env.build_jdk_path }} + with: + arguments: build -xtest -xspotbugsMain -xjavadoc publishToMavenLocal runMavenTest -Pjava${{ matrix.test_java_version }}Home="${{ env.test_jdk_path }}" diff --git a/build-steps/build-steps.gradle b/build-steps/build-steps.gradle index 32487608fd..d62806af59 100644 --- a/build-steps/build-steps.gradle +++ b/build-steps/build-steps.gradle @@ -1,5 +1,6 @@ def utilsPath = { "build-steps/${it}" } +apply from: utilsPath('testing/testing.gradle') apply from: utilsPath('archiving/archiving.gradle') apply from: utilsPath('codequality/spotbugs.gradle') apply from: utilsPath('release/publish.gradle') @@ -8,4 +9,4 @@ apply from: utilsPath('maven-integration-test/maven-integration-test.gradle') apply from: utilsPath('ci/ci-config.gradle') apply from: utilsPath('release/check-uploaded-artifacts.gradle') apply from: utilsPath('release/create-release-news.gradle') -apply from: utilsPath('release/release.gradle') \ No newline at end of file +apply from: utilsPath('release/release.gradle') diff --git a/build-steps/maven-integration-test/maven-integration-test.gradle b/build-steps/maven-integration-test/maven-integration-test.gradle index 01c3e8e2cb..e664e75d7f 100644 --- a/build-steps/maven-integration-test/maven-integration-test.gradle +++ b/build-steps/maven-integration-test/maven-integration-test.gradle @@ -190,23 +190,9 @@ def addMavenTest = { config -> } } -def javaConfigs = [ - [suffix: "java7", javaVersion: JavaVersion.VERSION_1_7, jdkProp: "java7Home"], - [suffix: "java8", javaVersion: JavaVersion.VERSION_1_8, jdkProp: "java8Home"], - [suffix: "java9", javaVersion: JavaVersion.VERSION_1_9, jdkProp: "java9Home"], - [suffix: "java10", javaVersion: JavaVersion.VERSION_1_10, jdkProp: "java10Home"], - [suffix: "java11", javaVersion: JavaVersion.VERSION_11, jdkProp: "java11Home"], - [suffix: "java12", javaVersion: JavaVersion.VERSION_12, jdkProp: "java12Home"], - [suffix: "java13", javaVersion: JavaVersion.VERSION_13, jdkProp: "java13Home"], - [suffix: "java14", javaVersion: JavaVersion.VERSION_14, jdkProp: "java14Home"] -] - -javaConfigs = javaConfigs.findAll { project.hasProperty(it.jdkProp) } - .collect { config -> config + [jdkPath: project[config.jdkProp]] } - -javaConfigs = javaConfigs ?: [[suffix: 'java7', javaVersion: JavaVersion.VERSION_1_7]] +def integrationTestJdks = testJdks() ?: [[suffix: 'java7', javaVersion: JavaVersion.VERSION_1_7]] -javaConfigs = javaConfigs.collect { config -> +integrationTestJdks = integrationTestJdks.collect { config -> // JUnit 5 needs at least Java 8 def testSupportTypes = config.javaVersion > JavaVersion.VERSION_1_7 ? ['plain', 'junit4', 'junit5'] : @@ -216,17 +202,17 @@ javaConfigs = javaConfigs.collect { config -> } }.flatten() -javaConfigs.findAll { it.suffix.endsWith('plain') }*.with { +integrationTestJdks.findAll { it.suffix.endsWith('plain') }*.with { surefireExampleConfiguration = 'com.tngtech.archunit.exampletest.Example' archunitTestArtifact = 'archunit' } -javaConfigs.findAll { it.suffix.endsWith('junit4') }*.with { +integrationTestJdks.findAll { it.suffix.endsWith('junit4') }*.with { surefireExampleConfiguration = 'com.tngtech.archunit.exampletest.junit4.Example' archunitTestArtifact = 'archunit-junit4' } -javaConfigs.findAll { it.suffix.endsWith('junit5') }*.with { +integrationTestJdks.findAll { it.suffix.endsWith('junit5') }*.with { surefireExampleConfiguration = 'example' archunitTestArtifact = 'archunit-junit5' additionalDependencies = """ @@ -238,11 +224,11 @@ javaConfigs.findAll { it.suffix.endsWith('junit5') }*.with { """ } -javaConfigs.each { config -> +integrationTestJdks.each { config -> project.with(addMavenTest(config)) } -def suffixes = javaConfigs*.suffix.sort() +def suffixes = integrationTestJdks*.suffix.sort() [suffixes, suffixes.tail()].transpose().each { twoConsecutiveSuffixes -> tasks["prepareMavenTest${twoConsecutiveSuffixes[1]}"].mustRunAfter(tasks["cleanUpMavenTest${twoConsecutiveSuffixes[0]}"]) } diff --git a/build-steps/testing/testing.gradle b/build-steps/testing/testing.gradle new file mode 100644 index 0000000000..fd79a14fbd --- /dev/null +++ b/build-steps/testing/testing.gradle @@ -0,0 +1,19 @@ +def unquote = { string -> string.replaceAll(/^"(.*)"$/, '$1')} +def testJdksDefinition = [ + [suffix: "Jre7", javaVersion: JavaVersion.VERSION_1_7, jdkProp: "java7Home"], + [suffix: "Jre8", javaVersion: JavaVersion.VERSION_1_8, jdkProp: "java8Home"], + [suffix: "Jre9", javaVersion: JavaVersion.VERSION_1_9, jdkProp: "java9Home"], + [suffix: "Jre10", javaVersion: JavaVersion.VERSION_1_10, jdkProp: "java10Home"], + [suffix: "Jre11", javaVersion: JavaVersion.VERSION_11, jdkProp: "java11Home"], + [suffix: "Jre12", javaVersion: JavaVersion.VERSION_12, jdkProp: "java12Home"], + [suffix: "Jre13", javaVersion: JavaVersion.VERSION_13, jdkProp: "java13Home"], + [suffix: "Jre14", javaVersion: JavaVersion.VERSION_14, jdkProp: "java14Home"] +] + .findAll { project.hasProperty(it.jdkProp) } + .collect { config -> config + [jdkPath: unquote(project[config.jdkProp])] } + +ext { + testJdks = { + testJdksDefinition.collect { [:] + it } + } +} From c644333504d507affdca79e7f2fd3aefd4ea87cc Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Tue, 26 May 2020 23:01:10 +0200 Subject: [PATCH 012/115] add possibility to execute multi JDK tests Adds the possibility to `addMultiJdkTestsFor` any test tasks, which will look for the same properties as the `mavenIntegrationTest`, i.e. `javaxHome` properties, and then use those to run the tests. If `-PmultiJdkTest` is set then the original test task will be replaced by adjusted `originalTestJre${x}` tasks for all Java versions that are available. Furthermore extended GitHub actions to run these cross JDK tests as a matrix. Unfortunately this has proven to be way more difficult than expected, e.g. installing several JDKs and assigning the correct one to build and test. One problem is that for some reason the `setup-java` task does not provide the output described in the documentation (i.e. the path of the installed JDK). Furthermore the environment variables it sets are illegal for bash / Gradle, i.e. `System.env` will not detect these environment variables (or not all `process.env` variables are provided as environment variables to the task by GH Actions, unfortunately the docs at the moment are a little fuzzy there). So altogether you can specify `setup-java: with: java-version: 9`, but then there is no easy way to reference the path of the installed JDK, because it will be a variable like `JAVA_HOME_9.0.4_x64` and to hard reference this version number seems brittle. Thus I have included a simple script action as a workaround that will slurp all the `JAVA_HOME_.._x64` variables, parse the version number and then set robust env variables to be used to define the Gradle task (i.e. specify `JAVA_HOME` for the Gradle task, but also the `java${x}Home` for the multi-JDK test). Note that the GH Actions machines also have predefined JDKs that follow the naming pattern `JAVA_HOME_11_X64` without exact version numbers, so this is somewhat ambiguous, but in the end the exact `JAVA_HOME` doesn't matter, as long as the version is correct. Also note that the convention of `setup-java` for Java versions < 9 still follows the pattern `JAVA_HOME_7.x.y_x64` and not `1.7.x` like the legacy versioning would suggest. Thus the simple regex checking for the first number that the script action does seems good enough for now. Signed-off-by: Peter Gafert --- .github/workflows/build.yml | 63 ++++++++++++++++++++++ archunit-integration-test/build.gradle | 3 +- archunit/build.gradle | 4 ++ build-steps/testing/testing.gradle | 75 ++++++++++++++++++++++++++ build.gradle | 42 +-------------- 5 files changed, 145 insertions(+), 42 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7cd66089e1..b92576ce6e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,69 @@ env: build_java_version: 11 jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Java + uses: actions/setup-java@v1 + with: + java-version: ${{ env.build_java_version }} + - name: Build + uses: eskatos/gradle-command-action@v1 + with: + arguments: build + + test: + strategy: + matrix: + os: + - ubuntu-latest + - macos-latest + - windows-latest + test_java_version: + - 7 + - 8 + - 9 + - 10 + - 11 + - 12 + - 13 + - 14 + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Build JDK + uses: actions/setup-java@v1 + with: + java-version: ${{ env.build_java_version }} + - name: Set up Test JDK + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.test_java_version }} + - name: Provide installed JDKs + uses: actions/github-script@v1 + with: + script: | + for ( let envVarName in process.env ) { + if (/JAVA_HOME_\d.*64/.test(envVarName)) { + const version = envVarName.match(/JAVA_HOME_(\d+).*64/)[1]; + if (version === "${{ matrix.test_java_version }}") { + core.exportVariable('test_jdk_path', process.env[envVarName]); + } else if (version === "${{ env.build_java_version }}") { + core.exportVariable('build_jdk_path', process.env[envVarName]); + } + } + } + - name: Test + uses: eskatos/gradle-command-action@v1 + env: + JAVA_HOME: ${{ env.build_jdk_path }} + with: + arguments: testJre${{ matrix.test_java_version }} -PallTests -PmultiJdkTest -Pjava${{ matrix.test_java_version }}Home="${{ env.test_jdk_path }}" + integration-test: strategy: matrix: diff --git a/archunit-integration-test/build.gradle b/archunit-integration-test/build.gradle index cb0b666974..ad506484ef 100644 --- a/archunit-integration-test/build.gradle +++ b/archunit-integration-test/build.gradle @@ -25,4 +25,5 @@ dependencies { test { useJUnitPlatform() -} \ No newline at end of file +} +addMultiJdkTestsFor project, test diff --git a/archunit/build.gradle b/archunit/build.gradle index fbcb0dac9e..b479538fb7 100644 --- a/archunit/build.gradle +++ b/archunit/build.gradle @@ -89,6 +89,10 @@ task jdk9Test(type: Test) { [jar, test]*.dependsOn compileJdk9mainJava +[test, jdk9Test].each { testTask -> + addMultiJdkTestsFor project, testTask +} + spotbugsJdk9test.enabled = false idea { diff --git a/build-steps/testing/testing.gradle b/build-steps/testing/testing.gradle index fd79a14fbd..42055ffabf 100644 --- a/build-steps/testing/testing.gradle +++ b/build-steps/testing/testing.gradle @@ -16,4 +16,79 @@ ext { testJdks = { testJdksDefinition.collect { [:] + it } } + + addTestJarTo = { proj -> + proj.with { + configurations { + tests.extendsFrom testRuntime + } + + task testJar(type: Jar) { + archiveClassifier = 'tests' + from sourceSets.test.output + } + + artifacts { + tests testJar + } + } + } + + configureSlowTestsFor = { proj -> + proj.afterEvaluate { projAfterEvaluate -> + projAfterEvaluate.tasks.withType(Test) { + if (!project.hasProperty('allTests')) { + useJUnit { + excludeCategories 'com.tngtech.archunit.Slow' + } + } + } + } + } + + addMultiJdkTestsFor = { Project proj, Test testTask -> + if (!project.hasProperty('multiJdkTest')) { + return + } + + assert testTask.name == 'test' || testTask.name ==~ /jdk\d+Test/: "Task must either be named 'test' or match 'jdk{x}Test'" + testTask.enabled = false + + def taskMinimumJdkVersion = testTask.name.replaceAll(/jdk(\d+)Test/, '$1').with { it ==~ /\d+/ ? JavaVersion.toVersion(it) : JavaVersion.VERSION_1_7 } + def additionalJdksToTest = testJdks().findAll { it.javaVersion >= taskMinimumJdkVersion } + + def findJavaExecutable = { jdkPath -> + def javaExecutableUnix = new File("${jdkPath}", 'bin/java') + def javaExecutableWindows = new File("${javaExecutableUnix.absolutePath}.exe") + def result = javaExecutableUnix.exists() ? javaExecutableUnix : javaExecutableWindows + assert result.exists(): "Could not find path of Java executable for JDK path ${jdkPath}. Tried ${javaExecutableUnix} and ${javaExecutableWindows}." + result + } + + additionalJdksToTest.each { jdk -> + def additionalTestTask = proj.tasks.create(name: "${testTask.name}${jdk.suffix}", type: Test) { + executable = findJavaExecutable(jdk.jdkPath) + + testClassesDirs = testTask.testClassesDirs + classpath = testTask.classpath + } + + testTask.dependsOn(additionalTestTask) + } + } +} + +configure(subprojects.findAll { it.name != 'docs' }) { + afterEvaluate { project -> + project.tasks.withType(Test) { + maxHeapSize = "2G" + + testLogging { + events "failed" + exceptionFormat "full" + } + + ignoreFailures = project.hasProperty('ignoreTestFailures') + } + } } diff --git a/build.gradle b/build.gradle index 416a2a2da0..93dca64022 100644 --- a/build.gradle +++ b/build.gradle @@ -89,35 +89,6 @@ ext { project(':archunit-junit5')] createModuleDescription = { description, proj -> "${description} - Module '${proj.name}'" } - addTestJarTo = { proj -> - proj.with { - configurations { - tests.extendsFrom testRuntime - } - - task testJar(type: Jar) { - archiveClassifier = 'tests' - from sourceSets.test.output - } - - artifacts { - tests testJar - } - } - } - - configureSlowTestsFor = { proj -> - proj.with { - test { - if (!project.hasProperty('allTests')) { - useJUnit { - excludeCategories 'com.tngtech.archunit.Slow' - } - } - } - } - } - currentScriptRootOf = { it.buildscript.sourceFile.parentFile } } @@ -142,7 +113,7 @@ task clean { } } -configure(subprojects.findAll {it.name != 'docs'}) { +configure(subprojects.findAll { it.name != 'docs' }) { apply plugin: 'java-library' description createModuleDescription(rootProject.description, project) @@ -153,17 +124,6 @@ configure(subprojects.findAll {it.name != 'docs'}) { javadoc { options.addBooleanOption('html5', true) } - - test { - maxHeapSize = "2G" - - testLogging { - events "failed" - exceptionFormat "full" - } - - ignoreFailures = project.hasProperty('ignoreTestFailures') - } } apply from: 'build-steps/build-steps.gradle' From 744fd09da0efa8be9419a4140ffa2a79e0e18b8d Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Mon, 1 Jun 2020 00:12:24 +0200 Subject: [PATCH 013/115] fix tests broken with JRE version < 9 Since the tests have not been run with a JRE < 9 for a while now, there were some tests that only were compatible with JRE >= 9. Those 7 failing tests were fixed by - considering `rt.jar` for JRE < 9 where only JRTs were considered - creating a class that is certainly not on the classpath instead of relying on `ClassFileImporter` not being able to import anything if the classpath properties (classpath + boot class path) are unset (because the legacy resolver would also look into the `URLClassLoaders` of `Location.class` and thus still be able to find classes, even if the system properties are unset) - fixing ordering problems - there were some problems with ordering (e.g. using `ImmutableList.copyOf(set)`) which did not show on JRE >= 9 (i.e. the order matched by accident) - decrease memory footprint of `SliceRulePerformanceTest`: Seems like JRE 7 & 8 are less memory efficient causing an `OutOfMemoryError` in this test. By counting the cycles within the list of failure details (instead of joining them via `toString()`) we refrain from creating a > 700MB string within the heap. Signed-off-by: Peter Gafert --- .../archunit/lang/ConditionEvents.java | 20 +++++--- .../archunit/lang/FailureMessages.java | 5 +- .../dependencies/SliceCycleArchCondition.java | 2 +- .../archunit/ArchConfigurationTest.java | 2 +- .../archunit/core/domain/TestUtils.java | 7 ++- .../properties/HasThrowsClauseTest.java | 6 +++ .../importer/ClassFileImporterSlowTest.java | 51 +++++++++++-------- .../core/importer/ImportOptionsTest.java | 12 +++-- .../archunit/core/importer/TestClassFile.java | 51 +++++++++++++++++++ .../archunit/core/importer/UrlSourceTest.java | 2 + .../SliceRulePerformanceTest.java | 13 +++-- build-steps/testing/testing.gradle | 2 +- 12 files changed, 130 insertions(+), 43 deletions(-) create mode 100644 archunit/src/test/java/com/tngtech/archunit/core/importer/TestClassFile.java diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvents.java b/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvents.java index 33009dea3c..705a9bc728 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvents.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/ConditionEvents.java @@ -18,12 +18,14 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; -import java.util.SortedSet; -import java.util.TreeSet; +import com.google.common.base.Function; import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; +import com.google.common.collect.Ordering; import com.google.common.reflect.TypeToken; import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.base.Optional; @@ -94,10 +96,9 @@ public List getFailureDescriptionLines() { */ @PublicAPI(usage = ACCESS) public FailureMessages getFailureMessages() { - SortedSet result = new TreeSet<>(); - for (ConditionEvent event : getViolating()) { - result.addAll(event.getDescriptionLines()); - } + ImmutableList result = FluentIterable.from(getViolating()) + .transformAndConcat(TO_DESCRIPTION_LINES) + .toSortedList(Ordering.natural()); return new FailureMessages(result, informationAboutNumberOfViolations); } @@ -160,6 +161,13 @@ public String toString() { '}'; } + private static final Function> TO_DESCRIPTION_LINES = new Function>() { + @Override + public Iterable apply(ConditionEvent input) { + return input.getDescriptionLines(); + } + }; + private enum Type { ALLOWED, VIOLATION; diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/FailureMessages.java b/archunit/src/main/java/com/tngtech/archunit/lang/FailureMessages.java index b3faecc8f8..3cd7a2e71f 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/FailureMessages.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/FailureMessages.java @@ -15,7 +15,6 @@ */ package com.tngtech.archunit.lang; -import java.util.Collection; import java.util.List; import com.google.common.base.Predicate; @@ -31,8 +30,8 @@ public class FailureMessages extends ForwardingList { private final List failures; private final Optional informationAboutNumberOfViolations; - FailureMessages(Collection failures, Optional informationAboutNumberOfViolations) { - this.failures = ImmutableList.copyOf(failures); + FailureMessages(ImmutableList failures, Optional informationAboutNumberOfViolations) { + this.failures = failures; this.informationAboutNumberOfViolations = informationAboutNumberOfViolations; } diff --git a/archunit/src/main/java/com/tngtech/archunit/library/dependencies/SliceCycleArchCondition.java b/archunit/src/main/java/com/tngtech/archunit/library/dependencies/SliceCycleArchCondition.java index 20d3b57ccc..e6d25fc820 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/dependencies/SliceCycleArchCondition.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/dependencies/SliceCycleArchCondition.java @@ -218,7 +218,7 @@ private String createDescription(Collection edgeDescriptions) { private String createDetails(Map> descriptionsToEdges) { List details = new ArrayList<>(); for (Map.Entry> edgeWithDescription : descriptionsToEdges.entrySet()) { - details.add(String.format("Dependencies of %s", edgeWithDescription.getKey())); + details.add("Dependencies of " + edgeWithDescription.getKey()); details.addAll(dependenciesDescription(edgeWithDescription.getValue())); } return Joiner.on(System.lineSeparator()).join(details); diff --git a/archunit/src/test/java/com/tngtech/archunit/ArchConfigurationTest.java b/archunit/src/test/java/com/tngtech/archunit/ArchConfigurationTest.java index b95e565221..de16791d64 100644 --- a/archunit/src/test/java/com/tngtech/archunit/ArchConfigurationTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/ArchConfigurationTest.java @@ -231,7 +231,7 @@ public void allows_to_override_any_property_via_system_property() { assertThat(configuration.md5InClassSourcesEnabled()).as("MD5 sum in class sources enabled").isTrue(); assertThat(configuration.getProperty(customPropertyName)).as("custom property").isEqualTo("changed"); - assertThat(configuration.getSubProperties(subPropertyKeyOf(customPropertyName))).containsExactly( + assertThat(configuration.getSubProperties(subPropertyKeyOf(customPropertyName))).containsOnly( entry(subPropertyNameOf(customPropertyName), "changed"), entry(subPropertyNameOf(otherPropertyName), "other")); } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/TestUtils.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/TestUtils.java index a3d2caf133..d2cb3b82cb 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/TestUtils.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/TestUtils.java @@ -8,7 +8,6 @@ import java.util.Set; import com.google.common.base.Suppliers; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.AccessTarget.ConstructorCallTarget; @@ -43,7 +42,11 @@ public static JavaClassList javaClassList(Class... types) { @SafeVarargs public static ThrowsClause throwsClause(Class... types) { - List importedTypes = ImmutableList.copyOf(importClassesWithContext(types)); + JavaClasses classes = importClassesWithContext(types); + List importedTypes = new ArrayList<>(); + for (Class type : types) { + importedTypes.add(classes.get(type)); + } JavaMethod irrelevantOwner = importClassWithContext(Object.class).getMethod("toString"); return ThrowsClause.from(irrelevantOwner, importedTypes); } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasThrowsClauseTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasThrowsClauseTest.java index 007748548c..06e94be661 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasThrowsClauseTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasThrowsClauseTest.java @@ -11,6 +11,7 @@ import org.junit.runner.RunWith; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.equivalentTo; +import static com.tngtech.archunit.core.domain.JavaClass.namesOf; import static com.tngtech.archunit.core.domain.TestUtils.throwsClause; import static com.tngtech.archunit.core.domain.properties.HasThrowsClause.Predicates.throwsClauseContainingType; import static com.tngtech.archunit.testutil.Assertions.assertThat; @@ -78,6 +79,11 @@ private final HasThrowsClause newHasThrowsClause(final Class getThrowsClause() { return throwsClause(throwsDeclarations); } + + @Override + public String toString() { + return HasThrowsClause.class.getSimpleName() + "{ throws " + namesOf(throwsDeclarations) + "}"; + } }; } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterSlowTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterSlowTest.java index 1f265413af..db90179326 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterSlowTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterSlowTest.java @@ -4,12 +4,8 @@ import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Field; -import java.net.URL; -import java.net.URLClassLoader; import java.util.List; -import com.google.common.base.Joiner; -import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.Slow; @@ -52,12 +48,7 @@ public void imports_the_classpath() { assertThatClasses(classes).doNotContain(Rule.class); // Default does not import jars assertThatClasses(classes).doNotContain(File.class); // Default does not import JDK classes - classes = new ClassFileImporter().importClasspath(new ImportOptions().with(new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.asURI().getScheme().equals("jrt") || location.contains("java.base"); - } - })); + classes = new ClassFileImporter().importClasspath(new ImportOptions().with(importJavaBaseOrRtAndJUnitJarAndFilesOnTheClasspath())); assertThatClasses(classes).contain(ClassFileImporter.class, getClass(), Rule.class, File.class); } @@ -120,28 +111,25 @@ public void imports_duplicate_classes() throws IOException { @Test public void imports_classes_from_classpath_specified_in_manifest_file() { - String manifestClasspath = - Joiner.on(" ").join(Splitter.on(File.pathSeparator).omitEmptyStrings().split(System.getProperty(JAVA_CLASS_PATH_PROP))); + TestClassFile testClassFile = new TestClassFile().create(); + String manifestClasspath = testClassFile.getClasspathRoot().getAbsolutePath(); String jarPath = new TestJarFile() .withManifestAttribute(CLASS_PATH, manifestClasspath) .create() .getName(); - System.clearProperty(JAVA_CLASS_PATH_PROP); - // Ensure we cannot load the class through the fallback via the Classloader - Thread.currentThread().setContextClassLoader(new URLClassLoader(new URL[0], null)); - verifyCantLoadWithCurrentClasspath(getClass()); + verifyCantLoadWithCurrentClasspath(testClassFile); System.setProperty(JAVA_CLASS_PATH_PROP, jarPath); - JavaClasses javaClasses = new ClassFileImporter().importPackages(getClass().getPackage().getName()); + JavaClasses javaClasses = new ClassFileImporter().importPackages(testClassFile.getPackageName()); - assertThatClasses(javaClasses).contain(getClass()); + assertThat(javaClasses).extracting("name").contains(testClassFile.getClassName()); } - private void verifyCantLoadWithCurrentClasspath(Class clazz) { + private void verifyCantLoadWithCurrentClasspath(TestClassFile testClassFile) { try { - new ClassFileImporter().importClass(clazz); - Assert.fail(String.format("Should not have been able to load class %s with the current classpath", clazz.getName())); + new ClassFileImporter().importPackages(testClassFile.getPackageName()).get(testClassFile.getClassName()); + Assert.fail(String.format("Should not have been able to load class %s with the current classpath", testClassFile.getClassName())); } catch (RuntimeException ignored) { } } @@ -168,8 +156,27 @@ private JavaClasses importJavaBase() { return new ClassFileImporter().importClasspath(new ImportOptions().with(new ImportOption() { @Override public boolean includes(Location location) { - return location.asURI().getScheme().equals("jrt") && location.contains("java.base"); + return + // before Java 9 package like java.lang were in rt.jar + location.contains("rt.jar") || + // from Java 9 on those packages were in a JRT with name 'java.base' + (location.asURI().getScheme().equals("jrt") && location.contains("java.base")); } })); } + + private ImportOption importJavaBaseOrRtAndJUnitJarAndFilesOnTheClasspath() { + return new ImportOption() { + @Override + public boolean includes(Location location) { + if (!location.isArchive()) { + return true; + } + if (location.isJar() && (location.contains("junit") || location.contains("/rt.jar"))) { + return true; + } + return location.asURI().getScheme().equals("jrt") && location.contains("java.base"); + } + }; + } } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportOptionsTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportOptionsTest.java index aa6c1f26d3..fd5ed6ddbd 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportOptionsTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportOptionsTest.java @@ -15,7 +15,7 @@ import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; -import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.collect.Iterables.getLast; import static com.tngtech.archunit.core.importer.ImportOption.Predefined.DO_NOT_INCLUDE_ARCHIVES; import static com.tngtech.archunit.core.importer.ImportOption.Predefined.DO_NOT_INCLUDE_JARS; import static com.tngtech.archunit.core.importer.ImportOption.Predefined.DO_NOT_INCLUDE_TESTS; @@ -99,7 +99,7 @@ public void detects_Jars_correctly(ImportOption doNotIncludeJars) { .isFalse(); assertThat(doNotIncludeJars.includes(locationOf(Object.class))) .as("includes Jrt location") - .isTrue(); + .isEqualTo(!comesFromJarArchive(Object.class)); } @DataProvider @@ -122,6 +122,10 @@ public void detects_archives_correctly(ImportOption doNotIncludeArchives) { } private static Location locationOf(Class clazz) { - return getOnlyElement(Locations.ofClass(clazz)); + return getLast(Locations.ofClass(clazz)); + } + + private static boolean comesFromJarArchive(Class clazz) { + return LocationTest.urlOfClass(clazz).getProtocol().equals("jar"); } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/TestClassFile.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/TestClassFile.java new file mode 100644 index 0000000000..700f9e5747 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/TestClassFile.java @@ -0,0 +1,51 @@ +package com.tngtech.archunit.core.importer; + +import java.io.File; +import java.io.IOException; + +import com.google.common.io.Files; + +import static com.google.common.base.Preconditions.checkState; +import static com.tngtech.archunit.testutil.TestUtils.newTemporaryFolder; +import static java.nio.charset.StandardCharsets.UTF_8; +import static javax.tools.ToolProvider.getSystemJavaCompiler; + +public class TestClassFile { + private static final String PACKAGE_NAME = "com.dummy"; + private static final String SOURCE_FOLDER = PACKAGE_NAME.replace(".", File.separator) + File.separator; + private static final String CLASS_NAME = "Dummy"; + private final File classpathRoot = newTemporaryFolder(); + private final File sourceFile = new File(classpathRoot, SOURCE_FOLDER + CLASS_NAME + ".java"); + + public TestClassFile() { + } + + public TestClassFile create() { + try { + String sourceCode = String.format("package %s;public class %s {}", PACKAGE_NAME, CLASS_NAME); + + checkState(sourceFile.getParentFile().exists() || sourceFile.getParentFile().mkdirs(), + "Can't create directory %s", sourceFile.getParentFile().getAbsolutePath()); + Files.write(sourceCode, sourceFile, UTF_8); + + int result = getSystemJavaCompiler().run(null, null, null, sourceFile.getAbsolutePath()); + checkState(result == 0, "Compiler exit code should be 0, but it was " + result); + + return this; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public String getPackageName() { + return PACKAGE_NAME; + } + + public String getClassName() { + return PACKAGE_NAME + "." + CLASS_NAME; + } + + public File getClasspathRoot() { + return classpathRoot; + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/UrlSourceTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/UrlSourceTest.java index f0182bfb83..5f75985a86 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/UrlSourceTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/UrlSourceTest.java @@ -80,6 +80,7 @@ public void ignores_invalid_paths_in_class_path_property() { String classPath = createClassPathProperty(valid.toString(), "/invalid/path/because/of/" + CHARACTER_THAT_IS_HOPEFULLY_ILLEGAL_ON_EVERY_PLATFORM + "/"); System.setProperty(JAVA_CLASS_PATH_PROP, classPath); + System.clearProperty(JAVA_BOOT_PATH_PROP); assertThat(UrlSource.From.classPathSystemProperties()).containsOnly(toUrl(valid)); } @@ -142,6 +143,7 @@ public void terminates_recursively_resolving_manifest_classpaths_if_manifests_ha .create(jarTwoPath); System.setProperty(JAVA_CLASS_PATH_PROP, jarOne.getName()); + System.clearProperty(JAVA_BOOT_PATH_PROP); UrlSource urls = UrlSource.From.classPathSystemProperties(); assertThat(urls).containsOnly(toUrl(Paths.get(jarOne.getName())), toUrl(Paths.get(jarTwo.getName()))); diff --git a/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SliceRulePerformanceTest.java b/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SliceRulePerformanceTest.java index 954ff5f7b8..71c999ddcb 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SliceRulePerformanceTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SliceRulePerformanceTest.java @@ -1,9 +1,12 @@ package com.tngtech.archunit.library.dependencies; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; import com.tngtech.archunit.ArchConfiguration; import com.tngtech.archunit.Slow; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; +import com.tngtech.archunit.lang.FailureReport; import com.tngtech.archunit.library.dependencies.testexamples.completedependencygraph.ninenodes.CompleteNineNodesGraphRoot; import com.tngtech.archunit.testutil.ArchConfigurationRule; import org.junit.Rule; @@ -11,7 +14,6 @@ import org.junit.experimental.categories.Category; import static com.tngtech.archunit.library.dependencies.CycleConfiguration.MAX_NUMBER_OF_CYCLES_TO_DETECT_PROPERTY_NAME; -import static com.tngtech.archunit.library.dependencies.SliceRuleTest.countCyclesInMessage; import static com.tngtech.archunit.library.dependencies.SliceRuleTest.getNumberOfCyclesInCompleteGraph; import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; import static org.assertj.core.api.Assertions.assertThat; @@ -37,9 +39,14 @@ public void searching_for_cycles_should_terminate_reasonably_fast_for_complete_g int expectedNumberOfCycles = getNumberOfCyclesInCompleteGraph(numberOfClassesFormingCompleteGraph); ArchConfiguration.get().setProperty(MAX_NUMBER_OF_CYCLES_TO_DETECT_PROPERTY_NAME, String.valueOf(2 * expectedNumberOfCycles)); - String violations = cycleFree.evaluate(classesFormingCompleteDependencyGraph).getFailureReport().toString(); + FailureReport failureReport = cycleFree.evaluate(classesFormingCompleteDependencyGraph).getFailureReport(); - int numberOfDetectedCycles = countCyclesInMessage(violations); + int numberOfDetectedCycles = FluentIterable.from(failureReport.getDetails()).filter(new Predicate() { + @Override + public boolean apply(String input) { + return input.contains("Cycle detected: "); + } + }).size(); assertThat(numberOfDetectedCycles).as("number of cycles detected").isEqualTo(expectedNumberOfCycles); } } diff --git a/build-steps/testing/testing.gradle b/build-steps/testing/testing.gradle index 42055ffabf..e5c75af8b0 100644 --- a/build-steps/testing/testing.gradle +++ b/build-steps/testing/testing.gradle @@ -54,7 +54,7 @@ ext { assert testTask.name == 'test' || testTask.name ==~ /jdk\d+Test/: "Task must either be named 'test' or match 'jdk{x}Test'" testTask.enabled = false - def taskMinimumJdkVersion = testTask.name.replaceAll(/jdk(\d+)Test/, '$1').with { it ==~ /\d+/ ? JavaVersion.toVersion(it) : JavaVersion.VERSION_1_7 } + def taskMinimumJdkVersion = testTask.name.replaceAll(/jdk(\d+)Test/, '$1').with { it ==~ /\d+/ ? JavaVersion.toVersion(it) : proj.targetCompatibility } def additionalJdksToTest = testJdks().findAll { it.javaVersion >= taskMinimumJdkVersion } def findJavaExecutable = { jdkPath -> From 4209762a505361f64d51b0dfd20859b2862c97db Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Thu, 11 Jun 2020 16:16:07 +0200 Subject: [PATCH 014/115] deflake `Random...SyntaxTest` This test has been flaky for a while, once every `x*100` runs the test would not finish building a rule within 50 steps. So far the logic was "if number of steps is low (<= 5) then immediately accept any `ArchRule` you arrive at while traversing the syntax methods". This could still lead to no `ArchRule` being randomly hit within those 5 steps. The chance to arrive at 45 steps was never very big, nor the chance that no valid endpoint would be hit within the next 5 steps, but it still happened and the chance has increased more and more, the more complex the tree of choices got. Now once we reach a low number of steps we will actively look for any method providing `ArchRule` as a return type within the candidates instead of continue to chose random and just stop if the type matches. I do not know if this solves the problem once at for all, but for a low max step number (10) I could deterministically reproduce the failure for `RandomMembersSyntaxTest` within the first test run and with these changes the error could not be reproduced anymore. I also constructed more than 100000 random rules for this test and the failure did not reappear. Signed-off-by: Peter Gafert --- .../testutil/syntax/MethodCallChain.java | 6 +++--- .../testutil/syntax/MethodCallChainTest.java | 2 +- .../testutil/syntax/MethodChoiceStrategy.java | 21 +++++++++++++++---- .../testutil/syntax/RandomSyntaxTestBase.java | 12 ++++++----- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodCallChain.java b/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodCallChain.java index 5a288da40a..1976530c6a 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodCallChain.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodCallChain.java @@ -18,7 +18,7 @@ class MethodCallChain { MethodCallChain(MethodChoiceStrategy methodChoiceStrategy, TypedValue typedValue) { this.methodChoiceStrategy = checkNotNull(methodChoiceStrategy); currentValue = checkNotNull(typedValue); - nextMethodCandidate = methodChoiceStrategy.choose(typedValue.getType()); + nextMethodCandidate = methodChoiceStrategy.choose(typedValue.getType(), false); } TypedValue getCurrentValue() { @@ -33,11 +33,11 @@ boolean hasAnotherMethodCandidate() { return nextMethodCandidate.isPresent(); } - void invokeNextMethodCandidate(Parameters parameters) { + void invokeNextMethodCandidate(Parameters parameters, boolean tryToTerminate) { PropagatedType nextType = currentValue.resolveType(nextMethodCandidate.get().getGenericReturnType()); Object nextValue = invoke(nextMethodCandidate.get(), currentValue.getValue(), parameters.getValues()); currentValue = validate(new TypedValue(nextType, nextValue)); - nextMethodCandidate = methodChoiceStrategy.choose(currentValue.getType()); + nextMethodCandidate = methodChoiceStrategy.choose(currentValue.getType(), tryToTerminate); } private TypedValue validate(TypedValue value) { diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodCallChainTest.java b/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodCallChainTest.java index f3a63bac56..f60e22f7df 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodCallChainTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodCallChainTest.java @@ -115,7 +115,7 @@ private MethodCallChain createCallChainStart(Class startInterface, T star } private void invokeNext(MethodCallChain callChain) { - callChain.invokeNextMethodCandidate(new Parameters(Collections.emptyList())); + callChain.invokeNextMethodCandidate(new Parameters(Collections.emptyList()), false); } private static class CallChainTestCase { diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodChoiceStrategy.java b/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodChoiceStrategy.java index 6e7cee59a3..655258ca47 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodChoiceStrategy.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/MethodChoiceStrategy.java @@ -48,11 +48,24 @@ public boolean apply(Method input) { }; } - Optional choose(PropagatedType type) { + Optional choose(PropagatedType type, boolean tryToTerminate) { List methods = getPossibleMethodCandidates(type.getRawType()); - return !methods.isEmpty() - ? Optional.of(methods.get(random.nextInt(methods.size()))) - : Optional.absent(); + if (methods.isEmpty()) { + return Optional.absent(); + } + + return tryToTerminate + ? findMethodWithReturnType(methods, ArchRule.class).or(Optional.of(methods.iterator().next())) + : Optional.of(methods.get(random.nextInt(methods.size()))); + } + + private Optional findMethodWithReturnType(List methods, Class returnType) { + for (Method method : methods) { + if (returnType.isAssignableFrom(method.getReturnType())) { + return Optional.of(method); + } + } + return Optional.absent(); } private List getPossibleMethodCandidates(Class clazz) { diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/RandomSyntaxTestBase.java b/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/RandomSyntaxTestBase.java index ba0e658848..8cd348bc72 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/RandomSyntaxTestBase.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/syntax/RandomSyntaxTestBase.java @@ -217,10 +217,12 @@ LastStep continueSteps(int currentStepCount, int maxSteps) { throw new IllegalStateException("Creating rule was not finished within " + maxSteps + " steps"); } - methodCallChain.invokeNextMethodCandidate(parameters); + int stepsLeft = maxSteps - currentStepCount; + boolean lowNumberOfStepsLeft = stepsLeft <= LOW_NUMBER_OF_LEFT_STEPS; + methodCallChain.invokeNextMethodCandidate(parameters, lowNumberOfStepsLeft); boolean shouldContinue = methodCallChain.hasAnotherMethodCandidate() - && shouldContinue(methodCallChain.getCurrentValue(), maxSteps - currentStepCount); + && shouldContinue(methodCallChain.getCurrentValue(), lowNumberOfStepsLeft); Step nextStep = shouldContinue ? new PartialStep(expectedDescription, methodCallChain) : new LastStep(expectedDescription, methodCallChain); @@ -229,11 +231,11 @@ LastStep continueSteps(int currentStepCount, int maxSteps) { return nextStep.continueSteps(currentStepCount + 1, maxSteps); } - private boolean shouldContinue(TypedValue nextValue, int stepsLeft) { + private boolean shouldContinue(TypedValue nextValue, boolean lowNumberOfStepsLeft) { if (!ArchRule.class.isAssignableFrom(nextValue.getRawType())) { return true; } - return random.nextBoolean() && stepsLeft > LOW_NUMBER_OF_LEFT_STEPS; + return random.nextBoolean() && !lowNumberOfStepsLeft; } public String getDescription() { @@ -471,7 +473,7 @@ private boolean firstParameterTypeMatches(List> parameterTypes) { } } - private abstract class CallCodeUnitParametersProvider extends SpecificParametersProvider { + private abstract static class CallCodeUnitParametersProvider extends SpecificParametersProvider { private final CanHandlePredicate predicate; CallCodeUnitParametersProvider(CanHandlePredicate predicate) { From 97e396a515cba46c9e0e64e8a527cb3254e93bfb Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Thu, 11 Jun 2020 23:31:12 +0200 Subject: [PATCH 015/115] use `ArchConfigurationRule` instead of manual `reset()` Signed-off-by: Peter Gafert --- .../archunit/core/importer/ClassFileImporterTest.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java index b2856d004f..6e66fb610c 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java @@ -172,6 +172,7 @@ import com.tngtech.archunit.core.importer.testexamples.simplenames.SimpleNameExamples; import com.tngtech.archunit.core.importer.testexamples.specialtargets.ClassCallingSpecialTarget; import com.tngtech.archunit.core.importer.testexamples.syntheticimport.ClassWithSynthetics; +import com.tngtech.archunit.testutil.ArchConfigurationRule; import com.tngtech.archunit.testutil.LogTestRule; import com.tngtech.archunit.testutil.OutsideOfClassPathRule; import com.tngtech.java.junit.dataprovider.DataProvider; @@ -180,7 +181,6 @@ import org.apache.logging.log4j.Level; import org.assertj.core.api.Condition; import org.assertj.core.util.Objects; -import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -242,11 +242,8 @@ public class ClassFileImporterTest { public final LogTestRule logTest = new LogTestRule(); @Rule public final IndependentClasspathRule independentClasspathRule = new IndependentClasspathRule(); - - @After - public void tearDown() { - ArchConfiguration.get().reset(); - } + @Rule + public final ArchConfigurationRule archConfigurationRule = new ArchConfigurationRule(); @Test public void imports_simple_package() throws Exception { From c395bc282b6c52feb0165a1657086d711547829f Mon Sep 17 00:00:00 2001 From: ldebruijn Date: Tue, 9 Jun 2020 23:12:49 +0200 Subject: [PATCH 016/115] Prevent excessive logging when supplying system properties by only logging if the system properties have changed Signed-off-by: ldebruijn --- .../tngtech/archunit/ArchConfiguration.java | 33 ++++++++++++------- .../archunit/ArchConfigurationTest.java | 18 ++++++---- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/ArchConfiguration.java b/archunit/src/main/java/com/tngtech/archunit/ArchConfiguration.java index 626426ab60..6cb161cd08 100644 --- a/archunit/src/main/java/com/tngtech/archunit/ArchConfiguration.java +++ b/archunit/src/main/java/com/tngtech/archunit/ArchConfiguration.java @@ -273,15 +273,16 @@ private static class PropertiesOverwritableBySystemProperties { ENABLE_MD5_IN_CLASS_SOURCES, Boolean.FALSE.toString() )); - private final Properties properties = createProperties(PROPERTY_DEFAULTS); + private final Properties baseProperties = createProperties(PROPERTY_DEFAULTS); + private final Properties overwrittenProperties = new Properties(); void clear() { - properties.clear(); - properties.putAll(PROPERTY_DEFAULTS); + replaceProperties(baseProperties, PROPERTY_DEFAULTS); + overwrittenProperties.clear(); } void load(InputStream inputStream) throws IOException { - properties.load(inputStream); + baseProperties.load(inputStream); } Set stringPropertyNames() { @@ -301,23 +302,33 @@ String getProperty(String propertyName, String defaultValue) { } void setProperty(String propertyName, String value) { - properties.setProperty(propertyName, value); + baseProperties.setProperty(propertyName, value); } void remove(String propertyName) { - properties.remove(propertyName); + baseProperties.remove(propertyName); } Properties getMergedProperties() { - Properties result = createProperties(this.properties); - Properties overwritten = getSubProperties("archunit", System.getProperties()); - if (!overwritten.isEmpty()) { - LOG.info("Merging properties: The following properties have been overwritten by system properties: {}", overwritten); + Properties result = createProperties(baseProperties); + Properties currentlyOverwritten = getSubProperties("archunit", System.getProperties()); + result.putAll(currentlyOverwritten); + + if (!overwrittenProperties.equals(currentlyOverwritten)) { + replaceProperties(overwrittenProperties, currentlyOverwritten); + if (!currentlyOverwritten.isEmpty()) { + LOG.info("Merging properties: The following properties have been overwritten by system properties: {}", currentlyOverwritten); + } } - result.putAll(overwritten); + return result; } + private static void replaceProperties(Properties properties, Properties newProperties) { + properties.clear(); + properties.putAll(newProperties); + } + private static Properties createProperties(Map entries) { Properties result = new Properties(); result.putAll(entries); diff --git a/archunit/src/test/java/com/tngtech/archunit/ArchConfigurationTest.java b/archunit/src/test/java/com/tngtech/archunit/ArchConfigurationTest.java index de16791d64..3ace2f8c96 100644 --- a/archunit/src/test/java/com/tngtech/archunit/ArchConfigurationTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/ArchConfigurationTest.java @@ -1,11 +1,5 @@ package com.tngtech.archunit; -import java.io.File; -import java.io.FileOutputStream; -import java.lang.reflect.Constructor; -import java.util.Arrays; -import java.util.Properties; - import com.tngtech.archunit.testutil.SystemPropertiesRule; import org.junit.After; import org.junit.Before; @@ -13,6 +7,12 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import java.io.File; +import java.io.FileOutputStream; +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.Properties; + import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.tngtech.archunit.testutil.Assertions.assertThat; @@ -234,6 +234,12 @@ public void allows_to_override_any_property_via_system_property() { assertThat(configuration.getSubProperties(subPropertyKeyOf(customPropertyName))).containsOnly( entry(subPropertyNameOf(customPropertyName), "changed"), entry(subPropertyNameOf(otherPropertyName), "other")); + + System.clearProperty("archunit." + ArchConfiguration.ENABLE_MD5_IN_CLASS_SOURCES); + System.clearProperty("archunit." + customPropertyName); + + assertThat(configuration.md5InClassSourcesEnabled()).as("MD5 sum in class sources enabled").isFalse(); + assertThat(configuration.getProperty(customPropertyName)).as("custom property").isEqualTo("original"); } @Test From 23a280ca28d07d9b8b4fc4a813a2346eff39a4c0 Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Fri, 12 Jun 2020 00:01:58 +0200 Subject: [PATCH 017/115] only check if MD5 is enabled once per import It does not make sense to continuously read `md5EnabledInClassSources` for every single class. Instead we now read the configuration once for all the classes to import and pass the configured value through with the source URI. This means that between separate runs of `ClassFileImporter.import...()` the property can be changed, but not while the import is running. Signed-off-by: Peter Gafert --- .../domain/DomainObjectCreationContext.java | 4 +-- .../tngtech/archunit/core/domain/Source.java | 16 +++------ .../core/importer/ClassFileProcessor.java | 12 ++++--- .../core/importer/DomainBuilders.java | 11 +++--- .../core/importer/JavaClassProcessor.java | 13 ++++--- .../core/importer/SourceDescriptor.java | 36 +++++++++++++++++++ .../archunit/core/domain/SourceTest.java | 30 +++++++++------- .../archunit/core/domain/TestUtils.java | 13 ++++++- 8 files changed, 91 insertions(+), 44 deletions(-) create mode 100644 archunit/src/main/java/com/tngtech/archunit/core/importer/SourceDescriptor.java diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java index 7b9075505e..56d5ddaaa7 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java @@ -135,8 +135,8 @@ public static JavaEnumConstant createJavaEnumConstant(JavaEnumConstantBuilder bu return new JavaEnumConstant(builder); } - public static Source createSource(URI uri, Optional sourceFileName) { - return new Source(uri, sourceFileName); + public static Source createSource(URI uri, Optional sourceFileName, boolean md5InClassSourcesEnabled) { + return new Source(uri, sourceFileName, md5InClassSourcesEnabled); } public static ThrowsClause createThrowsClause(CODE_UNIT codeUnit, List types) { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/Source.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/Source.java index ba4e0ce61f..726a562f22 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/Source.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/Source.java @@ -48,10 +48,10 @@ public class Source { private final Optional fileName; private final Md5sum md5sum; - Source(URI uri, Optional fileName) { + Source(URI uri, Optional fileName, boolean md5InClassSourcesEnabled) { this.uri = checkNotNull(uri); this.fileName = checkNotNull(fileName); - md5sum = Md5sum.of(uri); + md5sum = md5InClassSourcesEnabled ? Md5sum.of(uri) : Md5sum.DISABLED; } @PublicAPI(usage = ACCESS) @@ -167,21 +167,13 @@ private static MessageDigest getMd5Digest() { } } - static Md5sum of(byte[] input) { + private static Md5sum of(URI uri) { if (MD5_DIGEST == null) { return NOT_SUPPORTED; } - return ArchConfiguration.get().md5InClassSourcesEnabled() ? new Md5sum(input, MD5_DIGEST) : DISABLED; - } - - private static Md5sum of(URI uri) { - if (!ArchConfiguration.get().md5InClassSourcesEnabled()) { - return DISABLED; - } - Optional bytesFromUri = read(uri); - return bytesFromUri.isPresent() ? Md5sum.of(bytesFromUri.get()) : UNDETERMINED; + return bytesFromUri.isPresent() ? new Md5sum(bytesFromUri.get(), MD5_DIGEST) : UNDETERMINED; } private static Optional read(URI uri) { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java index 401eb09d1e..26f1d9dd35 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java @@ -19,6 +19,7 @@ import java.net.URI; import java.util.Set; +import com.tngtech.archunit.ArchConfiguration; import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; @@ -43,6 +44,7 @@ class ClassFileProcessor { static final int ASM_API_VERSION = ASM7; + private final boolean md5InClassSourcesEnabled = ArchConfiguration.get().md5InClassSourcesEnabled(); private final ClassResolver.Factory classResolverFactory = new ClassResolver.Factory(); JavaClasses process(ClassFileSource source) { @@ -52,7 +54,7 @@ JavaClasses process(ClassFileSource source) { for (ClassFileLocation location : source) { try (InputStream s = location.openStream()) { JavaClassProcessor javaClassProcessor = - new JavaClassProcessor(location.getUri(), classDetailsRecorder, accessHandler); + new JavaClassProcessor(new SourceDescriptor(location.getUri(), md5InClassSourcesEnabled), classDetailsRecorder, accessHandler); new ClassReader(s).accept(javaClassProcessor, 0); importRecord.addAll(javaClassProcessor.createJavaClass().asSet()); } catch (Exception e) { @@ -168,21 +170,23 @@ private > BUILDER filled(BU private ClassResolver getClassResolver(ClassDetailsRecorder classDetailsRecorder) { ClassResolver classResolver = classResolverFactory.create(); - classResolver.setClassUriImporter(new UriImporterOfProcessor(classDetailsRecorder)); + classResolver.setClassUriImporter(new UriImporterOfProcessor(classDetailsRecorder, md5InClassSourcesEnabled)); return classResolver; } private static class UriImporterOfProcessor implements ClassUriImporter { private final DeclarationHandler declarationHandler; + private final boolean md5InClassSourcesEnabled; - UriImporterOfProcessor(DeclarationHandler declarationHandler) { + UriImporterOfProcessor(DeclarationHandler declarationHandler, boolean md5InClassSourcesEnabled) { this.declarationHandler = declarationHandler; + this.md5InClassSourcesEnabled = md5InClassSourcesEnabled; } @Override public Optional tryImport(URI uri) { try (InputStream inputStream = uri.toURL().openStream()) { - JavaClassProcessor classProcessor = new JavaClassProcessor(uri, declarationHandler); + JavaClassProcessor classProcessor = new JavaClassProcessor(new SourceDescriptor(uri, md5InClassSourcesEnabled), declarationHandler); new ClassReader(inputStream).accept(classProcessor, 0); return classProcessor.createJavaClass(); } catch (Exception e) { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java index f2340d416c..96693399fc 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java @@ -15,7 +15,6 @@ */ package com.tngtech.archunit.core.importer; -import java.net.URI; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; @@ -318,7 +317,7 @@ JavaConstructor construct(JavaConstructorBuilder builder, ClassesByTypeName impo @Internal public static final class JavaClassBuilder { - private Optional sourceURI = Optional.absent(); + private Optional sourceDescriptor = Optional.absent(); private Optional sourceFileName = Optional.absent(); private JavaType javaType; private boolean isInterface; @@ -330,8 +329,8 @@ public static final class JavaClassBuilder { JavaClassBuilder() { } - JavaClassBuilder withSourceUri(URI sourceUri) { - this.sourceURI = Optional.of(sourceUri); + JavaClassBuilder withSourceDescriptor(SourceDescriptor sourceDescriptor) { + this.sourceDescriptor = Optional.of(sourceDescriptor); return this; } @@ -380,7 +379,9 @@ JavaClass build() { } public Optional getSource() { - return sourceURI.isPresent() ? Optional.of(createSource(sourceURI.get(), sourceFileName)) : Optional.absent(); + return sourceDescriptor.isPresent() + ? Optional.of(createSource(sourceDescriptor.get().getUri(), sourceFileName, sourceDescriptor.get().isMd5InClassSourcesEnabled())) + : Optional.absent(); } public JavaType getJavaType() { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java index f02f30a956..ecb6396651 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java @@ -16,7 +16,6 @@ package com.tngtech.archunit.core.importer; import java.lang.reflect.Array; -import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -69,18 +68,18 @@ class JavaClassProcessor extends ClassVisitor { private DomainBuilders.JavaClassBuilder javaClassBuilder; private final Set annotations = new HashSet<>(); - private final URI sourceURI; + private final SourceDescriptor sourceDescriptor; private final DeclarationHandler declarationHandler; private final AccessHandler accessHandler; private String className; - JavaClassProcessor(URI sourceURI, DeclarationHandler declarationHandler) { - this(sourceURI, declarationHandler, NO_OP); + JavaClassProcessor(SourceDescriptor sourceDescriptor, DeclarationHandler declarationHandler) { + this(sourceDescriptor, declarationHandler, NO_OP); } - JavaClassProcessor(URI sourceURI, DeclarationHandler declarationHandler, AccessHandler accessHandler) { + JavaClassProcessor(SourceDescriptor sourceDescriptor, DeclarationHandler declarationHandler, AccessHandler accessHandler) { super(ASM_API_VERSION); - this.sourceURI = sourceURI; + this.sourceDescriptor = sourceDescriptor; this.declarationHandler = declarationHandler; this.accessHandler = accessHandler; } @@ -112,7 +111,7 @@ public void visit(int version, int access, String name, String signature, String LOG.trace("Found superclass {} on class '{}'", superClassName.orNull(), name); javaClassBuilder = new DomainBuilders.JavaClassBuilder() - .withSourceUri(sourceURI) + .withSourceDescriptor(sourceDescriptor) .withType(javaType) .withInterface(opCodeForInterfaceIsPresent) .withEnum(opCodeForEnumIsPresent) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/SourceDescriptor.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/SourceDescriptor.java new file mode 100644 index 0000000000..feca31436d --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/SourceDescriptor.java @@ -0,0 +1,36 @@ +/* + * Copyright 2014-2020 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.core.importer; + +import java.net.URI; + +class SourceDescriptor { + private final URI sourceUri; + private final boolean md5InClassSourcesEnabled; + + SourceDescriptor(URI sourceUri, boolean md5InClassSourcesEnabled) { + this.sourceUri = sourceUri; + this.md5InClassSourcesEnabled = md5InClassSourcesEnabled; + } + + URI getUri() { + return sourceUri; + } + + boolean isMd5InClassSourcesEnabled() { + return md5InClassSourcesEnabled; + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/SourceTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/SourceTest.java index d7c1b52cbd..868a8722f5 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/SourceTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/SourceTest.java @@ -28,6 +28,7 @@ import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.java.junit.dataprovider.DataProviders.$; import static com.tngtech.java.junit.dataprovider.DataProviders.$$; +import static java.nio.charset.StandardCharsets.UTF_8; @RunWith(DataProviderRunner.class) public class SourceTest { @@ -38,10 +39,10 @@ public void tearDown() { @Test public void source_file_name() throws URISyntaxException { - Source source = new Source(urlOf(Object.class).toURI(), Optional.of("SomeClass.java")); + Source source = new Source(urlOf(Object.class).toURI(), Optional.of("SomeClass.java"), false); assertThat(source.getFileName()).as("source file name").contains("SomeClass.java"); - source = new Source(urlOf(Object.class).toURI(), Optional.absent()); + source = new Source(urlOf(Object.class).toURI(), Optional.absent(), false); assertThat(source.getFileName()).as("source file name").isAbsent(); } @@ -96,7 +97,7 @@ public static Object[][] equalMd5Sums() { return $$( $(Md5sum.UNDETERMINED, Md5sum.UNDETERMINED), $(Md5sum.NOT_SUPPORTED, Md5sum.NOT_SUPPORTED), - $(Md5sum.of("anything".getBytes()), Md5sum.of("anything".getBytes()))); + $(md5sumOf("anything"), md5sumOf("anything"))); } @Test @@ -111,8 +112,8 @@ public static List> unequalMd5Sums() { return createUnequalTestCasesFor( Md5sum.UNDETERMINED, Md5sum.NOT_SUPPORTED, - Md5sum.of("anything".getBytes()), - Md5sum.of("totallyDifferent".getBytes())); + md5sumOf("anything"), + md5sumOf("totallyDifferent")); } private static List> createUnequalTestCasesFor(Md5sum... md5sums) { @@ -148,13 +149,12 @@ public void compensates_error_on_md5_calculation() throws Exception { } @Test - public void disables_md5_calculation_via_config() throws Exception { - ArchConfiguration.get().setMd5InClassSourcesEnabled(false); + public void disables_md5_calculation_via_parameter() throws Exception { + Source source = new Source(urlOf(getClass()).toURI(), Optional.of("any.java"), false); + assertThat(source.getMd5sum()).isEqualTo(Md5sum.DISABLED); - assertThat(Md5sum.of("any".getBytes())).isEqualTo(Md5sum.DISABLED); - - // NOTE: This tests that URIs are note resolved, which costs performance, if it would be resolved, we would get UNDETERMINED - assertThat(newSource(new URI("bummer")).getMd5sum()).isEqualTo(Md5sum.DISABLED); + source = new Source(urlOf(getClass()).toURI(), Optional.of("any.java"), true); + assertThat(source.getMd5sum().asBytes()).isEqualTo(expectedMd5BytesAt(source.getUri().toURL())); } private Source newSource(URL url) throws URISyntaxException { @@ -162,7 +162,11 @@ private Source newSource(URL url) throws URISyntaxException { } private Source newSource(URI uri) { - return new Source(uri, Optional.absent()); + return new Source(uri, Optional.absent(), true); + } + + private static Md5sum md5sumOf(String data) { + return TestUtils.md5sumOf(data.getBytes(UTF_8)); } private static byte[] expectedMd5BytesAt(URL url) throws IOException, NoSuchAlgorithmException { @@ -186,4 +190,4 @@ public static URL urlOf(Class clazz) { return checkNotNull(clazz.getResource("/" + clazz.getName().replace('.', '/') + ".class"), "Can't determine url of %s", clazz.getName()); } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/TestUtils.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/TestUtils.java index d2cb3b82cb..95637ad341 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/TestUtils.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/TestUtils.java @@ -1,5 +1,7 @@ package com.tngtech.archunit.core.domain; +import java.io.File; +import java.io.IOException; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; @@ -9,7 +11,9 @@ import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableSet; +import com.google.common.io.Files; import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.domain.AccessTarget.ConstructorCallTarget; import com.tngtech.archunit.core.domain.AccessTarget.FieldAccessTarget; import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; @@ -26,6 +30,7 @@ import static com.tngtech.archunit.core.importer.ImportTestUtils.newFieldAccess; import static com.tngtech.archunit.core.importer.ImportTestUtils.newMethodCall; import static com.tngtech.archunit.testutil.ReflectionTestUtils.getHierarchy; +import static org.assertj.core.util.Files.newTemporaryFile; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -76,7 +81,13 @@ static ImportedContext withinImportedClasses(Class... contextClasses) { } public static Md5sum md5sumOf(byte[] bytes) { - return Md5sum.of(bytes); + File file = newTemporaryFile(); + try { + Files.write(bytes, file); + return new Source(file.toURI(), Optional.absent(), true).getMd5sum(); + } catch (IOException e) { + throw new RuntimeException(e); + } } public static JavaClass importClassWithContext(Class owner) { From fe992a6956b64868abd16beca33f228a626bab1d Mon Sep 17 00:00:00 2001 From: Roland Weisleder Date: Mon, 15 Jun 2020 06:35:06 +0200 Subject: [PATCH 018/115] Add note about JUnit's `@Disabled` to documentation From now on, users should be able to find `@ArchIgnore` if they search for one of "skip", "disable" or "ignore". Signed-off-by: Roland Weisleder --- docs/userguide/009_JUnit_Support.adoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/userguide/009_JUnit_Support.adoc b/docs/userguide/009_JUnit_Support.adoc index a1010ee72c..01c48bac18 100644 --- a/docs/userguide/009_JUnit_Support.adoc +++ b/docs/userguide/009_JUnit_Support.adoc @@ -156,6 +156,9 @@ public class ArchitectureTest { } ---- +Note for users of JUnit 5: the annotation `@Disabled` has no effect here. +Instead, `@ArchIgnore` should be used. + ==== Grouping Rules Often a project might end up with different categories of rules, for example "service rules" From 61e12fd8f80ecae0f8c26405a8be3b07d0588fde Mon Sep 17 00:00:00 2001 From: Roland Weisleder Date: Wed, 17 Jun 2020 22:02:30 +0200 Subject: [PATCH 019/115] Fix example output for configured JDK in Contributing Guide Signed-off-by: Roland Weisleder --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a4ba746a3..d006179a12 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ Windows users should use `gradlew.bat` instead. ``` $ cd /path/to/git/clone/of/ArchUnit $ ./gradlew showJdkVersion -Configured JDK: 1.9 +Configured JDK: 14 $ ./gradlew build ``` From 5d70d75872552dde34edf8240c57edced3425f9f Mon Sep 17 00:00:00 2001 From: "Sean C. Sullivan" Date: Sat, 13 Jun 2020 08:39:34 -0700 Subject: [PATCH 020/115] add method: CompositeArchRule.of(Iterable) Signed-off-by: Sean C. Sullivan Signed-off-by: Peter Gafert --- .../tngtech/archunit/lang/CompositeArchRule.java | 15 +++++++++++++++ .../archunit/lang/CompositeArchRuleTest.java | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/CompositeArchRule.java b/archunit/src/main/java/com/tngtech/archunit/lang/CompositeArchRule.java index b71fe0eb43..85ccefdd4d 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/CompositeArchRule.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/CompositeArchRule.java @@ -15,6 +15,7 @@ */ package com.tngtech.archunit.lang; +import java.util.Iterator; import java.util.List; import com.google.common.collect.ImmutableList; @@ -43,6 +44,20 @@ public static CompositeArchRule of(ArchRule rule) { return priority(MEDIUM).of(rule); } + @PublicAPI(usage = ACCESS) + public static CompositeArchRule of(Iterable rules) { + Iterator iterator = rules.iterator(); + if (!iterator.hasNext()) { + throw new IllegalArgumentException("Iterable must be non-empty"); + } + + CompositeArchRule composite = of(iterator.next()); + while (iterator.hasNext()) { + composite = composite.and(iterator.next()); + } + return composite; + } + @PublicAPI(usage = ACCESS) public static Creator priority(Priority priority) { return new Creator(priority); diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/CompositeArchRuleTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/CompositeArchRuleTest.java index 6546b6a51c..d44fe357db 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/CompositeArchRuleTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/CompositeArchRuleTest.java @@ -1,5 +1,7 @@ package com.tngtech.archunit.lang; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.java.junit.dataprovider.DataProvider; @@ -8,6 +10,9 @@ import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Iterator; +import java.util.List; + import static com.tngtech.archunit.core.domain.TestUtils.importClasses; import static com.tngtech.archunit.lang.Priority.HIGH; import static com.tngtech.archunit.lang.Priority.MEDIUM; @@ -40,6 +45,16 @@ public void rules_are_ANDed(ArchRule first, ArchRule second, boolean expectedSat assertPriority(result.getFailureReport().toString(), MEDIUM); } + @Test + @UseDataProvider("rules_to_AND") + public void archRuleCollection(ArchRule first, ArchRule second, boolean expectedSatisfied) { + List ruleCollection = ImmutableList.of(first, second); + EvaluationResult result = CompositeArchRule.of(ruleCollection).evaluate(importClasses(getClass())); + + assertThat(result.hasViolation()).as("result has violation").isEqualTo(!expectedSatisfied); + assertPriority(result.getFailureReport().toString(), MEDIUM); + } + @Test public void description_is_modified_correctly() { ArchRule input = classes().should().bePublic(); From a230af3a72d84f76bf4e17fc3e551b9d143de6e7 Mon Sep 17 00:00:00 2001 From: Roland Weisleder Date: Thu, 2 Jul 2020 17:46:01 +0200 Subject: [PATCH 021/115] Add JavaClass.tryGetConstructor(..) methods Before this commit, JavaClass had the methods * getField(..) and tryGetField(..) * getMethod(..) and tryGetMethod(..) * getConstructor(..) This commit adds the equivalent tryGetConstructor(..) methods. Issue #386 Signed-off-by: Roland Weisleder --- .../archunit/core/domain/JavaClass.java | 66 +++++++++++++++++++ .../archunit/core/domain/JavaClassTest.java | 13 ++++ .../core/importer/ClassFileImporterTest.java | 11 +++- 3 files changed, 88 insertions(+), 2 deletions(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java index 5c7022d1e3..36f520ee85 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java @@ -673,11 +673,18 @@ public Set getAllFields() { return allFields.get(); } + /** + * @return The field with the given name. + * @throws IllegalArgumentException If this class does not have such a field. + */ @PublicAPI(usage = ACCESS) public JavaField getField(String name) { return tryGetField(name).getOrThrow(new IllegalArgumentException("No field with name '" + name + " in class " + getName())); } + /** + * @return The field with the given name, if this class has such a field, otherwise {@link Optional#absent()}. + */ @PublicAPI(usage = ACCESS) public Optional tryGetField(String name) { for (JavaField field : fields) { @@ -748,31 +755,53 @@ private Optional tryFindMatchingCodeUnit(Set code return Optional.absent(); } + /** + * @return The method with the given name and with zero parameters. + * @throws IllegalArgumentException If this class does not have such a method. + */ @PublicAPI(usage = ACCESS) public JavaMethod getMethod(String name) { return findMatchingCodeUnit(methods, name, Collections.emptyList()); } + /** + * @return The method with the given name and the given parameter types. + * @throws IllegalArgumentException If this class does not have such a method. + */ @PublicAPI(usage = ACCESS) public JavaMethod getMethod(String name, Class... parameters) { return findMatchingCodeUnit(methods, name, namesOf(parameters)); } + /** + * Same as {@link #getMethod(String, Class[])}, but with parameter signature specified as fully qualified class names. + */ @PublicAPI(usage = ACCESS) public JavaMethod getMethod(String name, String... parameters) { return findMatchingCodeUnit(methods, name, ImmutableList.copyOf(parameters)); } + /** + * @return The method with the given name and with zero parameters, + * if this class has such a method, otherwise {@link Optional#absent()}. + */ @PublicAPI(usage = ACCESS) public Optional tryGetMethod(String name) { return tryFindMatchingCodeUnit(methods, name, Collections.emptyList()); } + /** + * @return The method with the given name and the given parameter types, + * if this class has such a method, otherwise {@link Optional#absent()}. + */ @PublicAPI(usage = ACCESS) public Optional tryGetMethod(String name, Class... parameters) { return tryFindMatchingCodeUnit(methods, name, namesOf(parameters)); } + /** + * Same as {@link #tryGetMethod(String, Class[])}, but with parameter signature specified as fully qualified class names. + */ @PublicAPI(usage = ACCESS) public Optional tryGetMethod(String name, String... parameters) { return tryFindMatchingCodeUnit(methods, name, ImmutableList.copyOf(parameters)); @@ -789,21 +818,58 @@ public Set getAllMethods() { return allMethods.get(); } + /** + * @return The constructor with zero parameters. + * @throws IllegalArgumentException If this class does not have such a constructor. + */ @PublicAPI(usage = ACCESS) public JavaConstructor getConstructor() { return findMatchingCodeUnit(constructors, CONSTRUCTOR_NAME, Collections.emptyList()); } + /** + * @return The constructor with the given parameter types. + * @throws IllegalArgumentException If this class does not have a constructor with the given parameter types. + */ @PublicAPI(usage = ACCESS) public JavaConstructor getConstructor(Class... parameters) { return findMatchingCodeUnit(constructors, CONSTRUCTOR_NAME, namesOf(parameters)); } + /** + * Same as {@link #getConstructor(Class[])}, but with parameter signature specified as full class names. + */ @PublicAPI(usage = ACCESS) public JavaConstructor getConstructor(String... parameters) { return findMatchingCodeUnit(constructors, CONSTRUCTOR_NAME, ImmutableList.copyOf(parameters)); } + /** + * @return The constructor with zero parameters, + * if this class has such a constructor, otherwise {@link Optional#absent()}. + */ + @PublicAPI(usage = ACCESS) + public Optional tryGetConstructor() { + return tryFindMatchingCodeUnit(constructors, CONSTRUCTOR_NAME, Collections.emptyList()); + } + + /** + * @return The constructor with the given parameter types, + * if this class has such a constructor, otherwise {@link Optional#absent()}. + */ + @PublicAPI(usage = ACCESS) + public Optional tryGetConstructor(Class... parameters) { + return tryFindMatchingCodeUnit(constructors, CONSTRUCTOR_NAME, namesOf(parameters)); + } + + /** + * Same as {@link #tryGetConstructor(Class[])}, but with parameter signature specified as fully qualified class names. + */ + @PublicAPI(usage = ACCESS) + public Optional tryGetConstructor(String... parameters) { + return tryFindMatchingCodeUnit(constructors, CONSTRUCTOR_NAME, ImmutableList.copyOf(parameters)); + } + @PublicAPI(usage = ACCESS) public Set getConstructors() { return constructors; diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java index 5384dc0179..190e1fce00 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java @@ -160,6 +160,19 @@ public void finds_constructors() { assertThat(javaClass.getConstructors()).is(containing(codeUnitWithSignature(CONSTRUCTOR_NAME, int.class, Object[].class))); } + @Test + public void reports_non_existing_members_as_absent() { + JavaClass javaClass = importClassWithContext(ParentWithFieldAndMethod.class); + + assertThat(javaClass.tryGetField("notthere")).isAbsent(); + assertThat(javaClass.tryGetMethod("notthere")).isAbsent(); + assertThat(javaClass.tryGetMethod("notthere", Object.class)).isAbsent(); + assertThat(javaClass.tryGetMethod("notthere", Object.class.getName())).isAbsent(); + assertThat(javaClass.tryGetConstructor()).isAbsent(); + assertThat(javaClass.tryGetConstructor(String.class)).isAbsent(); + assertThat(javaClass.tryGetConstructor(String.class.getName())).isAbsent(); + } + @Test public void anonymous_class_has_package_of_declaring_class() { Serializable input = new Serializable() { diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java index 6e66fb610c..a3f4498ee0 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java @@ -979,17 +979,24 @@ public void imports_simple_constructors_with_correct_parameters() throws Excepti JavaClass clazz = classesIn("testexamples/constructorimport").get(ClassWithSimpleConstructors.class); assertThat(clazz.getConstructors()).as("Constructors").hasSize(3); - assertThat(clazz.getConstructor()).isEquivalentTo(ClassWithSimpleConstructors.class.getDeclaredConstructor()); + + Constructor expectedConstructor = ClassWithSimpleConstructors.class.getDeclaredConstructor(); + assertThat(clazz.getConstructor()).isEquivalentTo(expectedConstructor); + assertThat(clazz.tryGetConstructor().get()).isEquivalentTo(expectedConstructor); Class[] parameterTypes = {Object.class}; - Constructor expectedConstructor = ClassWithSimpleConstructors.class.getDeclaredConstructor(parameterTypes); + expectedConstructor = ClassWithSimpleConstructors.class.getDeclaredConstructor(parameterTypes); assertThat(clazz.getConstructor(parameterTypes)).isEquivalentTo(expectedConstructor); assertThat(clazz.getConstructor(Objects.namesOf(parameterTypes))).isEquivalentTo(expectedConstructor); + assertThat(clazz.tryGetConstructor(parameterTypes).get()).isEquivalentTo(expectedConstructor); + assertThat(clazz.tryGetConstructor(Objects.namesOf(parameterTypes)).get()).isEquivalentTo(expectedConstructor); parameterTypes = new Class[]{int.class, int.class}; expectedConstructor = ClassWithSimpleConstructors.class.getDeclaredConstructor(parameterTypes); assertThat(clazz.getConstructor(parameterTypes)).isEquivalentTo(expectedConstructor); assertThat(clazz.getConstructor(Objects.namesOf(parameterTypes))).isEquivalentTo(expectedConstructor); + assertThat(clazz.tryGetConstructor(parameterTypes).get()).isEquivalentTo(expectedConstructor); + assertThat(clazz.tryGetConstructor(Objects.namesOf(parameterTypes)).get()).isEquivalentTo(expectedConstructor); } @Test From cd647813aa8246a12e101bba206812e7193bde79 Mon Sep 17 00:00:00 2001 From: Kamil Szymanski Date: Thu, 13 Aug 2020 16:18:24 +0200 Subject: [PATCH 022/115] clarify javadocs that used `iff` Signed-off-by: Kamil Szymanski --- .../java/com/tngtech/archunit/core/domain/JavaClass.java | 2 +- .../java/com/tngtech/archunit/core/importer/Location.java | 7 ++++--- .../java/com/tngtech/archunit/lang/ConditionEvent.java | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java index 36f520ee85..5cc292e52b 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java @@ -1536,7 +1536,7 @@ public static DescribedPredicate implement(final DescribedPredicate Date: Mon, 20 Jul 2020 11:37:59 +0200 Subject: [PATCH 023/115] Removed unused java imports Signed-off-by: Dariusz Zbyrad --- .../archunit/junit/testexamples/TestFieldWithMetaTags.java | 1 - .../java/com/tngtech/archunit/lang/CompositeArchRuleTest.java | 2 -- .../tngtech/archunit/lang/syntax/elements/GivenMethodsTest.java | 1 - .../com/tngtech/archunit/library/dependencies/EdgeTest.java | 2 -- 4 files changed, 6 deletions(-) diff --git a/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/testexamples/TestFieldWithMetaTags.java b/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/testexamples/TestFieldWithMetaTags.java index a4e626d674..3f2c5475cb 100644 --- a/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/testexamples/TestFieldWithMetaTags.java +++ b/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/testexamples/TestFieldWithMetaTags.java @@ -11,7 +11,6 @@ import java.lang.annotation.Target; import static java.lang.annotation.ElementType.*; -import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; @AnalyzeClasses diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/CompositeArchRuleTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/CompositeArchRuleTest.java index d44fe357db..849c08dad5 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/CompositeArchRuleTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/CompositeArchRuleTest.java @@ -1,7 +1,6 @@ package com.tngtech.archunit.lang; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.java.junit.dataprovider.DataProvider; @@ -10,7 +9,6 @@ import org.junit.Test; import org.junit.runner.RunWith; -import java.util.Iterator; import java.util.List; import static com.tngtech.archunit.core.domain.TestUtils.importClasses; diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMethodsTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMethodsTest.java index 3adbb41295..b7d4d50c72 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMethodsTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMethodsTest.java @@ -2,7 +2,6 @@ import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.lang.EvaluationResult; -import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.*; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; diff --git a/archunit/src/test/java/com/tngtech/archunit/library/dependencies/EdgeTest.java b/archunit/src/test/java/com/tngtech/archunit/library/dependencies/EdgeTest.java index 6777e7dc6c..61fb24ccdf 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/dependencies/EdgeTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/dependencies/EdgeTest.java @@ -1,7 +1,5 @@ package com.tngtech.archunit.library.dependencies; -import java.util.Collections; - import org.junit.Test; import static java.util.Collections.emptySet; From 5b250e2bef99820ec1bbb5db5c031059c11f27db Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Wed, 19 Aug 2020 21:40:30 +0200 Subject: [PATCH 024/115] add Spotless Gradle plugin Since we obviously miss unused imports sometimes, we should just automatically make sure this does not happen. I have compared PMD as an alternative, but there are so many "false positives" / rules I do not agree with, that I have decided to go for the quick and lean way, since I have not noticed many other code quality problems so far that PMD would have caught (and PMD is a lot slower). Signed-off-by: Peter Gafert --- .../annotated/PackageLevelAnnotation.java | 22 +++++++++---------- .../annotated/package-info.java | 4 ++-- build-steps/build-steps.gradle | 2 +- .../{spotbugs.gradle => codequality.gradle} | 10 +++++++++ build.gradle | 1 + 5 files changed, 25 insertions(+), 14 deletions(-) rename build-steps/codequality/{spotbugs.gradle => codequality.gradle} (71%) diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/PackageLevelAnnotation.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/PackageLevelAnnotation.java index 314699e93a..aa43304c56 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/PackageLevelAnnotation.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/PackageLevelAnnotation.java @@ -1,11 +1,11 @@ -package com.tngtech.archunit.core.domain.packageexamples.annotated; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.PACKAGE) -public @interface PackageLevelAnnotation { -} +package com.tngtech.archunit.core.domain.packageexamples.annotated; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PACKAGE) +public @interface PackageLevelAnnotation { +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/package-info.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/package-info.java index 5132c8172a..6110533479 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/package-info.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/package-info.java @@ -1,2 +1,2 @@ -@PackageLevelAnnotation -package com.tngtech.archunit.core.domain.packageexamples.annotated; +@PackageLevelAnnotation +package com.tngtech.archunit.core.domain.packageexamples.annotated; diff --git a/build-steps/build-steps.gradle b/build-steps/build-steps.gradle index d62806af59..5af27d7796 100644 --- a/build-steps/build-steps.gradle +++ b/build-steps/build-steps.gradle @@ -2,7 +2,7 @@ def utilsPath = { "build-steps/${it}" } apply from: utilsPath('testing/testing.gradle') apply from: utilsPath('archiving/archiving.gradle') -apply from: utilsPath('codequality/spotbugs.gradle') +apply from: utilsPath('codequality/codequality.gradle') apply from: utilsPath('release/publish.gradle') apply from: utilsPath('license/license.gradle') apply from: utilsPath('maven-integration-test/maven-integration-test.gradle') diff --git a/build-steps/codequality/spotbugs.gradle b/build-steps/codequality/codequality.gradle similarity index 71% rename from build-steps/codequality/spotbugs.gradle rename to build-steps/codequality/codequality.gradle index e90b078877..7fb7964ac4 100644 --- a/build-steps/codequality/spotbugs.gradle +++ b/build-steps/codequality/codequality.gradle @@ -18,3 +18,13 @@ productionProjects*.with { spotbugsTest.enabled = false } + +productionProjects*.with { + apply plugin: 'com.diffplug.spotless' + + spotless { + java { + removeUnusedImports() + } + } +} diff --git a/build.gradle b/build.gradle index 93dca64022..f5a403c78b 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'com.github.johnrengelman.shadow' version '5.2.0' apply false id 'com.github.spotbugs' version '4.0.5' apply false id "de.marcphilipp.nexus-publish" version "0.4.0" apply false + id "com.diffplug.spotless" version "5.1.1" apply false } def appAndSourceUrl = 'https://github.com/TNG/ArchUnit' From 2fd7d322b719f683546e3e46b1d3650eff3c7ce6 Mon Sep 17 00:00:00 2001 From: Kamil Szymanski Date: Fri, 14 Aug 2020 23:35:38 +0200 Subject: [PATCH 025/115] simplify code that creates class stream Signed-off-by: Kamil Szymanski --- .../com/tngtech/archunit/junit/ArchUnitTestEngine.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/ArchUnitTestEngine.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/ArchUnitTestEngine.java index bbd3e9f4db..fa01f573fa 100644 --- a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/ArchUnitTestEngine.java +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/ArchUnitTestEngine.java @@ -22,12 +22,10 @@ import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Stream; -import java.util.stream.StreamSupport; import com.tngtech.archunit.Internal; import com.tngtech.archunit.core.MayResolveTypesViaReflection; import com.tngtech.archunit.core.domain.JavaClass; -import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; @@ -143,15 +141,11 @@ private void resolveRequestedUniqueIds(EngineDiscoveryRequest discoveryRequest, } private Stream getContainedClasses(String[] packages) { - return stream(new ClassFileImporter().importPackages(packages)); + return new ClassFileImporter().importPackages(packages).stream(); } private Stream getContainedClasses(ClasspathRootSelector selector) { - return stream(new ClassFileImporter().importUrl(toUrl(selector.getClasspathRoot()))); - } - - private Stream stream(JavaClasses classes) { - return StreamSupport.stream(classes.spliterator(), false); + return new ClassFileImporter().importUrl(toUrl(selector.getClasspathRoot())).stream(); } private Predicate isAllowedBy(EngineDiscoveryRequest discoveryRequest) { From 6cdcb0e432f209c7227f514ab3b34747d6ea1212 Mon Sep 17 00:00:00 2001 From: Erik Lumme Date: Fri, 28 Aug 2020 15:17:25 +0300 Subject: [PATCH 026/115] Don't escape scheme specific part before resolving as URI #414 When using the non-raw getSchemeSpecificPart() method on an URI, it will no longer be escaped, and the subsequent call to URI#resolve may fail. Signed-off-by: Erik Lumme --- .../tngtech/archunit/core/importer/UrlSource.java | 2 +- .../archunit/core/importer/UrlSourceTest.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/UrlSource.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/UrlSource.java index 670a99cc40..f0903f4891 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/UrlSource.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/UrlSource.java @@ -153,7 +153,7 @@ private static boolean isUrl(String classpathEntry) { private static Optional parseUrl(Path parent, String classpathUrlEntry) { try { - return Optional.of(convertToJarUrlIfNecessary(parent.toUri().resolve(URI.create(classpathUrlEntry).getSchemeSpecificPart()))); + return Optional.of(convertToJarUrlIfNecessary(parent.toUri().resolve(URI.create(classpathUrlEntry).getRawSchemeSpecificPart()))); } catch (Exception e) { LOG.warn("Cannot parse URL classpath entry " + classpathUrlEntry, e); return Optional.absent(); diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/UrlSourceTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/UrlSourceTest.java index 5f75985a86..8be6c2ba1d 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/UrlSourceTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/UrlSourceTest.java @@ -108,6 +108,20 @@ public void handles_paths_with_spaces() throws Exception { assertThat(urls).contains(toUrl(destination)); } + @Test + public void handles_jar_uri_with_spaces() throws Exception { + File folderWithSpaces = temporaryFolder.newFolder("folder with spaces"); + File folder = temporaryFolder.newFolder(); + + WrittenJarFile jarInFolderWithSpaces = writeJarWithManifestClasspathAttribute(folderWithSpaces, "folder-with-spaces"); + WrittenJarFile parentJar = writeJarWithManifestClasspathAttribute(folder, "parent", ManifestClasspathEntry.absoluteUrl(jarInFolderWithSpaces.path)); + + System.setProperty(JAVA_CLASS_PATH_PROP, parentJar.path.toString()); + UrlSource urls = UrlSource.From.classPathSystemProperties(); + + assertThat(urls).containsAll(concat(parentJar.getExpectedClasspathUrls(), jarInFolderWithSpaces.getExpectedClasspathUrls())); + } + @Test public void recursively_resolves_classpath_attributes_in_manifests() throws Exception { File folder = temporaryFolder.newFolder(); From 13c769bd3fd5fbd7e917bed4a3eb79ca91042282 Mon Sep 17 00:00:00 2001 From: "Sean C. Sullivan" Date: Sat, 29 Aug 2020 12:08:21 -0700 Subject: [PATCH 027/115] Gradle 6.6.1 Signed-off-by: Sean C. Sullivan --- gradle/wrapper/gradle-wrapper.jar | Bin 58695 -> 58694 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew.bat | 3 +++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f3d88b1c2faf2fc91d853cd5d4242b5547257070..490fda8577df6c95960ba7077c43220e5bb2c0d9 100644 GIT binary patch delta 6577 zcmYkAbyQT*w}4>)kxq%Bq>+@Al)yql!t+#+E>53IV^;nKUp^h z4s3gkgN%3})P~|EIG7tA>p3fA-P09~3?!BA;4bImM)6XMVtxPCsNO*R8`BM+7JTT( z%DMK_X0u;^`W#m#Ec6g#cs0%#ER_VbZbDE;Xfo6SxH#Jk{G(@Ad9*Ni==)yN&+Rs+ z!c5TRmq9CHM7*0Q{Uj9E>5GhmX#~DLb;+ll z-!FDVFymGnKRbAxQ0Rzpxzf2^IIJZ1>a*fh3^K^l2iUjT$-gD*2u?zG!9_ig1Ulvk zVy#gFy&uq-r`L2o`taG$t$-ROOh@WB(V7|PSzLEhBel)=tr_h5q~-=lfBiIaG-@wk zBq3>qaP`ZEdoQnNbun7EP_R74YiH^8;&y3c`JXY2C}9eWD~SoPu(5u~BT-ou705&# z(j53;{6KX%ts|QD8 zmei!%J?bD0pGB6rrzF3Ql4*rgVKrN33Y||4vWuVRKs>deCPbA_CvjUl;RXEOrT4(m zxINRPIa9#uO~1D1Q#bsM9eukHf}6O{pGw;+ppWNgFcO`3yrOJ5y(f`P;lLa*;FbRM zB@6#w0+(7p)M&JU*^0=M55Aoo4{;;*yUD~nK0+Oa6Wk=2f3o#?BO2E}-q{g_3H_wg z0;-~+W22xve~yBJB8{@|3ve$aMM2@_LP2>6s|At4rllw#)_$CkVXs~Am0ogKD*|j_ zgiI6wW=_0?pQ`6cF%!hwoxE7)ja4t2s;W$!XAP>%4?b0uF*&iXt(lmnIlq5b)v-z5 z@o_CEs960G(Va2M1b+Ve&u{3Tt&W=wujzA1L{0!A;<4?7f{1J9D<+5sp{o0Gl5$Qh zvBaG^vwV&eGKy$?nc}Imhos%j6{jeAIh|0KF*kvI?($YQ(>(!ky77|cTSHMssfR~G z$!TD|WuAv}uxh9`c^b%!wg_oPRMgR?<4-nbn$pQN=jV~oM~!_>Yym71wP48|FE*y1 z96R%lnZ`e5kFBux^GVnme^+#ojZ%|>Xp;`YTt;t&7%2RdyYrDTqAOysp!;^Q-zL2m z{<3O67MM#{q;G@|kDYT#DpCIJl3H#GxYt0ge(`7+S_gDW^oSMNIwm;Zn$I<&Bf(q6 zXRfi^Ts7qA$iN`Y1fg>%(2}%hvhO1!6{>4Wyb#F1d4sm-*u{B+XkX)35({w=e9p@w z!Pg7I))TN#nc`rdU`tKl&M>kWI4ayM{EB@QRb%u*hp0?(Z|kK`q<%-Mn|Rk$Kry&x z=mbY6CaVbil`u$ZZ(N{TTq$+NqK_^ai;mb{lDg>40G|0=XRo2tJyC3p-5k}f^7?0m z!}f`0iJ$zgCO+DX83Hi1e4nescg=5HJKW77vKP%&cungqf-bJ@?y8f`cxo82Am4tdK5irHk!Zy(hjoC+G|8`B*GSSqK!XpB3>XX;C&&ThUp z(T{Z|%<&VjZseczWppu0qfOIq$Lpwg#xP`3*axm&594YRNEg^VdLLbql&Crh zxk@ZEo?micfn~+C=G#?x?rA~#u&fZ4B$0|oO=>5vz&Kr7CNNmEd3)%nX`0iU3>HC! zT?bwEC1;a$T-+#3;`a*P5!UkiVw=dO4u;bWwdE8VOW8ZCEPG&c8+TG;hC!Qi?L4?I zpC)lC*?uKaF3_iZ?^3Bi#f72TX`BY)$Sz@TFjGb|Zko819O%|kphiM-?J-}y*4>24 z1Z`uQG#^U(&XK9hTXJ7k*3IpxwO28-Dcqg~T2-zRcbnj>tQ;LXWH2x&vxfUL{jOGO z3G7epiCpEHPXb!vwOG}1y?}zf&~r@rl2pr0FJBLQe`Zx7xHwB+JF#v)zK?|P1iX%qe47=-$dP5eQmJLn)-7P*Q!|X_fg;{OP$8M}6aFDyBn9pp zAG@AQAIDED;?BF7i8eLnRcFHyi)s-y#2l}t%q{o~>R{|~BTF`M^WV@5Cp9RwF;YB6 z<;I-(^`&Co1awRat-Ba9hLnXWmjQi;b*q2AmBvwGJ*HLuGRtUGBr-<{d2^Hu9VCZ` zEmOQhVN;&3KEb$l;r&K7A0?lp9EmdU&B;|uK(khuYyBj6%w^jdc&x#vzIGg$3?Hm8 z@&DKtMcG{Syi=P=@)YSR&oIsVgN%b7)F$*IQZ&0Za*om#%Wi<02tTVqyF>I4B3MWt z$6TfNCMHLfuNPIvoPmrVvin(*Mh=UE#s_GL15-#6WAt#bomte?X~%J9PErp?aWm_n z6lC5s;l4)APgN^F#?aa2m|4Q`;UwvKYujR)bBgi{_!r2nF?gepca~A@k$Q-lOW9J@ zT}hH0!rO#xTxp@eRMm^NN=@IJWL+;(YROkv8}+tG!s*uW>Q8j@ z8yI`^Q1vgVB+2|UR@B92xet~aB{n8TyP3Tk_Fj3<8o;FK;@Z5{Gg>9^7N=Q;5{>05 z?gpL*2unrhmi!!Ns>5h4>9`#B4c;3@=pp;6=&OFGw$~@ z9Y6gX{2KFq*mUYB(M5GKeOJH@BzLxEN4wMMkP& zbZd=x`^V5OBR^aQz-jX^ef%>lW|0AxwHk&qir#mGAB{?bfHO#7H$G0T!6G}XdKt;y zZc@qt${l)haQ|wn=A!ggAy$%+4%53k(rxLsA&}pBq(uty$Hw|v1n#zDnlDow{`uwy zo?r@Fpm%qyWPIK<%_NqMdvJB27(^PubDrk?z-L){A^m{u86QAdaAxT90ECz$WCJ6n zw!gWlc$H2?+$z9N3dl3KMKwpMrnp}8;Y7i3`i`;qDdSj=Ub7ple;(*p=p?WsYhDg3 zYJl$CU0Oh>nn`x>?apggqu-0Hky~UJADVt4^=tRgQoMReTK!sFe)PN4;2&SS8W zGIaS8t1|V~wXlXvDc)Mdp3H+2z795??E|9^aaGeDdpnrjbPKoZ zuU~yQPN-*{EAb2vp4|}=+_3IxJNAm&8$2TmUQdCrI9x(IVpJ#HD?mg2%|wT(3@N?2Ch8K}NQP5-Veg)fb^46sXoW4y10LgLp>&pXJ6ZL0<68iSn68NFv#Q3fB)8gl>sZdbrt485)IyFEm9l=S*!Je&xWea7c*N9-;LD*Kr#-&UeRz zad>a;uZ=i4>lcMsZqbIIAu%E&t==)^#MxS(qUoWse#ukF6Z2v}ZSol;W&?|Jr131@ zMtl}@2kRk*DR%yZp#*&iupcJ%T`0^|^K< z3I^_?k9s2xUww#5&!)YD!Xecc4M}3rLqF0RvBrK9mpgStQ75;3?p1?R{i5ae?x(@3 z5aql@kOL)4FD`Z|xDw4M6bDPsa74e3@PO{?r)o|sL?4qN&>h;+w+pw+_f&AmIOMCW z@=p^Y>P7fDdt;J3Mv-(w{BI4b$NXWSAyevLFOMWsjUVo7OZLqE z*?ZdqiHo?-m%L}ZecB>T-1DR@5FI@@O3@KF$SI*Tt9QdyUJLLc^IGYcH7z-=n=C^p ziVaaw>_ zz6kp8%4Iy$Moa{Inys8lHMdLni*TK<>prSjVxnv`)1mFAkVe%5eiLIEY@WiQW7uRx z|K4S?+sOIa%WP2e>H_`-Lb-}_=>Kh$mu&oQmFwso2^JN-mA9J={gMk+Di>`!(|3!) z#Hd2HS|Q*;#&Hk_KQ*)Q$JCjusbivMi)FM^U3`4J*@J>(5cp4s;WO4 zaZ~J1_IHyYdhi4^y=X)|W4%8+6R#sv1(#$llI=pm)70JHa2&2*qNP*1qKmySp>KK+ zwoK}Im2^ODta_af$&3@pa8qp$cFcsRs8&z8d-^)98trqt2Y6j8mSu-5vS$gh_$Msk zjY2X6Jway6GlU@yCqLpytlFhFWmsr%+bqVRDxO_}=Q1ujX^9)jwG($`l%b}CID2~z zHSh=O<6IZOtQ9u`dzNl}&&)F-JW=q+c?G-SGSPAX>!(^s4d!~ZvX>K23UOk*%q41j zOgi_lA??Qm?ENX!6AVw({2ar%w^yA})k7D!GZwOR@_%>(&GGRq#1ScYGp+T~*v+Id z)1`{flq6+H#>V0k3=BNN?(I_)op!C8`i5sUSS8om(kV+`d6U_tD>jrttEYbUzCvT~*T815Plap2EGI3m6BGFADJWSzH2gNbXK zAMevc_gV`Hwqv_d6t2nD#8mRtLj}5u1A`p|zy^L7tn)2^#cmn5ttx>AzWu|}4319d zmTCBd3DG$iJAc12RQBtaqtaDO<(lhp)saUjc}ckOF-?*CILc)CHQ3-c&R_bIx^RC(Uh>H=?Hc!Jfq*uf^5pvZ1qUEjUGFLA48xlJ@Id&^o~ zAxnaPkQJ{5`miM|3u`!5Yl>vOG3{InE)J-^?GFBYhs^S3{f%XmmMDbY929%)tXDK^ z4&0msZpvP=Oj^{;CiXzs=(d5-Tj9y&vR~?%ulrK|3M7R8AoRPFd*Jh%S=Iyda9Ke_ zrF5}XI&XAA(WM2qY$-Iw=VH7%AroF4;p~b8;9td1F#2cg%y^x}8|g+T(nMU&Zr#zB z-RYWpGePM7mRPYj^xvwV5!U1{Qb-VxZQ=%)g%P$JAS;+A)+%LtlNZ;uSA+=6xC;W1 zZ&!}Qje-aZE$+yMeC&-WJLqg}I+P*%A{y4Qaq5y97gk+F4qy~fVTW7#R8qx7{kLj@ z_Ak&Hi`GnE(YIf+nBX>YuN&8z>0+n8Y4Mw_D`*=uT-^XHMD;CpOPj0`pX1G}5>QX= zPS1iRQ#%re7!OK%X6W0M^BrF0IHK`4^^7#J+x`8GKi86ZU=OWN9Rd zbc#BaTYr?doP4Q$Tbac6h=c1Tcuy;l?Gu<2wG$iKh^=kN1p-~6nuHE#vN&}$>STjm zpd>NS?sZTc`Yti+^Jx(&e|e>jw51=3B!N5zF}}Z+dmjmLgD^?|K2t{vCP(Y5cxl45 z^#&!362V;(_~IFmEp7G&NyG+08Lf|URTC2r&e;9YS?LAO`7_Iiod$D!uB3}mMv5NZLM!7V8_tEyUwc&kFa1isI?26Eogw$4lsNRB(#c3Ssm(>CFP`< zuem=>#4!%PU48QZO*F)iwJsf#~c=|+1W5feb` z44pz7si?Qj-K8bF6sL7&%FICc1M1vBmTxRa~P2hdeYJpZ#955J&b zqeVyms=gR(%w^R?^1A&w#Ap@G%}hbE=bp6}sf~VMdpZjHb}bxykA59XXKm?+-Sd~% z;Xw}ENaem6xp{yUqkQ@z^x;+Il6-@d59N}XiYXGL6;QWzd#QUz8R&)Ql$)Ph=q4%t z2Unt^=Ru1Mji9_%K^h15uS`f6VVOTS&b2=_dU&nt%RSrsMUY+vWcC91ej!2YKzLFi z7o|5#RqpAxW)fo!>%GSC=QWq}-chx2_7Cw$HaRJ14sv$m%L#iajDtdxcqEnql!qgs1EZuI-bz*5EO zAWxzL1X}g$g^3JgM8S%;%wjN|95AK3o{Z`BBlLV(B_zdIva)EKP4Y8FOYwp;$Raw@wT4E<{pj3{hDai8KZje zcEuA-{d?JgLv!WnmKq5MyMEX52loR(6fdEA-RV<{G8H5Igxq1>w}%2S)_ju;wF_ZM z$7!A^lLCtCZdv033jL{f&eI>9ISF2x$~~6;tnOzYI*(I*?>+6ozHgn+iutW-50rn% ztIAoG0!guTBfvFW3Thg_WtLf?4+*6q61dY`qXbfO*(>@w!l|u3&BIZu84UE^j!yro z^oi)PjvWObd1M?(HjP?Hjc1s_HH?DvC)%cciIXHNQnqKY1Mg3}aOh6*=l4mzd4Txc zLVTFGo>@6$+loh+i-?qdkxJD?$#HzVN62jNChy z4YB@j$_b-hu>?T$VRfJvu%s0s0Ef{(lrq7C9j(X!@J;?lNnl2+?0`t?f7)S9^Q45Z zG6zDOr=jV;rzj)?wzFyiNCrKXu>VVcSOWr1JYl$A%&@I}YQk6lTl(}a3eog}xp;BF z2-ewA(_y0P;(%cL?=XaO+#VrrP#hBP1}@E>Nc z)4|rBGPfW9Y4aX6jC&IZkPLfLMi?Xv6E-?e2or%4;{NZwMIr3ae@SO35VpC=4w(A< zPw^v(VQ;tC0lm@xG)9oQ zxqJfxZgT&HB=QJh)Z2tGvcms=GiKqxqjKmdC2Q%Df@d50Zk!pNuo|L1uQJKl2yY)r#$r^WuYHGdz7S_A9cR|BBV!D#1L$+T24p8a>Pgr3$< MViXjGx&OBR0?kH%b^rhX delta 6547 zcmYkAbx_pNyT)OqL%K`4yFnJ0Ub0fbkcY00Ec`v8pw# zP1%=K=fTZQx1pfej+Ro3pZ{H+B$tvoY7*_j#twUpZpfOnC9Xc>mcgedjEy*!&BAw+ z!Pb8qzSx)i-geP%Y&mo93hXitf4u*5hTDllPosG z#)a_-^*6(UY8N`S7#Hmosbzg7Pl<;TElEZd0hEZc|TV zsfGsW_Cs|WF=Fk4&PWdE3~w?1)ajZRB`0|;a45l@mC9V@1@RVN@ykVBK8wj$z=wr@aDeA*lqRvbqEYcJ++2G(*rVbDu7M7;lVb@s zUpiabP+>}OT-jh)W+<}$*eWiZ!a{(GunZh*`?>0O^2Pop%YFQ-&u%m(0r8~z!-&?N zYn(_=J{6xvr3iEFhzT?{vM~CW%j8)1I6t@AfImYf>vJhH!Xrw5h_lkT}!v{y-23=jSt)Sxt`>B z(!Au<2-0p1MQWh`&bz(aR;aC0Ywui+>UmdxbpB&%mezJJ*n&xThv`}u!B~E(N6-K3 z3_8U>zN>1nxd(h1iZ4Rq7~R3ap1mtva6>is57nm3v~T=d4VC6NTP-$W3|T+EOHnOs z6tTAIq*mP>cz`uFr^&$b^x`)MujcOSgT=Yceij*Y2cU~z8-M<+1mERc*)H-}DR&(h zw?8L`cL$at6C$(3&N&zm$_4RI;qh@^|D<^Q1j)=%Hg<)&3a~S>T?6fn(Y2$jXta6S zO*-lYV;1+QIO#)S7L)%6kv;6q8ytk%rpw(R;ZohTbgfkyhu`}w@D}dQrJTkg$+${qm4m?HteM^(ho{20(c64>NjM2%I9G12_vO{<(vZQd zeYr)er=*_dY|4^hg-E$#nyQ03GpQ4-Q>6Mi+kNh?FK_xpfIl`MPV4Yy3cqmDKrpYQ zesF@i+ZSGz(@?*!1V@TSA=|@^9YkoSsgwI8i46HP#)kQLQx{t)nUusL!hR_fp_d86 zt6zUwGi1>GCU1(kw9Tn*Z*I4U?>Bm*Gn!a26D8kkO%asgWz9h?L?M`Aamwl&@P$p8 z-0z1ko0m^H#GcxW?8A@Qr~$iG<1%aA=Y(bR-G`#gEI$V!O^dX_dwmioj(5~kcZc}q z!j}a(&4VKAIw7#H5%M(h8rbr}@-_RxC5_YaHM%uX&ADKNdnWvcPF=7P{=yoTljgvk z6!VD4fE~l^=#+;87bGzasykginl9YLMr2J*O+NeCPMyo2Gra8fsqiQ`7s-BU8kRw} z=mQ^6!JW;kd*js3IK%X_n$F2?gnyPdmMz;<}hhX8vL8# zDwb%YeX5HF4~B8Zit^3_wRA8m_7pTF3j1!)mdP4XLSH2=$J-dPiqH6Dh@j@?CD;r` zR$IQ+WWpb>Xw^^DmRHcmN+#F^#-;d8?l%bvl|*4MN7OhV)mNH&72YV%wl(zBp+! zp{cou)D(g0n+xXCANKg!ER|_wPC>bx7-khT3EI#3PL)x9?_em_p`|iUe;3QW2p4Uc zv$CIRUL;gYhF`->`J<_bMn!l*UX&>W{xC7-XnRWc1|lH6m4ygrIo&mVs`>#Pb1v8>{GX-P4kK_KxSuyies;QBq1e->cP5+I;eAg9LbM^wtQ6eSW_zWF8 zI^>q<)j(@pva4?EE_PMo%gu%y`?E7d?e(WTWB>9&u`(yaalT)+pV9kcLPsL0KfV%u zc`H~JJ^Mh-J-BS0P}*69ouWEE<<9j7`A|5;d{M00Q6yV@At949h5jx_bv?(4%R{?J z_4E1c!gX?~p~<^gRf=g=E+_Vx$91C{%zJsH*EwHU74kDfi9elX)j7Vu%$osz1mq6S z+B0uR{A^U4QBOY9fAqYUmBU~EL2x~|c|3g-%f>aR(w}?1@Z7oGd`J3P^A-Ibj>6_w z{k0xhog3$NkbWcm+%+P{D8VWVW?dkh{@(R^1TWWEv_V^> zSaBI*x8WKK6-py7SIMl02$MS^6zBz{1@ z;bPeEOV*SwCmd}1zQ9Bt<1dP>ANcVrX`sqZ#Lctm56lic7SnjvsdF;>)i~)4)}6<8 zw>3kuJ6R?7lqCYM4+5leLIB{FKq@^Srr;_e9vKqp49!1e$Mo?uyV%V<^c}k0JY$e141jJkVTsm>WF? zzUm(myxyEf#<`GTnpaS5;b$-*bddR+=ipA45;OVx0Ci>}3ay2L1rZ&dWRo=voeU)U zukSaL`h57RPMmtbU6(#zA_lo?M$T~-&?rm`EIP1}2tL8<<{_<907tgqeEL3SsAI!k z2jgOUsW&{QL9N^1M$%VrXYb}SSI09g{%-q=@X+@NcaGE;Sk$ED=7Ox*;0*3Wi3^HW zfICY#b-$>~7%kFL&inoFFjq%+hvAJu*EQCjZXD-^tNyY(*JC&W!5tIGKI+i+N%gZY zSI5{_ZHY*1*6KBtgiF3f{Xo5ez5t)u!c$YO$IQpv|5==g7wqgwAyp*JJEs<+<#2Rb{s&@eV z;2pLXV}CIoejpWOF`HSeP>^@;wg--*snbwmz`h7Km33$+4sZ4=Hmpex-O zqJ1uQVCQliL8^Z2hc8r1pwrjeeG2L?3*AUK8hh7QV|M3XApI#FY-5`B0)FYsr+=TV zW?AHTHxy>#QbyO{Hb$0bq!##z*Ym!$b|RRW%<5ZHstN4rCK^^7pXU)ZD$diO;3SMm z-`5g7n|)S@A4GiKE1ec08xG$SOOPM=Ca1DfbRDca!_%7>sjyFiOWb;e>%9W&D$+?cLXYCh4ba##?-1<&69 zaH<~z9paWS)W!bcJ>&>%5zAt1xWSIIq5I>NE=@0mFzu$HKeDf>M`UydKzZyyx3FPV zeRI)5yX39+UAoH#@F)&0l$T-Q32(vjWcJ8eIYr*4HhHYu%Gzp;u^`rY^W9 z9F01NSn zDq+@Ud?UjbN4hEecEWu;zy1v)2|B(eJ@>Y7Tx@Gh>-?RsXZ|m`h$HcGdoCYKwmdKt z!(gspq5CDyr$8fzL?5HV6GmaPn2^yS@h89yg7P zv>kt>NjC;EWQ^Fk5ru=wy$FaZ-QCgW9%v=u{A~W?Tclu3=TMA6jUg>Q%z z0DZE&sp8FZymao0;o)X{%Kqin7mz{+-}O9v=eaHJm*EyfbIhlxL9)+En^Fen+s9N8 z?9Ax9wJ!8+3B12oy|Xcu{_u^c3VR%TaC=L%`u^wPqiI^v5FuzD97y?^zu;%?ANsX1Oib}xXjsN4^999+mULA4 zgAz^MtI5vp+<<&i@}JBu)`MW``uU|zgiw9nK(r^5AqHH64wH&)Qevoo`c(_9aG01@ zOK>GiZKeWSW2QnW&mnZ%&H5dtc^FZGo$L)1(otL-f>EU)oZoVaN*x-JV|xu-6Vyj&P0i{$#{T=~MwSw&I{A?F84i1gv( z)hRc=+_D2|mF=9Hi-23y=4-gvA3{SnYbVCzd5b9L(c9g?RP7|X zfs^d06B_u77gR!RA#r8+96}-`o@w!3Ua}0@QXG~eTeTy#G2yvRp$i%!$*HKZgl67s zu|>QhVci1yp>ajz$vxQsho-|ozQ!k%SwpGlrDD35d#FL5P0j9;aVK~M5V~R&*^=+L zSCzmzQciQYuf=0RCpt@)51vxm3rMU&y&##ir%NGZ&Zk(@TKmq)9z>pPm|7MW(fbxl zxZwmY; zN}{MPKvPp3B+<7pUV#b^t*{b12zyQPbh;WkjXCz}Ru>nJ#lDvm^~g+2m2&Ci#rf=W zlJ_Ne%V*;Dx(!}T2D|P6(VS$XM*iB2tVXeM6k^E?d+?5QXHqc1K{0n$%%*tB^=D>C z{Rv@&Y!C1X_)ss(h1eJ5{yqpOSSDRwxO1!itaD>RV1%dmf;F}BSF>z$+!ZNCm9>%3 zB$H}@JlE71f7KotsYWn%*}UuP-u5Lk4KCN2ahPFJs6v=g4a{r>xdoBi>Ku#l+Z>K= zwezjvKQ#3mdA(SahO=mcpI~JXIP!P>a*IrMJHz{yqYw^43@u);$e^P?Gl5N#L7VQX zb<;DDo;5P(0!j*-Ol}^`?3^Xd62%kK*S5*8(>qs@nJ8z%hMxE6519pfM|vn27qDE} zaJ>x&>A|+9=<^>R+%%8!d%3@~L?_MoFch9k8I9>)gNs0!m?%lJ@1~%hFpIc)ymh0K zd|UJS+{$Q#W+iY{stH?!&L(ymcFmPp%e!D^=o;<%1)qad$Ec-kK<%kdOG^}6NJy$G z)-+x^HXfcue(T86JkI|61%F15!*t1QUQa~Zk?9V@%;2+9n1|TEn<#9XV56}1AgZXl zEh`qo?!^}YIboKsV&BnqLav{2(1Y+83WbvGuyYYPD9q+)<7S|B zv-f*t`|zOOR4wEft=PL?k(rp6xJk;UDDyB{zVT`P3c`{8>*$4wl)kAd6io(Cm^}aF z@C!An4E3sss?9XD7k6BLFka4g)>Tcp@K(zv^>w~9bj{;Xq`%KV|84fFZ+^RDD5 z&D||R7u@IaMNW;>*F1*|X9|Zd_bnyKvu5EamB_jG`JPsUj_cXtfG9+Gjipd&=k*=@ zSAhOH1m8eW(icWXDUj9~ZfM}7GM$VC!a9aC-m z$9&}vXeQ@XN!yio)>wnSzdn=;q=i?)3mhg93pVMVBsjb;$m27x6+9D7HHXZ%-ySdS z%3-ymPnpOtY1D7si5fq6BpxnqYV$BGQ`pqmw2tS?7BLGj=p*uFAyE(xmF>T8^XMzz zw6z-2|HajrqxK4b-%h7+T@usb1> z->hmpIo^MR&k=ug(hd`I0w7tJq^B~q6snow@@qlwFrL0U_=9red9nQV!BLB*n%au_ z7SnFMfboKV`|!#-oxrN~aRU2-@%*wMv2nra9iSwbJ^W%l?!oMq_Pzy9gWK=ig7*ih zB4=|XT0P7ng?xD0PG3&1^@!%hf88|Yw;)fv9#>!EWu<)Ax(s=2e1TwHbCi+=oj+08 zYBbA9IG4oN*_Z#e$jD{DF%?^1`f9_>PM~~3ITW_pk)`WtDBgMk1&kTF^j1$1=|$tJ zjtNrAbC8($17KUyjjj)^@<#sc>1}DWs&?n>sE4Im$OpCZ^NIkktFI`#ivyY!GJ81& z3AJgh3$7e@uki@7pOuM3VcMnN-@w(jd&ay>k_L(%yKLOfHOtmDSNr6C3u$I%N$SQHW%=$FPV6i$Fz%`f zvTF|4kS7dRnJ>42(TDsLqaLY5@&Ey0u$q}4o#Y||v|WUqL1NK1mLOKneC`^BVDKV^ z+z6G7-OEnW<=4(hE4U}46Ng}{OS8|)el0=}!}g3YXD{bM1NRr-cDVaKP2}q4tH-0Q zC<%qSM}j(pfkZIce@5`Y*LfrC|DAIJGz*rXAcKFC&T0cZAY*|G#AE!=%EIu0!v#4I z0qlP)2{5=q2-q)DgFaaQLoL>H|4@+~A@1Mt>A#i#J{8zlgn^K7U~`cc7=b?pFy{#Y z&n0TqQy^hU8>HsmB*F;s{;wwP zuzw*uj2c*3KQ=Lj=5I&{G_6sCC_nz&@Ow=QG?@5LzFAj7 zy#Q*~;h Date: Sat, 5 Sep 2020 11:44:25 +0000 Subject: [PATCH 028/115] Bump kramdown from 2.2.1 to 2.3.0 in /docs Bumps [kramdown](https://github.com/gettalong/kramdown) from 2.2.1 to 2.3.0. - [Release notes](https://github.com/gettalong/kramdown/releases) - [Changelog](https://github.com/gettalong/kramdown/blob/master/doc/news.page) - [Commits](https://github.com/gettalong/kramdown/commits) Signed-off-by: dependabot[bot] --- docs/Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index b9e7e03b27..b95105f4ec 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -56,7 +56,7 @@ GEM gemoji (~> 3.0) html-pipeline (~> 2.2) jekyll (>= 3.0, < 5.0) - kramdown (2.2.1) + kramdown (2.3.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) @@ -78,7 +78,7 @@ GEM rb-fsevent (0.10.4) rb-inotify (0.10.1) ffi (~> 1.0) - rexml (3.2.3) + rexml (3.2.4) rouge (3.19.0) safe_yaml (1.0.5) sassc (2.3.0) From a10ffb6f17408f3e16b9d4c2fb24d1d7d566993d Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Mon, 3 Feb 2020 23:42:11 +0700 Subject: [PATCH 029/115] rename `JavaType` -> `JavaClassDescriptor` We need to free the name `JavaType` if we want to add support for generics to be consistent with the Reflection API. I.e. if the Reflection API provides a super type `Type` for `Class` and `TypeVariable`, we should probably mimic this as `JavaType`, `JavaClass` and `JavaTypeVariable`. Otherwise we break the consistency with the Reflection API there, making the API confusing. Note that I do not consider `JavaClassDescriptor` the best name in the universe, but merely could not come up with anything better. It is pretty much all the information we can parse out of the info parsed by ASM, name parts, component type, etc. Signed-off-by: Peter Gafert --- .../com/tngtech/archunit/ImporterRules.java | 12 +-- .../archunit/core/domain/AnnotationProxy.java | 6 +- .../archunit/core/domain/JavaClass.java | 20 ++-- ...JavaType.java => JavaClassDescriptor.java} | 99 ++++++++++--------- .../archunit/core/importer/AccessRecord.java | 16 +-- .../core/importer/ClassGraphCreator.java | 2 +- .../core/importer/DomainBuilders.java | 56 +++++------ .../core/importer/ImportedClasses.java | 12 +-- ....java => JavaClassDescriptorImporter.java} | 18 ++-- .../core/importer/JavaClassProcessor.java | 56 +++++------ .../core/importer/RawAccessRecord.java | 14 +-- .../core/domain/AnnotationProxyTest.java | 6 +- ...Test.java => JavaClassDescriptorTest.java} | 36 +++---- .../core/importer/ImportTestUtils.java | 20 ++-- .../importer/JavaAnnotationTestBuilder.java | 4 +- .../JavaClassDescriptorImporterTest.java | 27 +++++ .../core/importer/JavaTypeImporterTest.java | 27 ----- .../tngtech/archunit/testutil/Assertions.java | 16 +-- 18 files changed, 224 insertions(+), 223 deletions(-) rename archunit/src/main/java/com/tngtech/archunit/core/domain/{JavaType.java => JavaClassDescriptor.java} (69%) rename archunit/src/main/java/com/tngtech/archunit/core/importer/{JavaTypeImporter.java => JavaClassDescriptorImporter.java} (70%) rename archunit/src/test/java/com/tngtech/archunit/core/domain/{JavaTypeTest.java => JavaClassDescriptorTest.java} (76%) create mode 100644 archunit/src/test/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporterTest.java delete mode 100644 archunit/src/test/java/com/tngtech/archunit/core/importer/JavaTypeImporterTest.java diff --git a/archunit-integration-test/src/test/java/com/tngtech/archunit/ImporterRules.java b/archunit-integration-test/src/test/java/com/tngtech/archunit/ImporterRules.java index a0b3befb06..320707e372 100644 --- a/archunit-integration-test/src/test/java/com/tngtech/archunit/ImporterRules.java +++ b/archunit-integration-test/src/test/java/com/tngtech/archunit/ImporterRules.java @@ -2,7 +2,7 @@ import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaClass; -import com.tngtech.archunit.core.domain.JavaType; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.DomainBuilders; import com.tngtech.archunit.junit.ArchTest; @@ -21,15 +21,15 @@ public class ImporterRules { .should().accessClassesThat(belong_to_the_import_context()); @ArchTest - public static final ArchRule ASM_type_is_only_accessed_within_JavaType_or_JavaTypeImporter = + public static final ArchRule ASM_type_is_only_accessed_within_JavaClassDescriptor_or_JavaClassDescriptorImporter = noClasses() .that().resideOutsideOfPackage(THIRDPARTY_PACKAGE_IDENTIFIER) - .and(not(belongToAnyOf(JavaType.class))) - .and().doNotHaveFullyQualifiedName("com.tngtech.archunit.core.importer.JavaTypeImporter") + .and(not(belongToAnyOf(JavaClassDescriptor.class))) + .and().doNotHaveFullyQualifiedName("com.tngtech.archunit.core.importer.JavaClassDescriptorImporter") .should().dependOnClassesThat().haveNameMatching(".*\\.asm\\..*Type") - .as("org.objectweb.asm.Type should only be accessed within JavaType(Importer)") + .as("org.objectweb.asm.Type should only be accessed within JavaClassDescriptor(Importer)") .because("org.objectweb.asm.Type handles array types inconsistently (uses the canonical name instead of the class name), " - + "so the correct behavior is implemented only within JavaType"); + + "so the correct behavior is implemented only within JavaClassDescriptor"); private static DescribedPredicate belong_to_the_import_context() { return new DescribedPredicate("belong to the import context") { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationProxy.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationProxy.java index 5df3e59552..28247930b7 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationProxy.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationProxy.java @@ -120,7 +120,7 @@ private JavaClassConversion(ClassLoader classLoader) { @Override public Class convert(JavaClass input, Class returnType) { - return JavaType.From.javaClass(input).resolveClass(classLoader); + return JavaClassDescriptor.From.javaClass(input).resolveClass(classLoader); } @Override @@ -150,7 +150,7 @@ public boolean canHandle(Class returnType) { private static class JavaEnumConstantConversion implements Conversion { @Override public Enum convert(JavaEnumConstant input, Class returnType) { - for (Object constant : JavaType.From.javaClass(input.getDeclaringClass()).resolveClass().getEnumConstants()) { + for (Object constant : JavaClassDescriptor.From.javaClass(input.getDeclaringClass()).resolveClass().getEnumConstants()) { Enum anEnum = (Enum) constant; if (anEnum.name().equals(input.name())) { return anEnum; @@ -196,7 +196,7 @@ public Annotation convert(JavaAnnotation input, Class returnType) { // JavaAnnotation.getType() will return the type name of a Class @SuppressWarnings("unchecked") Class type = (Class) - JavaType.From.javaClass(input.getRawType()).resolveClass(classLoader); + JavaClassDescriptor.From.javaClass(input.getRawType()).resolveClass(classLoader); return AnnotationProxy.of(type, input); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java index 5cc292e52b..941d75711f 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java @@ -64,7 +64,7 @@ public class JavaClass implements HasName.AndFullName, HasAnnotations, HasModifiers, HasSourceCodeLocation { private final Optional source; private final SourceCodeLocation sourceCodeLocation; - private final JavaType javaType; + private final JavaClassDescriptor descriptor; private JavaPackage javaPackage; private final boolean isInterface; private final boolean isEnum; @@ -101,7 +101,7 @@ public Set get() { JavaClass(JavaClassBuilder builder) { source = checkNotNull(builder.getSource()); - javaType = checkNotNull(builder.getJavaType()); + descriptor = checkNotNull(builder.getDescriptor()); isInterface = builder.isInterface(); isEnum = builder.isEnum(); isAnonymousClass = builder.isAnonymousClass(); @@ -138,7 +138,7 @@ public String getDescription() { @Override @PublicAPI(usage = ACCESS) public String getName() { - return javaType.getName(); + return descriptor.getFullyQualifiedClassName(); } /** @@ -152,7 +152,7 @@ public String getFullName() { @PublicAPI(usage = ACCESS) public String getSimpleName() { - return javaType.getSimpleName(); + return descriptor.getSimpleClassName(); } @PublicAPI(usage = ACCESS) @@ -166,12 +166,12 @@ void setPackage(JavaPackage javaPackage) { @PublicAPI(usage = ACCESS) public String getPackageName() { - return javaType.getPackageName(); + return descriptor.getPackageName(); } @PublicAPI(usage = ACCESS) public boolean isPrimitive() { - return javaType.isPrimitive(); + return descriptor.isPrimitive(); } @PublicAPI(usage = ACCESS) @@ -213,7 +213,7 @@ public Set getEnumConstants() { @PublicAPI(usage = ACCESS) public boolean isArray() { - return javaType.isArray(); + return descriptor.isArray(); } /** @@ -1208,7 +1208,7 @@ CompletionProcess completeFrom(ImportContext context) { private void completeComponentType(ImportContext context) { JavaClass current = this; while (current.isArray() && !current.componentType.isPresent()) { - JavaClass componentType = context.resolveClass(current.javaType.tryGetComponentType().get().getName()); + JavaClass componentType = context.resolveClass(current.descriptor.tryGetComponentType().get().getFullyQualifiedClassName()); current.componentType = Optional.of(componentType); current = componentType; } @@ -1216,7 +1216,7 @@ private void completeComponentType(ImportContext context) { @Override public String toString() { - return "JavaClass{name='" + javaType.getName() + "'}"; + return "JavaClass{name='" + descriptor.getFullyQualifiedClassName() + "'}"; } @PublicAPI(usage = ACCESS) @@ -1741,7 +1741,7 @@ AccessContext.Part completeCodeUnitsFrom(ImportContext context) { private class ReflectClassSupplier implements Supplier> { @Override public Class get() { - return javaType.resolveClass(getCurrentClassLoader(getClass())); + return descriptor.resolveClass(getCurrentClassLoader(getClass())); } } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDescriptor.java similarity index 69% rename from archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java rename to archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDescriptor.java index b844d3518a..a19be58634 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDescriptor.java @@ -40,10 +40,10 @@ import static com.tngtech.archunit.core.domain.Formatters.ensureSimpleName; @Internal -public interface JavaType { - String getName(); +public interface JavaClassDescriptor { + String getFullyQualifiedClassName(); - String getSimpleName(); + String getSimpleClassName(); String getPackageName(); @@ -53,30 +53,31 @@ public interface JavaType { @ResolvesTypesViaReflection Class resolveClass(ClassLoader classLoader); - Optional tryGetComponentType(); + Optional tryGetComponentType(); boolean isPrimitive(); boolean isArray(); - JavaType withSimpleName(String simpleName); + JavaClassDescriptor withSimpleClassName(String simpleName); @Internal final class From { - private static final LoadingCache typeCache = CacheBuilder.newBuilder().build(new CacheLoader() { - @Override - public JavaType load(String typeName) { - if (primitiveClassesByNameOrDescriptor.containsKey(typeName)) { - return new PrimitiveType(Type.getType(primitiveClassesByNameOrDescriptor.get(typeName)).getClassName()); - } - if (isArray(typeName)) { - // NOTE: ASM uses the canonical name for arrays (i.e. java.lang.Object[]), but we want the class name, - // i.e. [Ljava.lang.Object; - return new ArrayType(ensureCorrectArrayTypeName(typeName)); - } - return new ObjectType(typeName); - } - }); + private static final LoadingCache descriptorCache = + CacheBuilder.newBuilder().build(new CacheLoader() { + @Override + public JavaClassDescriptor load(String typeName) { + if (primitiveClassesByNameOrDescriptor.containsKey(typeName)) { + return new PrimitiveClassDescriptor(Type.getType(primitiveClassesByNameOrDescriptor.get(typeName)).getClassName()); + } + if (isArray(typeName)) { + // NOTE: ASM uses the canonical name for arrays (i.e. java.lang.Object[]), but we want the class name, + // i.e. [Ljava.lang.Object; + return new ArrayClassDescriptor(ensureCorrectArrayTypeName(typeName)); + } + return new ObjectClassDescriptor(typeName); + } + }); private static final ImmutableMap> primitiveClassesByName = Maps.uniqueIndex(allPrimitiveTypes(), new Function, String>() { @Override @@ -97,8 +98,8 @@ public String apply(Class input) { .putAll(primitiveClassesByDescriptor) .build(); - public static JavaType name(String typeName) { - return typeCache.getUnchecked(typeName); + public static JavaClassDescriptor name(String typeName) { + return descriptorCache.getUnchecked(typeName); } private static boolean isArray(String typeName) { @@ -131,28 +132,28 @@ private static String createObjectComponentType(String componentTypeName) { return "L" + componentTypeName + ";"; } - static JavaType javaClass(JavaClass javaClass) { + static JavaClassDescriptor javaClass(JavaClass javaClass) { return name(javaClass.getName()); } - private abstract static class AbstractType implements JavaType { + private abstract static class AbstractClassDescriptor implements JavaClassDescriptor { private final String name; private final String simpleName; private final String javaPackage; - private AbstractType(String name, String simpleName, String javaPackage) { + private AbstractClassDescriptor(String name, String simpleName, String javaPackage) { this.name = name; this.simpleName = simpleName; this.javaPackage = javaPackage; } @Override - public String getName() { + public String getFullyQualifiedClassName() { return name; } @Override - public String getSimpleName() { + public String getSimpleClassName() { return simpleName; } @@ -177,11 +178,11 @@ public Class resolveClass(ClassLoader classLoader) { @MayResolveTypesViaReflection(reason = "This method is one of the known sources for resolving via reflection") Class classForName(ClassLoader classLoader) throws ClassNotFoundException { - return Class.forName(getName(), false, classLoader); + return Class.forName(getFullyQualifiedClassName(), false, classLoader); } @Override - public Optional tryGetComponentType() { + public Optional tryGetComponentType() { return Optional.absent(); } @@ -197,7 +198,7 @@ public boolean isArray() { @Override public int hashCode() { - return Objects.hash(getName()); + return Objects.hash(getFullyQualifiedClassName()); } @Override @@ -208,28 +209,28 @@ public boolean equals(Object obj) { if (obj == null || getClass() != obj.getClass()) { return false; } - final JavaType other = (JavaType) obj; - return Objects.equals(this.getName(), other.getName()); + final JavaClassDescriptor other = (JavaClassDescriptor) obj; + return Objects.equals(this.getFullyQualifiedClassName(), other.getFullyQualifiedClassName()); } @Override public String toString() { - return getClass().getSimpleName() + "{" + getName() + "}"; + return getClass().getSimpleName() + "{" + getFullyQualifiedClassName() + "}"; } } - private static class ObjectType extends AbstractType { - ObjectType(String fullName) { + private static class ObjectClassDescriptor extends AbstractClassDescriptor { + ObjectClassDescriptor(String fullName) { this(fullName, ensureSimpleName(fullName), createPackage(fullName)); } - private ObjectType(String fullName, String simpleName, String packageName) { + private ObjectClassDescriptor(String fullName, String simpleName, String packageName) { super(fullName, simpleName, packageName); } @Override - public JavaType withSimpleName(String simpleName) { - return new ObjectType(getName(), simpleName, getPackageName()); + public JavaClassDescriptor withSimpleClassName(String simpleName) { + return new ObjectClassDescriptor(getFullyQualifiedClassName(), simpleName, getPackageName()); } } @@ -238,15 +239,15 @@ private static String createPackage(String fullName) { return packageEnd >= 0 ? fullName.substring(0, packageEnd) : ""; } - private static class PrimitiveType extends AbstractType { - PrimitiveType(String fullName) { + private static class PrimitiveClassDescriptor extends AbstractClassDescriptor { + PrimitiveClassDescriptor(String fullName) { super(fullName, fullName, ""); checkArgument(primitiveClassesByName.containsKey(fullName), "'%s' must be a primitive name", fullName); } @Override Class classForName(ClassLoader classLoader) { - return primitiveClassesByName.get(getName()); + return primitiveClassesByName.get(getFullyQualifiedClassName()); } @Override @@ -255,17 +256,17 @@ public boolean isPrimitive() { } @Override - public JavaType withSimpleName(String simpleName) { + public JavaClassDescriptor withSimpleClassName(String simpleName) { throw new UnsupportedOperationException("It should never make sense to override the simple type of a primitive"); } } - private static class ArrayType extends AbstractType { - ArrayType(String fullName) { + private static class ArrayClassDescriptor extends AbstractClassDescriptor { + ArrayClassDescriptor(String fullName) { this(fullName, createSimpleName(fullName), createPackageOfComponentType(fullName)); } - private ArrayType(String fullName, String simpleName, String packageName) { + private ArrayClassDescriptor(String fullName, String simpleName, String packageName) { super(fullName, simpleName, packageName); } @@ -288,15 +289,15 @@ public boolean isArray() { } @Override - public JavaType withSimpleName(String simpleName) { - return new ArrayType(getName(), simpleName, getPackageName()); + public JavaClassDescriptor withSimpleClassName(String simpleName) { + return new ArrayClassDescriptor(getFullyQualifiedClassName(), simpleName, getPackageName()); } @Override - public Optional tryGetComponentType() { - String canonicalName = getCanonicalName(getName()); + public Optional tryGetComponentType() { + String canonicalName = getCanonicalName(getFullyQualifiedClassName()); String componentTypeName = canonicalName.substring(0, canonicalName.lastIndexOf("[")); - return Optional.of(JavaType.From.name(componentTypeName)); + return Optional.of(JavaClassDescriptor.From.name(componentTypeName)); } } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java index eaffa473b6..a87c570743 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java @@ -30,13 +30,13 @@ import com.tngtech.archunit.core.domain.AccessTarget.FieldAccessTarget; import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaClassList; import com.tngtech.archunit.core.domain.JavaCodeUnit; import com.tngtech.archunit.core.domain.JavaConstructor; import com.tngtech.archunit.core.domain.JavaField; import com.tngtech.archunit.core.domain.JavaFieldAccess.AccessType; import com.tngtech.archunit.core.domain.JavaMethod; -import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.properties.HasDescriptor; import com.tngtech.archunit.core.domain.properties.HasName; import com.tngtech.archunit.core.domain.properties.HasOwner; @@ -102,7 +102,7 @@ private static class RawConstructorCallRecordProcessed implements AccessRecord> methodsSupplier = new MethodTargetSupplier(targetOwner.getAllMethods(), record.target); JavaClassList parameters = getArgumentTypesFrom(record.target.desc, classes); - JavaClass returnType = classes.getOrResolve(JavaTypeImporter.importAsmMethodReturnType(record.target.desc).getName()); + JavaClass returnType = classes.getOrResolve(JavaClassDescriptorImporter.importAsmMethodReturnType(record.target.desc).getFullyQualifiedClassName()); return new MethodCallTargetBuilder() .withOwner(targetOwner) .withName(record.target.name) @@ -207,7 +207,7 @@ private static class RawFieldAccessRecordProcessed implements FieldAccessRecord RawFieldAccessRecordProcessed(RawAccessRecord.ForField record, ImportedClasses classes) { this.record = record; this.classes = classes; - targetOwner = this.classes.getOrResolve(record.target.owner.getName()); + targetOwner = this.classes.getOrResolve(record.target.owner.getFullyQualifiedClassName()); callerSupplier = createCallerSupplier(record.caller, classes); } @@ -224,7 +224,7 @@ public JavaCodeUnit getCaller() { @Override public FieldAccessTarget getTarget() { Supplier> fieldSupplier = new FieldTargetSupplier(targetOwner.getAllFields(), record.target); - JavaClass fieldType = classes.getOrResolve(JavaTypeImporter.importAsmType(record.target.desc).getName()); + JavaClass fieldType = classes.getOrResolve(JavaClassDescriptorImporter.importAsmType(record.target.desc).getFullyQualifiedClassName()); return new FieldAccessTargetBuilder() .withOwner(targetOwner) .withName(record.target.name) @@ -290,8 +290,8 @@ private static Optional uniqueTargetIn(Collection collection) { private static JavaClassList getArgumentTypesFrom(String descriptor, ImportedClasses classes) { List paramTypes = new ArrayList<>(); - for (JavaType type : JavaTypeImporter.importAsmMethodArgumentTypes(descriptor)) { - paramTypes.add(classes.getOrResolve(type.getName())); + for (JavaClassDescriptor type : JavaClassDescriptorImporter.importAsmMethodArgumentTypes(descriptor)) { + paramTypes.add(classes.getOrResolve(type.getFullyQualifiedClassName())); } return createJavaClassList(paramTypes); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java index dd78c5da88..d4c13f3ed2 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java @@ -113,7 +113,7 @@ JavaClasses complete() { private void ensureCallTargetsArePresent() { for (RawAccessRecord record : importRecord.getAccessRecords()) { - classes.ensurePresent(record.target.owner.getName()); + classes.ensurePresent(record.target.owner.getFullyQualifiedClassName()); } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java index 96693399fc..2d14e5c79b 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java @@ -38,6 +38,7 @@ import com.tngtech.archunit.core.domain.Formatters; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaClassList; import com.tngtech.archunit.core.domain.JavaCodeUnit; import com.tngtech.archunit.core.domain.JavaConstructor; @@ -51,7 +52,6 @@ import com.tngtech.archunit.core.domain.JavaMethodCall; import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.core.domain.JavaStaticInitializer; -import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.Source; import com.tngtech.archunit.core.domain.ThrowsClause; import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder.ValueBuilder; @@ -196,18 +196,18 @@ public final OUTPUT build(JavaClass owner, ClassesByTypeName importedClasses) { @Internal public static final class JavaFieldBuilder extends JavaMemberBuilder { - private JavaType type; + private JavaClassDescriptor type; JavaFieldBuilder() { } - JavaFieldBuilder withType(JavaType type) { + JavaFieldBuilder withType(JavaClassDescriptor type) { this.type = type; return self(); } public JavaClass getType() { - return get(type.getName()); + return get(type.getFullyQualifiedClassName()); } @Override @@ -218,30 +218,30 @@ JavaField construct(JavaFieldBuilder builder, ClassesByTypeName importedClasses) @Internal public abstract static class JavaCodeUnitBuilder> extends JavaMemberBuilder { - private JavaType returnType; - private List parameters; - private List throwsDeclarations; + private JavaClassDescriptor returnType; + private List parameters; + private List throwsDeclarations; private JavaCodeUnitBuilder() { } - SELF withReturnType(JavaType type) { + SELF withReturnType(JavaClassDescriptor type) { returnType = type; return self(); } - SELF withParameters(List parameters) { + SELF withParameters(List parameters) { this.parameters = parameters; return self(); } - SELF withThrowsClause(List throwsDeclarations) { + SELF withThrowsClause(List throwsDeclarations) { this.throwsDeclarations = throwsDeclarations; return self(); } public JavaClass getReturnType() { - return get(returnType.getName()); + return get(returnType.getFullyQualifiedClassName()); } public JavaClassList getParameters() { @@ -252,10 +252,10 @@ public ThrowsClause getThrowsClause( return createThrowsClause(codeUnit, asJavaClasses(this.throwsDeclarations)); } - private List asJavaClasses(List javaTypes) { + private List asJavaClasses(List descriptors) { ImmutableList.Builder result = ImmutableList.builder(); - for (JavaType javaType : javaTypes) { - result.add(get(javaType.getName())); + for (JavaClassDescriptor javaClassDescriptor : descriptors) { + result.add(get(javaClassDescriptor.getFullyQualifiedClassName())); } return result.build(); } @@ -319,7 +319,7 @@ JavaConstructor construct(JavaConstructorBuilder builder, ClassesByTypeName impo public static final class JavaClassBuilder { private Optional sourceDescriptor = Optional.absent(); private Optional sourceFileName = Optional.absent(); - private JavaType javaType; + private JavaClassDescriptor descriptor; private boolean isInterface; private boolean isEnum; private boolean isAnonymousClass; @@ -339,8 +339,8 @@ JavaClassBuilder withSourceFileName(String sourceFileName) { return this; } - JavaClassBuilder withType(JavaType javaType) { - this.javaType = javaType; + JavaClassBuilder withDescriptor(JavaClassDescriptor descriptor) { + this.descriptor = descriptor; return this; } @@ -370,7 +370,7 @@ JavaClassBuilder withModifiers(Set modifiers) { } JavaClassBuilder withSimpleName(String simpleName) { - this.javaType = javaType.withSimpleName(simpleName); + this.descriptor = descriptor.withSimpleClassName(simpleName); return this; } @@ -384,8 +384,8 @@ public Optional getSource() { : Optional.absent(); } - public JavaType getJavaType() { - return javaType; + public JavaClassDescriptor getDescriptor() { + return descriptor; } public boolean isInterface() { @@ -411,19 +411,19 @@ public Set getModifiers() { @Internal public static final class JavaAnnotationBuilder { - private JavaType type; + private JavaClassDescriptor type; private final Map values = new LinkedHashMap<>(); private ClassesByTypeName importedClasses; JavaAnnotationBuilder() { } - JavaAnnotationBuilder withType(JavaType type) { + JavaAnnotationBuilder withType(JavaClassDescriptor type) { this.type = type; return this; } - JavaType getJavaType() { + JavaClassDescriptor getTypeDescriptor() { return type; } @@ -433,7 +433,7 @@ JavaAnnotationBuilder addProperty(String key, ValueBuilder valueBuilder) { } public JavaClass getType() { - return importedClasses.get(type.getName()); + return importedClasses.get(type.getFullyQualifiedClassName()); } public Map getValues(T owner) { @@ -449,7 +449,7 @@ public Map getValues(T owner) { } private void addDefaultValues(ImmutableMap.Builder result, ClassesByTypeName importedClasses) { - for (JavaMethod method : importedClasses.get(type.getName()).getMethods()) { + for (JavaMethod method : importedClasses.get(type.getFullyQualifiedClassName()).getMethods()) { if (!values.containsKey(method.getName()) && method.getDefaultValue().isPresent()) { result.put(method.getName(), method.getDefaultValue().get()); } @@ -487,13 +487,13 @@ Optional build(T owner, ClassesByTypeName imp @Internal public static final class JavaStaticInitializerBuilder extends JavaCodeUnitBuilder { JavaStaticInitializerBuilder() { - withReturnType(JavaType.From.name(void.class.getName())); - withParameters(Collections.emptyList()); + withReturnType(JavaClassDescriptor.From.name(void.class.getName())); + withParameters(Collections.emptyList()); withName(JavaStaticInitializer.STATIC_INITIALIZER_NAME); withDescriptor("()V"); withAnnotations(Collections.emptySet()); withModifiers(Collections.emptySet()); - withThrowsClause(Collections.emptyList()); + withThrowsClause(Collections.emptyList()); } @Override diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ImportedClasses.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ImportedClasses.java index fe0730723c..bf164bc0e4 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ImportedClasses.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ImportedClasses.java @@ -23,8 +23,8 @@ import com.google.common.collect.Sets; import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaModifier; -import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.importer.resolvers.ClassResolver; import static com.tngtech.archunit.core.domain.JavaModifier.ABSTRACT; @@ -84,14 +84,14 @@ public JavaClass get(String typeName) { } private static JavaClass simpleClassOf(String typeName) { - JavaType type = JavaType.From.name(typeName); - DomainBuilders.JavaClassBuilder builder = new DomainBuilders.JavaClassBuilder().withType(type); - addModifiersIfPossible(builder, type); + JavaClassDescriptor descriptor = JavaClassDescriptor.From.name(typeName); + DomainBuilders.JavaClassBuilder builder = new DomainBuilders.JavaClassBuilder().withDescriptor(descriptor); + addModifiersIfPossible(builder, descriptor); return builder.build(); } - private static void addModifiersIfPossible(DomainBuilders.JavaClassBuilder builder, JavaType type) { - if (type.isPrimitive() || type.isArray()) { + private static void addModifiersIfPossible(DomainBuilders.JavaClassBuilder builder, JavaClassDescriptor descriptor) { + if (descriptor.isPrimitive() || descriptor.isArray()) { builder.withModifiers(PRIMITIVE_AND_ARRAY_TYPE_MODIFIERS); } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaTypeImporter.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporter.java similarity index 70% rename from archunit/src/main/java/com/tngtech/archunit/core/importer/JavaTypeImporter.java rename to archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporter.java index 7fd076bd75..1533630d2e 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaTypeImporter.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporter.java @@ -18,39 +18,39 @@ import java.util.List; import com.google.common.collect.ImmutableList; -import com.tngtech.archunit.core.domain.JavaType; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import org.objectweb.asm.Type; -class JavaTypeImporter { +class JavaClassDescriptorImporter { /** * Takes an 'internal' ASM object type name, i.e. the class name but with slashes instead of periods, * i.e. java/lang/Object (note that this is not a descriptor like Ljava/lang/Object;) */ - static JavaType createFromAsmObjectTypeName(String objectTypeName) { + static JavaClassDescriptor createFromAsmObjectTypeName(String objectTypeName) { return importAsmType(Type.getObjectType(objectTypeName)); } - static JavaType importAsmType(Type type) { - return JavaType.From.name(type.getClassName()); + static JavaClassDescriptor importAsmType(Type type) { + return JavaClassDescriptor.From.name(type.getClassName()); } static Object importAsmTypeIfPossible(Object value) { return value instanceof Type ? importAsmType((Type) value) : value; } - static JavaType importAsmType(String typeDescriptor) { + static JavaClassDescriptor importAsmType(String typeDescriptor) { return importAsmType(Type.getType(typeDescriptor)); } - static List importAsmMethodArgumentTypes(String methodDescriptor) { - ImmutableList.Builder result = ImmutableList.builder(); + static List importAsmMethodArgumentTypes(String methodDescriptor) { + ImmutableList.Builder result = ImmutableList.builder(); for (Type type : Type.getArgumentTypes(methodDescriptor)) { result.add(importAsmType(type)); } return result.build(); } - static JavaType importAsmMethodReturnType(String methodDescriptor) { + static JavaClassDescriptor importAsmMethodReturnType(String methodDescriptor) { return importAsmType(Type.getReturnType(methodDescriptor)); } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java index ecb6396651..70b344ee23 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java @@ -38,10 +38,10 @@ import com.tngtech.archunit.core.MayResolveTypesViaReflection; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaEnumConstant; import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.core.domain.JavaModifier; -import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder.ValueBuilder; import com.tngtech.archunit.core.importer.RawAccessRecord.CodeUnit; import org.objectweb.asm.AnnotationVisitor; @@ -98,8 +98,8 @@ public void visitSource(String source, String debug) { @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { LOG.debug("Analyzing class '{}'", name); - JavaType javaType = JavaTypeImporter.createFromAsmObjectTypeName(name); - if (alreadyImported(javaType)) { + JavaClassDescriptor descriptor = JavaClassDescriptorImporter.createFromAsmObjectTypeName(name); + if (alreadyImported(descriptor)) { return; } @@ -112,17 +112,17 @@ public void visit(int version, int access, String name, String signature, String javaClassBuilder = new DomainBuilders.JavaClassBuilder() .withSourceDescriptor(sourceDescriptor) - .withType(javaType) + .withDescriptor(descriptor) .withInterface(opCodeForInterfaceIsPresent) .withEnum(opCodeForEnumIsPresent) .withModifiers(JavaModifier.getModifiersForClass(access)); - className = javaType.getName(); + className = descriptor.getFullyQualifiedClassName(); declarationHandler.onNewClass(className, superClassName, interfaceNames); } - private boolean alreadyImported(JavaType javaType) { - return !declarationHandler.isNew(javaType.getName()); + private boolean alreadyImported(JavaClassDescriptor descriptor) { + return !declarationHandler.isNew(descriptor.getFullyQualifiedClassName()); } // NOTE: For some reason ASM claims superName == java/lang/Object for Interfaces??? @@ -203,7 +203,7 @@ public FieldVisitor visitField(int access, String name, String desc, String sign DomainBuilders.JavaFieldBuilder fieldBuilder = new DomainBuilders.JavaFieldBuilder() .withName(name) - .withType(JavaTypeImporter.importAsmType(desc)) + .withType(JavaClassDescriptorImporter.importAsmType(desc)) .withModifiers(JavaModifier.getModifiersForField(access)) .withDescriptor(desc); declarationHandler.onDeclaredField(fieldBuilder); @@ -217,7 +217,7 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si } LOG.trace("Analyzing method {}.{}:{}", className, name, desc); - List parameters = JavaTypeImporter.importAsmMethodArgumentTypes(desc); + List parameters = JavaClassDescriptorImporter.importAsmMethodArgumentTypes(desc); accessHandler.setContext(new CodeUnit(name, namesOf(parameters), className)); DomainBuilders.JavaCodeUnitBuilder codeUnitBuilder = addCodeUnitBuilder(name); @@ -225,18 +225,18 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si .withName(name) .withModifiers(JavaModifier.getModifiersForMethod(access)) .withParameters(parameters) - .withReturnType(JavaTypeImporter.importAsmMethodReturnType(desc)) + .withReturnType(JavaClassDescriptorImporter.importAsmMethodReturnType(desc)) .withDescriptor(desc) .withThrowsClause(typesFrom(exceptions)); return new MethodProcessor(className, accessHandler, codeUnitBuilder); } - private List typesFrom(String[] throwsDeclarations) { - List result = new ArrayList<>(); + private List typesFrom(String[] throwsDeclarations) { + List result = new ArrayList<>(); if (throwsDeclarations != null) { for (String throwsDeclaration : throwsDeclarations) { - result.add(JavaTypeImporter.createFromAsmObjectTypeName(throwsDeclaration)); + result.add(JavaClassDescriptorImporter.createFromAsmObjectTypeName(throwsDeclaration)); } } return result; @@ -277,10 +277,10 @@ public void visitEnd() { LOG.trace("Done analyzing {}", className); } - private static List namesOf(Iterable types) { + private static List namesOf(Iterable descriptors) { ImmutableList.Builder result = ImmutableList.builder(); - for (JavaType type : types) { - result.add(type.getName()); + for (JavaClassDescriptor descriptor : descriptors) { + result.add(descriptor.getFullyQualifiedClassName()); } return result.build(); } @@ -475,7 +475,7 @@ public void visitEnd() { } private static DomainBuilders.JavaAnnotationBuilder annotationBuilderFor(String desc) { - return new DomainBuilders.JavaAnnotationBuilder().withType(JavaTypeImporter.importAsmType(desc)); + return new DomainBuilders.JavaAnnotationBuilder().withType(JavaClassDescriptorImporter.importAsmType(desc)); } private static class AnnotationProcessor extends AnnotationVisitor { @@ -508,7 +508,7 @@ public AnnotationVisitor visitArray(final String name) { return new AnnotationArrayProcessor(new AnnotationArrayContext() { @Override public String getDeclaringAnnotationTypeName() { - return annotationBuilder.getJavaType().getName(); + return annotationBuilder.getTypeDescriptor().getFullyQualifiedClassName(); } @Override @@ -669,13 +669,13 @@ private Optional> determineComponentTypeFromReturnValue(JavaMethod meth @MayResolveTypesViaReflection(reason = "Resolving primitives does not really use reflection") private Optional> resolveComponentTypeFrom(String arrayTypeName) { - JavaType type = JavaType.From.name(arrayTypeName); - JavaType componentType = getComponentType(type); + JavaClassDescriptor descriptor = JavaClassDescriptor.From.name(arrayTypeName); + JavaClassDescriptor componentType = getComponentType(descriptor); if (componentType.isPrimitive()) { return Optional.>of(componentType.resolveClass()); } - if (String.class.getName().equals(componentType.getName())) { + if (String.class.getName().equals(componentType.getFullyQualifiedClassName())) { return Optional.>of(String.class); } @@ -687,10 +687,10 @@ private Optional> resolveComponentTypeFrom(String arrayTypeName) { return Optional.>of(Object.class); } - private JavaType getComponentType(JavaType type) { - Optional result = type.tryGetComponentType(); + private JavaClassDescriptor getComponentType(JavaClassDescriptor descriptor) { + Optional result = descriptor.tryGetComponentType(); checkState(result.isPresent(), "Couldn't determine component type of array return type %s, " + - "this is most likely a bug", type.getName()); + "this is most likely a bug", descriptor.getFullyQualifiedClassName()); return result.get(); } @@ -711,7 +711,7 @@ private static ValueBuilder javaEnumBuilder(final String desc, final String valu public Optional build(T owner, ClassesByTypeName importedClasses) { return Optional.of( new DomainBuilders.JavaEnumConstantBuilder() - .withDeclaringClass(importedClasses.get(JavaTypeImporter.importAsmType(desc).getName())) + .withDeclaringClass(importedClasses.get(JavaClassDescriptorImporter.importAsmType(desc).getFullyQualifiedClassName())) .withName(value) .build()); } @@ -720,12 +720,12 @@ public Optional build(T owner, ClassesByTypeN private static class AnnotationTypeConversion { static ValueBuilder convert(Object input) { - final Object value = JavaTypeImporter.importAsmTypeIfPossible(input); - if (value instanceof JavaType) { + final Object value = JavaClassDescriptorImporter.importAsmTypeIfPossible(input); + if (value instanceof JavaClassDescriptor) { return new ValueBuilder() { @Override public Optional build(T owner, ClassesByTypeName importedClasses) { - return Optional.of(importedClasses.get(((JavaType) value).getName())); + return Optional.of(importedClasses.get(((JavaClassDescriptor) value).getFullyQualifiedClassName())); } }; } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java index 229fa06508..4ec15086d4 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java @@ -27,12 +27,12 @@ import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaCodeUnit; import com.tngtech.archunit.core.domain.JavaConstructor; import com.tngtech.archunit.core.domain.JavaField; import com.tngtech.archunit.core.domain.JavaFieldAccess.AccessType; import com.tngtech.archunit.core.domain.JavaMethod; -import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.properties.HasDescriptor; import com.tngtech.archunit.core.domain.properties.HasName; import com.tngtech.archunit.core.domain.properties.HasOwner; @@ -145,12 +145,12 @@ && getParameters().equals(method.getRawParameterTypes().getNames()) } abstract static class TargetInfo { - final JavaType owner; + final JavaClassDescriptor owner; final String name; final String desc; TargetInfo(String owner, String name, String desc) { - this.owner = JavaTypeImporter.createFromAsmObjectTypeName(owner); + this.owner = JavaClassDescriptorImporter.createFromAsmObjectTypeName(owner); this.name = name; this.desc = desc; } @@ -159,7 +159,7 @@ > boolean matches(T memb if (!name.equals(member.getName()) || !desc.equals(member.getDescriptor())) { return false; } - return owner.getName().equals(member.getOwner().getName()) || + return owner.getFullyQualifiedClassName().equals(member.getOwner().getName()) || classHierarchyFrom(member).hasExactlyOneMatchFor(this); } @@ -198,15 +198,15 @@ public boolean equals(Object obj) { @Override public String toString() { - return getClass().getSimpleName() + "{owner='" + owner.getName() + "', name='" + name + "', desc='" + desc + "'}"; + return getClass().getSimpleName() + "{owner='" + owner.getFullyQualifiedClassName() + "', name='" + name + "', desc='" + desc + "'}"; } private static class ClassHierarchyPath { private final List path = new ArrayList<>(); - private ClassHierarchyPath(JavaType childType, JavaClass parent) { + private ClassHierarchyPath(JavaClassDescriptor childType, JavaClass parent) { Set classesToSearchForChild = Sets.union(singleton(parent), parent.getAllSubClasses()); - Optional child = tryFind(classesToSearchForChild, nameMatching(quote(childType.getName()))); + Optional child = tryFind(classesToSearchForChild, nameMatching(quote(childType.getFullyQualifiedClassName()))); if (child.isPresent()) { createPath(child.get(), parent); } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationProxyTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationProxyTest.java index f244725221..5ac99da59e 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationProxyTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/AnnotationProxyTest.java @@ -241,7 +241,7 @@ public void wrong_annotation_type_is_rejected() { public void array_is_converted_to_the_correct_type() { ImportTestUtils.ImportedTestClasses importedClasses = simpleImportedClasses(); JavaAnnotation annotation = new JavaAnnotationTestBuilder() - .withType(JavaType.From.name(TestAnnotation.class.getName())) + .withType(JavaClassDescriptor.From.name(TestAnnotation.class.getName())) .addProperty("types", new Object[0]) .addProperty("enumConstants", new Object[0]) .addProperty("subAnnotations", new Object[0]) @@ -333,9 +333,9 @@ public String toString() { Class typeWithDefault() default Serializable.class; - Class[] types(); + Class[] types(); - Class[] typesWithDefault() default {Serializable.class, String.class}; + Class[] typesWithDefault() default {Serializable.class, String.class}; TestEnum enumConstant(); diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassDescriptorTest.java similarity index 76% rename from archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeTest.java rename to archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassDescriptorTest.java index db3621b14c..99dfdfc574 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassDescriptorTest.java @@ -17,14 +17,14 @@ import static com.tngtech.archunit.testutil.Assertions.assertThat; @RunWith(DataProviderRunner.class) -public class JavaTypeTest { +public class JavaClassDescriptorTest { @Rule public ExpectedException thrown = ExpectedException.none(); @Test @UseDataProvider("primitives") public void primitive_types_by_name_and_descriptor(String name, Class expected) { - JavaType primitiveType = JavaType.From.name(name); + JavaClassDescriptor primitiveType = JavaClassDescriptor.From.name(name); assertThat(primitiveType.isPrimitive()).isTrue(); assertThat(primitiveType.isArray()).isFalse(); assertThat(primitiveType.tryGetComponentType()).isAbsent(); @@ -35,17 +35,17 @@ public void primitive_types_by_name_and_descriptor(String name, Class expecte @Test @UseDataProvider("arrays") public void array_types_by_name_and_canonical_name(String name, Class expected) { - JavaType arrayType = JavaType.From.name(name); + JavaClassDescriptor arrayType = JavaClassDescriptor.From.name(name); assertThat(arrayType.isPrimitive()).isFalse(); assertThat(arrayType.isArray()).isTrue(); - assertThat(arrayType.tryGetComponentType()).contains(JavaType.From.name(expected.getComponentType().getName())); + assertThat(arrayType.tryGetComponentType()).contains(JavaClassDescriptor.From.name(expected.getComponentType().getName())); assertThat(arrayType).isEquivalentTo(expected); } @Test public void object_name() { - JavaType objectType = JavaType.From.name(Object.class.getName()); + JavaClassDescriptor objectType = JavaClassDescriptor.From.name(Object.class.getName()); assertThat(objectType.isPrimitive()).isFalse(); assertThat(objectType.isArray()).isFalse(); assertThat(objectType.tryGetComponentType()).isAbsent(); @@ -56,24 +56,24 @@ public void object_name() { @Test @UseDataProvider(value = "primitives") public void resolves_primitive_type_names(String name, Class expected) { - assertThat(JavaType.From.name(name).resolveClass()).isEqualTo(expected); + assertThat(JavaClassDescriptor.From.name(name).resolveClass()).isEqualTo(expected); } @Test @UseDataProvider(value = "arrays") public void resolves_arrays_type_names(String name, Class expected) { - assertThat(JavaType.From.name(name).resolveClass()).isEqualTo(expected); + assertThat(JavaClassDescriptor.From.name(name).resolveClass()).isEqualTo(expected); } @Test public void resolves_standard_class_name() { - assertThat(JavaType.From.name(getClass().getName()).resolveClass()).isEqualTo(getClass()); + assertThat(JavaClassDescriptor.From.name(getClass().getName()).resolveClass()).isEqualTo(getClass()); } @Test public void resolving_throws_exception_if_type_doesnt_exist() { thrown.expect(ReflectionException.class); - JavaType.From.name("does.not.exist").resolveClass(); + JavaClassDescriptor.From.name("does.not.exist").resolveClass(); } @Test @@ -81,28 +81,28 @@ public void anonymous_type() { Serializable input = new Serializable() { }; - JavaType anonymousType = JavaType.From.name(input.getClass().getName()); + JavaClassDescriptor anonymousType = JavaClassDescriptor.From.name(input.getClass().getName()); - assertThat(anonymousType.getName()).isEqualTo(getClass().getName() + "$1"); - assertThat(anonymousType.getSimpleName()).isEmpty(); + assertThat(anonymousType.getFullyQualifiedClassName()).isEqualTo(getClass().getName() + "$1"); + assertThat(anonymousType.getSimpleClassName()).isEmpty(); assertThat(anonymousType.getPackageName()).isEqualTo(getClass().getPackage().getName()); } @Test public void special_chars_type() { - JavaType specialChars = JavaType.From.name("s_123_wéirdâ.Weird_αρετη_Type"); + JavaClassDescriptor specialChars = JavaClassDescriptor.From.name("s_123_wéirdâ.Weird_αρετη_Type"); - assertThat(specialChars.getName()).isEqualTo("s_123_wéirdâ.Weird_αρετη_Type"); - assertThat(specialChars.getSimpleName()).isEqualTo("Weird_αρετη_Type"); + assertThat(specialChars.getFullyQualifiedClassName()).isEqualTo("s_123_wéirdâ.Weird_αρετη_Type"); + assertThat(specialChars.getSimpleClassName()).isEqualTo("Weird_αρετη_Type"); assertThat(specialChars.getPackageName()).isEqualTo("s_123_wéirdâ"); } @Test public void default_package() { - JavaType specialChars = JavaType.From.name("DefaultPackage"); + JavaClassDescriptor specialChars = JavaClassDescriptor.From.name("DefaultPackage"); - assertThat(specialChars.getName()).isEqualTo("DefaultPackage"); - assertThat(specialChars.getSimpleName()).isEqualTo("DefaultPackage"); + assertThat(specialChars.getFullyQualifiedClassName()).isEqualTo("DefaultPackage"); + assertThat(specialChars.getSimpleClassName()).isEqualTo("DefaultPackage"); assertThat(specialChars.getPackageName()).isEmpty(); } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java index a5ad7a5a7b..9bc3b45e3f 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java @@ -26,6 +26,7 @@ import com.tngtech.archunit.core.domain.ImportContext; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaCodeUnit; import com.tngtech.archunit.core.domain.JavaConstructor; import com.tngtech.archunit.core.domain.JavaConstructorCall; @@ -36,7 +37,6 @@ import com.tngtech.archunit.core.domain.JavaMethodCall; import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.core.domain.JavaStaticInitializer; -import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.ThrowsDeclaration; import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodCallBuilder; import org.objectweb.asm.Type; @@ -66,7 +66,7 @@ private static Set> methodBuilders = new HashSet<>(); for (Method method : inputClass.getDeclaredMethods()) { methodBuilders.add(new DomainBuilders.JavaMethodBuilder() - .withReturnType(JavaType.From.name(method.getReturnType().getName())) + .withReturnType(JavaClassDescriptor.From.name(method.getReturnType().getName())) .withParameters(typesFrom(method.getParameterTypes())) .withName(method.getName()) .withDescriptor(Type.getMethodDescriptor(method)) @@ -92,7 +92,7 @@ private static Set> constructorBuilders = new HashSet<>(); for (Constructor constructor : inputClass.getDeclaredConstructors()) { constructorBuilders.add(new DomainBuilders.JavaConstructorBuilder() - .withReturnType(JavaType.From.name(void.class.getName())) + .withReturnType(JavaClassDescriptor.From.name(void.class.getName())) .withParameters(typesFrom(constructor.getParameterTypes())) .withName(CONSTRUCTOR_NAME) .withDescriptor(Type.getConstructorDescriptor(constructor)) @@ -123,7 +123,7 @@ private static Set javaAnnotationBuildersF private static JavaClass javaClassFor(Class owner) { return new DomainBuilders.JavaClassBuilder() - .withType(JavaType.From.name(owner.getName())) + .withDescriptor(JavaClassDescriptor.From.name(owner.getName())) .withInterface(owner.isInterface()) .withModifiers(JavaModifier.getModifiersForClass(owner.getModifiers())) .build(); @@ -215,10 +215,10 @@ public static JavaFieldAccess newFieldAccess(JavaMethod origin, JavaField target .build(); } - private static List typesFrom(Class[] classes) { - List result = new ArrayList<>(); + private static List typesFrom(Class[] classes) { + List result = new ArrayList<>(); for (Class clazz : classes) { - result.add(JavaType.From.name(clazz.getName())); + result.add(JavaClassDescriptor.From.name(clazz.getName())); } return result; } @@ -269,7 +269,7 @@ private static JavaAnnotation javaAnnotationFrom(Annotation annotatio private static DomainBuilders.JavaAnnotationBuilder javaAnnotationBuilderFrom(Annotation annotation, Class annotatedClass, ClassesByTypeName importedClasses) { DomainBuilders.JavaAnnotationBuilder builder = new DomainBuilders.JavaAnnotationBuilder() - .withType(JavaType.From.name(annotation.annotationType().getName())); + .withType(JavaClassDescriptor.From.name(annotation.annotationType().getName())); for (Map.Entry entry : mapOf(annotation, annotatedClass, importedClasses).entrySet()) { builder.addProperty(entry.getKey(), DomainBuilders.JavaAnnotationBuilder.ValueBuilder.ofFinished(entry.getValue())); } @@ -333,7 +333,7 @@ void register(JavaClass clazz) { public JavaClass get(String typeName) { return imported.containsKey(typeName) ? imported.get(typeName) : - importNew(JavaType.From.name(typeName).resolveClass()); + importNew(JavaClassDescriptor.From.name(typeName).resolveClass()); } private JavaClass importNew(Class owner) { diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaAnnotationTestBuilder.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaAnnotationTestBuilder.java index ff7d104fb2..6e4d95c9e8 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaAnnotationTestBuilder.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaAnnotationTestBuilder.java @@ -2,14 +2,14 @@ import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; -import com.tngtech.archunit.core.domain.JavaType; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder.ValueBuilder; public class JavaAnnotationTestBuilder { private final JavaAnnotationBuilder delegate = new JavaAnnotationBuilder(); - public JavaAnnotationTestBuilder withType(JavaType type) { + public JavaAnnotationTestBuilder withType(JavaClassDescriptor type) { delegate.withType(type); return this; } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporterTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporterTest.java new file mode 100644 index 0000000000..27bcd0e7e9 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporterTest.java @@ -0,0 +1,27 @@ +package com.tngtech.archunit.core.importer; + +import com.tngtech.archunit.core.domain.JavaClassDescriptor; +import org.junit.Test; +import org.objectweb.asm.Type; + +import static com.tngtech.archunit.testutil.Assertions.assertThat; + +public class JavaClassDescriptorImporterTest { + + @Test + public void asm_object_type() { + JavaClassDescriptor objectType = JavaClassDescriptorImporter.createFromAsmObjectTypeName("java/lang/Object"); + + assertThat(objectType).isEquivalentTo(Object.class); + } + + @Test + public void asm_Type() throws NoSuchMethodException { + Type toStringType = Type.getReturnType(Object.class.getDeclaredMethod("toString")); + + JavaClassDescriptor toStringDescriptor = JavaClassDescriptorImporter.importAsmType(toStringType); + + assertThat(toStringDescriptor.getFullyQualifiedClassName()).isEqualTo(String.class.getName()); + assertThat(toStringDescriptor.resolveClass()).isEqualTo(String.class); + } +} \ No newline at end of file diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaTypeImporterTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaTypeImporterTest.java deleted file mode 100644 index a6fd6555a1..0000000000 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/JavaTypeImporterTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.tngtech.archunit.core.importer; - -import com.tngtech.archunit.core.domain.JavaType; -import org.junit.Test; -import org.objectweb.asm.Type; - -import static com.tngtech.archunit.testutil.Assertions.assertThat; - -public class JavaTypeImporterTest { - - @Test - public void asm_object_type() { - JavaType objectType = JavaTypeImporter.createFromAsmObjectTypeName("java/lang/Object"); - - assertThat(objectType).isEquivalentTo(Object.class); - } - - @Test - public void asm_Type() throws NoSuchMethodException { - Type toStringType = Type.getReturnType(Object.class.getDeclaredMethod("toString")); - - JavaType toStringJavaType = JavaTypeImporter.importAsmType(toStringType); - - assertThat(toStringJavaType.getName()).isEqualTo(String.class.getName()); - assertThat(toStringJavaType.resolveClass()).isEqualTo(String.class); - } -} \ No newline at end of file diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java b/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java index 1b47b47a5e..7d194e4062 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java @@ -23,6 +23,7 @@ import com.tngtech.archunit.core.domain.Dependency; import com.tngtech.archunit.core.domain.JavaAccess; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaClassList; import com.tngtech.archunit.core.domain.JavaCodeUnit; import com.tngtech.archunit.core.domain.JavaConstructor; @@ -35,7 +36,6 @@ import com.tngtech.archunit.core.domain.JavaMethodCall; import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.core.domain.JavaPackage; -import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.ThrowsClause; import com.tngtech.archunit.core.domain.ThrowsDeclaration; import com.tngtech.archunit.lang.ArchCondition; @@ -158,8 +158,8 @@ public static JavaEnumConstantsAssertion assertThat(JavaEnumConstant[] enumConst return new JavaEnumConstantsAssertion(enumConstants); } - public static JavaTypeAssertion assertThat(JavaType javaType) { - return new JavaTypeAssertion(javaType); + public static JavaClassDescriptorAssertion assertThat(JavaClassDescriptor javaClassDescriptor) { + return new JavaClassDescriptorAssertion(javaClassDescriptor); } public static ThrowsDeclarationAssertion assertThat(ThrowsDeclaration throwsDeclaration) { @@ -541,14 +541,14 @@ protected ConstructorCallAssertion newAssertion(JavaConstructorCall call) { } } - public static class JavaTypeAssertion extends AbstractObjectAssert { - private JavaTypeAssertion(JavaType actual) { - super(actual, JavaTypeAssertion.class); + public static class JavaClassDescriptorAssertion extends AbstractObjectAssert { + private JavaClassDescriptorAssertion(JavaClassDescriptor actual) { + super(actual, JavaClassDescriptorAssertion.class); } public void isEquivalentTo(Class clazz) { - assertThat(actual.getName()).as("name").isEqualTo(clazz.getName()); - assertThat(actual.getSimpleName()).as("simple name").isEqualTo(clazz.getSimpleName()); + assertThat(actual.getFullyQualifiedClassName()).as("name").isEqualTo(clazz.getName()); + assertThat(actual.getSimpleClassName()).as("simple name").isEqualTo(clazz.getSimpleName()); String expectedPackageName = getExpectedPackageName(clazz); assertThat(actual.getPackageName()).as("package").isEqualTo(expectedPackageName); } From 79c103e789d04428fd8f25e6768c5940d740ffbe Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Sun, 1 Mar 2020 21:45:43 +0100 Subject: [PATCH 030/115] introduce `JavaType` as super type of `JavaClass` Similar to the Reflection API we will need a common super type of `JavaClass` and `TypeVariable` to describe generic bounds. Signed-off-by: Peter Gafert --- .../archunit/core/domain/JavaClass.java | 2 +- .../archunit/core/domain/JavaType.java | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java index 941d75711f..6fa36f22f6 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java @@ -61,7 +61,7 @@ import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; -public class JavaClass implements HasName.AndFullName, HasAnnotations, HasModifiers, HasSourceCodeLocation { +public class JavaClass implements JavaType, HasName.AndFullName, HasAnnotations, HasModifiers, HasSourceCodeLocation { private final Optional source; private final SourceCodeLocation sourceCodeLocation; private final JavaClassDescriptor descriptor; diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java new file mode 100644 index 0000000000..0fbd514a13 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java @@ -0,0 +1,21 @@ +/* + * Copyright 2014-2020 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.core.domain; + +import com.tngtech.archunit.core.domain.properties.HasName; + +public interface JavaType extends HasName { +} From 925bdd344d7397ba028d7b28f9c6a9000e0e43bf Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Sun, 1 Mar 2020 21:50:05 +0100 Subject: [PATCH 031/115] prepare test assertions for `JavaType` Instead of `JavaClass(es)Assertion` we will need `JavaType(s)Assertion` in the future to be able to handle generic test cases. We can easily allow assertions on `JavaType` by simply pre-asserting `instanceof JavaClass` for the relevant cases specific to `JavaClass`. We could introduce a special sub-assertion `JavaClassAssertion` extending `JavaTypeAssertion`, where an assertion of the correct sub type is not necessary anymore, but to me the complexity seems to be fairly low to simply assert the correct sub type where necessary, so I kept it simple and only introduced `JavaType(s)Assertion`. Signed-off-by: Peter Gafert --- .../archunit/junit/ClassCacheTest.java | 10 +- .../archunit/core/domain/JavaClass.java | 2 +- .../core/domain/AccessTargetTest.java | 5 +- .../archunit/core/domain/DependencyTest.java | 37 +-- .../archunit/core/domain/JavaClassTest.java | 25 +- .../archunit/core/domain/JavaClassesTest.java | 6 +- .../archunit/core/domain/JavaPackageTest.java | 15 +- .../core/domain/ThrowsClauseTest.java | 6 +- .../domain/properties/HasReturnTypeTest.java | 6 +- .../core/domain/properties/HasTypeTest.java | 6 +- .../importer/ClassFileImporterSlowTest.java | 43 +-- .../core/importer/ClassFileImporterTest.java | 121 ++++---- .../syntax/elements/GivenClassesThatTest.java | 161 +++++----- .../elements/ShouldClassesThatTest.java | 285 +++++++++--------- .../elements/ShouldOnlyByClassesThatTest.java | 158 +++++----- .../library/dependencies/SlicesTest.java | 8 +- .../tngtech/archunit/testutil/Assertions.java | 151 +--------- .../tngtech/archunit/testutil/TestUtils.java | 25 +- .../JavaClassDescriptorAssertion.java | 20 ++ .../assertion/JavaCodeUnitAssertion.java | 5 +- .../assertion/JavaConstructorAssertion.java | 3 +- .../assertion/JavaFieldAssertion.java | 4 +- .../assertion/JavaMethodAssertion.java | 7 +- .../assertion/JavaPackagesAssertion.java | 15 +- .../testutil/assertion/JavaTypeAssertion.java | 67 ++++ .../assertion/JavaTypesAssertion.java | 89 ++++++ 26 files changed, 674 insertions(+), 606 deletions(-) create mode 100644 archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaClassDescriptorAssertion.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypesAssertion.java diff --git a/archunit-junit/src/test/java/com/tngtech/archunit/junit/ClassCacheTest.java b/archunit-junit/src/test/java/com/tngtech/archunit/junit/ClassCacheTest.java index 8c34920ba1..5a9a42a281 100644 --- a/archunit-junit/src/test/java/com/tngtech/archunit/junit/ClassCacheTest.java +++ b/archunit-junit/src/test/java/com/tngtech/archunit/junit/ClassCacheTest.java @@ -28,7 +28,7 @@ import org.mockito.junit.MockitoRule; import static com.tngtech.archunit.junit.CacheMode.PER_CLASS; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -114,13 +114,13 @@ public void get_all_classes_by_LocationProvider() { .withPackagesRoots(ClassCacheTest.class) .withLocationProviders(TestLocationProviderOfClass_String.class, TestLocationProviderOfClass_Rule.class)); - assertThatClasses(classes).contain(String.class, Rule.class, getClass()); + assertThatTypes(classes).contain(String.class, Rule.class, getClass()); classes = cache.getClassesToAnalyzeFor(TestClassWithLocationProviderUsingTestClass.class, analyzeLocation(LocationOfClass.Provider.class)); - assertThatClasses(classes).contain(String.class); - assertThatClasses(classes).doNotContain(getClass()); + assertThatTypes(classes).contain(String.class); + assertThatTypes(classes).doNotContain(getClass()); } @Test @@ -275,4 +275,4 @@ public Set get(Class testClass) { return Collections.emptySet(); } } -} \ No newline at end of file +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java index 6fa36f22f6..7e4456b20f 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java @@ -1225,7 +1225,7 @@ public static List namesOf(Class... paramTypes) { } @PublicAPI(usage = ACCESS) - public static List namesOf(List> paramTypes) { + public static List namesOf(Iterable> paramTypes) { ArrayList result = new ArrayList<>(); for (Class paramType : paramTypes) { result.add(paramType.getName()); diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/AccessTargetTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/AccessTargetTest.java index 5eacb41545..8a70dfbd7c 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/AccessTargetTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/AccessTargetTest.java @@ -14,6 +14,7 @@ import static com.tngtech.archunit.core.domain.TestUtils.simulateCall; import static com.tngtech.archunit.core.domain.TestUtils.withinImportedClasses; import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; public class AccessTargetTest { @Test @@ -130,7 +131,7 @@ public void no_throws_clause_is_resolved() { assertThat(throwsClause).as("throws clause").isEmpty(); assertThat(throwsClause.getTypes()).isEmpty(); assertThat(throwsClause.getOwner()).isEqualTo(target); - assertThat(throwsClause.getDeclaringClass()).matches(Target.class); + assertThatType(throwsClause.getDeclaringClass()).matches(Target.class); } @Test @@ -197,7 +198,7 @@ private void assertDeclarations(CodeUnitCallTarget target, Class... exception ThrowsClause throwsClause = target.getThrowsClause(); assertThat(throwsClause.getTypes()).matches(exceptionTypes); for (ThrowsDeclaration throwsDeclaration : throwsClause) { - assertThat(throwsDeclaration.getDeclaringClass()).isEqualTo(target.getOwner()); + assertThatType(throwsDeclaration.getDeclaringClass()).isEqualTo(target.getOwner()); assertThat(throwsDeclaration.getOwner()).isEqualTo(target.getThrowsClause()); assertThat(throwsDeclaration.getLocation()).isEqualTo(target); } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/DependencyTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/DependencyTest.java index f2e6baf9c4..a1290e2a0c 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/DependencyTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/DependencyTest.java @@ -24,6 +24,7 @@ import static com.tngtech.archunit.core.domain.TestUtils.importClassesWithContext; import static com.tngtech.archunit.core.domain.TestUtils.simulateCall; import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; @RunWith(DataProviderRunner.class) @@ -33,7 +34,7 @@ public void Dependency_from_access() { JavaMethodCall call = simulateCall().from(getClass(), "toString").to(Object.class, "toString"); Dependency dependency = Dependency.tryCreateFromAccess(call).get(); - assertThat(dependency.getTargetClass()).as("target class").isEqualTo(call.getTargetOwner()); + assertThatType(dependency.getTargetClass()).as("target class").isEqualTo(call.getTargetOwner()); assertThat(dependency.getDescription()) .as("description").isEqualTo(call.getDescription()); } @@ -65,8 +66,8 @@ public void Dependency_from_throws_declaration() { Dependency dependency = Dependency.tryCreateFromThrowsDeclaration(throwsDeclaration).get(); - assertThat(dependency.getOriginClass()).matches(ClassWithDependencyOnThrowable.class); - assertThat(dependency.getTargetClass()).matches(IOException.class); + assertThatType(dependency.getOriginClass()).matches(ClassWithDependencyOnThrowable.class); + assertThatType(dependency.getTargetClass()).matches(IOException.class); assertThat(dependency.getDescription()).as("description") .contains("Method <" + origin.getFullName() + "> throws type <" + IOException.class.getName() + ">"); } @@ -88,8 +89,8 @@ public void Dependency_from_annotation_on_class(JavaClass annotatedClass) { Class annotationClass = annotation.getRawType().reflect(); Dependency dependency = Dependency.tryCreateFromAnnotation(annotation).get(); - assertThat(dependency.getOriginClass()).isEqualTo(annotatedClass); - assertThat(dependency.getTargetClass()).matches(annotationClass); + assertThatType(dependency.getOriginClass()).isEqualTo(annotatedClass); + assertThatType(dependency.getTargetClass()).matches(annotationClass); assertThat(dependency.getDescription()).as("description") .contains("Class <" + annotatedClass.getName() + "> is annotated with <" + annotationClass.getName() + ">"); } @@ -112,8 +113,8 @@ public void Dependency_from_annotation_on_member(JavaMember annotatedMember) { Class annotationClass = annotation.getRawType().reflect(); Dependency dependency = Dependency.tryCreateFromAnnotation(annotation).get(); - assertThat(dependency.getOriginClass()).matches(ClassWithAnnotatedMembers.class); - assertThat(dependency.getTargetClass()).matches(annotationClass); + assertThatType(dependency.getOriginClass()).matches(ClassWithAnnotatedMembers.class); + assertThatType(dependency.getTargetClass()).matches(annotationClass); assertThat(dependency.getDescription()).as("description") .contains(annotatedMember.getDescription() + " is annotated with <" + annotationClass.getName() + ">"); } @@ -125,8 +126,8 @@ public void Dependency_from_class_annotation_member(JavaClass annotatedClass) { JavaClass memberType = ((JavaClass) annotation.get("value").get()); Dependency dependency = Dependency.tryCreateFromAnnotationMember(annotation, memberType).get(); - assertThat(dependency.getOriginClass()).isEqualTo(annotatedClass); - assertThat(dependency.getTargetClass()).isEqualTo(memberType); + assertThatType(dependency.getOriginClass()).isEqualTo(annotatedClass); + assertThatType(dependency.getTargetClass()).isEqualTo(memberType); assertThat(dependency.getDescription()).as("description") .contains("Class <" + annotatedClass.getName() + "> has annotation member of type <" + memberType.getName() + ">"); } @@ -138,8 +139,8 @@ public void Dependency_from_member_annotation_member(JavaMember annotatedMember) JavaClass memberType = ((JavaClass) annotation.get("value").get()); Dependency dependency = Dependency.tryCreateFromAnnotationMember(annotation, memberType).get(); - assertThat(dependency.getOriginClass()).isEqualTo(annotatedMember.getOwner()); - assertThat(dependency.getTargetClass()).isEqualTo(memberType); + assertThatType(dependency.getOriginClass()).isEqualTo(annotatedMember.getOwner()); + assertThatType(dependency.getTargetClass()).isEqualTo(memberType); assertThat(dependency.getDescription()).as("description") .contains(annotatedMember.getDescription() + " has annotation member of type <" + memberType.getName() + ">"); } @@ -198,14 +199,14 @@ public void dependency_predicates_descriptions() { @Test public void functions() { - assertThat(GET_ORIGIN_CLASS.apply(createDependency(Origin.class, Target.class))).matches(Origin.class); - assertThat(GET_TARGET_CLASS.apply(createDependency(Origin.class, Target.class))).matches(Target.class); + assertThatType(GET_ORIGIN_CLASS.apply(createDependency(Origin.class, Target.class))).matches(Origin.class); + assertThatType(GET_TARGET_CLASS.apply(createDependency(Origin.class, Target.class))).matches(Target.class); } private Dependency createDependency(JavaClass origin, JavaClass target) { Dependency dependency = Dependency.fromInheritance(origin, target); - assertThat(dependency.getOriginClass()).as("origin class").isEqualTo(origin); - assertThat(dependency.getTargetClass()).as("target class").isEqualTo(target); + assertThatType(dependency.getOriginClass()).as("origin class").isEqualTo(origin); + assertThatType(dependency.getTargetClass()).as("target class").isEqualTo(target); return dependency; } @@ -257,10 +258,12 @@ private static class SomeMemberType { } @SomeAnnotation(SomeMemberType.class) - private static class ClassWithDependencyOnAnnotation { } + private interface InterfaceWithDependencyOnAnnotation { + } @SomeAnnotation(SomeMemberType.class) - private interface InterfaceWithDependencyOnAnnotation { } + private static class ClassWithDependencyOnAnnotation { + } @SuppressWarnings("unused") private static class ClassWithAnnotatedMembers { diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java index 190e1fce00..fedd522efc 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java @@ -70,7 +70,8 @@ import static com.tngtech.archunit.core.domain.TestUtils.simulateCall; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; import static com.tngtech.archunit.testutil.Assertions.assertThat; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static com.tngtech.archunit.testutil.Conditions.codeUnitWithSignature; import static com.tngtech.archunit.testutil.Conditions.containing; import static com.tngtech.archunit.testutil.ReflectionTestUtils.getHierarchy; @@ -89,7 +90,7 @@ public void finds_array_type() { JavaMethod method = importClassWithContext(IsArrayTestClass.class).getMethod("anArray"); assertThat(method.getRawReturnType().isArray()).isTrue(); - assertThat(method.getRawReturnType().tryGetComponentType().get()).matches(Object.class); + assertThatType(method.getRawReturnType().tryGetComponentType().get()).matches(Object.class); } @Test @@ -108,10 +109,10 @@ public void finds_multidimensional_array_type() { JavaClass twoDimArray = classes.get(ClassAccessingTwoDimensionalArray.class).getField("array").getRawType(); assertThat(oneDimArray.isArray()).isTrue(); - assertThat(oneDimArray.tryGetComponentType().get()).isEqualTo(type); + assertThatType(oneDimArray.tryGetComponentType().get()).isEqualTo(type); assertThat(twoDimArray.isArray()).isTrue(); - assertThat(twoDimArray.tryGetComponentType().get()).isEqualTo(oneDimArray); - assertThat(twoDimArray.tryGetComponentType().get().tryGetComponentType().get()).isEqualTo(type); + assertThatType(twoDimArray.tryGetComponentType().get()).isEqualTo(oneDimArray); + assertThatType(twoDimArray.tryGetComponentType().get().tryGetComponentType().get()).isEqualTo(type); } @Test @@ -131,7 +132,7 @@ class OnlyReferencingMultiDimArray { assertThat(oneDim.getName()).isEqualTo(OnlyReferencingMultiDimArray[].class.getName()); JavaClass original = oneDim.getComponentType(); - assertThat(original).isEqualTo(javaClass); + assertThatType(original).isEqualTo(javaClass); } @Test @@ -143,10 +144,10 @@ public void finds_fields_and_methods() { assertThat(javaClass.getMethods()).hasSize(2); for (JavaField field : javaClass.getFields()) { - assertThat(field.getOwner()).isSameAs(javaClass); + assertThatType(field.getOwner()).isSameAs(javaClass); } for (JavaCodeUnit method : javaClass.getCodeUnits()) { - assertThat(method.getOwner()).isSameAs(javaClass); + assertThatType(method.getOwner()).isSameAs(javaClass); } } @@ -482,7 +483,7 @@ public void direct_dependencies_from_self_finds_correct_set_of_target_types() { Set targets = FluentIterable.from(javaClass.getDirectDependenciesFromSelf()) .transform(toGuava(GET_TARGET_CLASS)).toSet(); - assertThatClasses(targets).matchInAnyOrder( + assertThatTypes(targets).matchInAnyOrder( B.class, AhavingMembersOfTypeB.class, Object.class, String.class, List.class, Serializable.class, SomeSuperClass.class, WithType.class, WithNestedAnnotations.class, OnClass.class, OnMethod.class, @@ -606,16 +607,16 @@ public void direct_dependencies_to_self_finds_correct_set_of_origin_types() { Set origins = getOriginsOfDependenciesTo(classes.get(WithType.class)); - assertThatClasses(origins).matchInAnyOrder(ClassWithAnnotationDependencies.class, ClassWithSelfReferences.class, OnMethodParam.class); + assertThatTypes(origins).matchInAnyOrder(ClassWithAnnotationDependencies.class, ClassWithSelfReferences.class, OnMethodParam.class); origins = getOriginsOfDependenciesTo(classes.get(B.class)); - assertThatClasses(origins).matchInAnyOrder( + assertThatTypes(origins).matchInAnyOrder( ClassWithAnnotationDependencies.class, OnMethodParam.class, AAccessingB.class, AhavingMembersOfTypeB.class); origins = getOriginsOfDependenciesTo(classes.get(SomeEnumAsNestedAnnotationParameter.class)); - assertThatClasses(origins).matchInAnyOrder( + assertThatTypes(origins).matchInAnyOrder( ClassWithAnnotationDependencies.class, WithEnum.class); } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassesTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassesTest.java index 29e1152b4c..77cae703b7 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassesTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassesTest.java @@ -12,7 +12,7 @@ import static com.tngtech.archunit.core.domain.TestUtils.importClassWithContext; import static com.tngtech.archunit.core.domain.TestUtils.importClassesWithContext; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; @@ -37,7 +37,7 @@ public void restriction_on_classes_should_keep_the_original_package_tree() { JavaPackage javaPackage = restrictedClasses.getPackage(SomeClass.class.getPackage().getName()); - assertThatClasses(javaPackage.getClasses()).contain(SomeOtherClass.class); + assertThatTypes(javaPackage.getClasses()).contain(SomeOtherClass.class); } @Test @@ -136,4 +136,4 @@ private static class SomeClass { private static class SomeOtherClass { } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaPackageTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaPackageTest.java index 25028e314f..70dd924535 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaPackageTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaPackageTest.java @@ -35,9 +35,10 @@ import static com.tngtech.archunit.core.domain.JavaPackage.Functions.GET_RELATIVE_NAME; import static com.tngtech.archunit.core.domain.JavaPackage.Functions.GET_SUB_PACKAGES; import static com.tngtech.archunit.testutil.Assertions.assertThat; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; import static com.tngtech.archunit.testutil.Assertions.assertThatDependencies; import static com.tngtech.archunit.testutil.Assertions.assertThatPackages; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static java.util.regex.Pattern.quote; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -105,7 +106,7 @@ public void creates_single_package() { assertThat(javaPackage.getName()).isEqualTo("java.lang"); assertThat(javaPackage.getDescription()).isEqualTo("Package "); assertThat(javaPackage.getRelativeName()).isEqualTo("lang"); - assertThatClasses(javaPackage.getClasses()).contain(Object.class, String.class); + assertThatTypes(javaPackage.getClasses()).contain(Object.class, String.class); } @Test @@ -200,7 +201,7 @@ public void iterates_all_classes() { JavaPackage javaLang = defaultPackage.getPackage("java.lang"); - assertThatClasses(javaLang.getAllClasses()).contain(Object.class, String.class, Annotation.class, Field.class); + assertThatTypes(javaLang.getAllClasses()).contain(Object.class, String.class, Annotation.class, Field.class); } @Test @@ -226,7 +227,7 @@ public void visit(JavaClass javaClass) { } }); - assertThatClasses(visitedClasses).contain(String.class, Serializable.class, Security.class); + assertThatTypes(visitedClasses).contain(String.class, Serializable.class, Security.class); for (JavaClass visitedClass : visitedClasses) { assertThat(visitedClass.getSimpleName()).startsWith("S"); } @@ -357,7 +358,7 @@ public void test_getAnnotations() { JavaPackage nonAnnotatedPackage = importPackage("packageexamples"); JavaAnnotation annotation = getOnlyElement(annotatedPackage.getAnnotations()); - assertThat(annotation.getRawType()).matches(PackageLevelAnnotation.class); + assertThatType(annotation.getRawType()).matches(PackageLevelAnnotation.class); assertThat(annotation.getOwner()).isEqualTo(annotatedPackage); assertThat(nonAnnotatedPackage.getAnnotations()).isEmpty(); @@ -392,7 +393,7 @@ public void test_getAnnotationOfType_typeName() { final JavaPackage annotatedPackage = importPackage("packageexamples.annotated"); final JavaPackage nonAnnotatedPackage = importPackage("packageexamples"); - assertThat(annotatedPackage.getAnnotationOfType(PackageLevelAnnotation.class.getName()) + assertThatType(annotatedPackage.getAnnotationOfType(PackageLevelAnnotation.class.getName()) .getRawType()).matches(PackageLevelAnnotation.class); assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { @@ -481,7 +482,7 @@ public void function_GET_CLASSES() { Iterable classes = GET_CLASSES.apply(defaultPackage.getPackage("java.lang")); - assertThatClasses(classes).contain(Object.class, String.class); + assertThatTypes(classes).contain(Object.class, String.class); for (JavaClass javaClass : classes) { assertThat(javaClass.getPackageName()).startsWith("java.lang"); } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/ThrowsClauseTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/ThrowsClauseTest.java index 3f128dd208..e96c9dc4ff 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/ThrowsClauseTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/ThrowsClauseTest.java @@ -9,7 +9,7 @@ import static com.tngtech.archunit.core.domain.JavaClass.Predicates.equivalentTo; import static com.tngtech.archunit.core.domain.TestUtils.importMethod; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static org.assertj.core.api.Assertions.assertThat; public class ThrowsClauseTest { @@ -32,7 +32,7 @@ public void size() { public void getTypes() { JavaMethod method = importMethod(SomeClass.class, "method"); - assertThatClasses(method.getThrowsClause().getTypes()).matchInAnyOrder(IOException.class, SQLDataException.class); + assertThatTypes(method.getThrowsClause().getTypes()).matchInAnyOrder(IOException.class, SQLDataException.class); } private void assertAllTrue(Iterable> asserts) { @@ -59,4 +59,4 @@ private static class SomeClass { void method() throws IOException, SQLDataException { } } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasReturnTypeTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasReturnTypeTest.java index b20842d906..dc83ebf1e3 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasReturnTypeTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasReturnTypeTest.java @@ -5,8 +5,10 @@ import org.junit.Test; import static com.tngtech.archunit.core.domain.TestUtils.importClassWithContext; +import static com.tngtech.archunit.core.domain.properties.HasReturnType.Functions.GET_RAW_RETURN_TYPE; import static com.tngtech.archunit.core.domain.properties.HasReturnType.Predicates.rawReturnType; import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; public class HasReturnTypeTest { @Test @@ -44,7 +46,7 @@ public void predicate_on_return_type_by_Predicate() { @Test public void function_get_return_type() { JavaClass expectedType = importClassWithContext(String.class); - assertThat(HasReturnType.Functions.GET_RAW_RETURN_TYPE.apply(newHasReturnType(expectedType))) + assertThatType(GET_RAW_RETURN_TYPE.apply(newHasReturnType(expectedType))) .as("result of GET_RAW_RETURN_TYPE").isEqualTo(expectedType); } @@ -57,4 +59,4 @@ public JavaClass getRawReturnType() { } }; } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasTypeTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasTypeTest.java index 87cb0a2457..44cf4301a7 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasTypeTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasTypeTest.java @@ -10,7 +10,9 @@ import static com.tngtech.archunit.core.domain.JavaClass.Predicates.equivalentTo; import static com.tngtech.archunit.core.domain.TestUtils.importClassWithContext; +import static com.tngtech.archunit.core.domain.properties.HasType.Functions.GET_RAW_TYPE; import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; @RunWith(DataProviderRunner.class) @@ -44,7 +46,7 @@ public void predicate_description() { @Test public void function_getType() { - assertThat(HasType.Functions.GET_RAW_TYPE.apply(newHasType(String.class))).matches(String.class); + assertThatType(GET_RAW_TYPE.apply(newHasType(String.class))).matches(String.class); } private HasType newHasType(final Class owner) { @@ -56,4 +58,4 @@ public JavaClass getRawType() { } }; } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterSlowTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterSlowTest.java index db90179326..01d12c9fa7 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterSlowTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterSlowTest.java @@ -26,7 +26,8 @@ import static com.tngtech.archunit.core.importer.ImportOption.Predefined.DO_NOT_INCLUDE_TESTS; import static com.tngtech.archunit.core.importer.UrlSourceTest.JAVA_CLASS_PATH_PROP; import static com.tngtech.archunit.testutil.Assertions.assertThat; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static java.util.jar.Attributes.Name.CLASS_PATH; @Category(Slow.class) @@ -44,57 +45,57 @@ public class ClassFileImporterSlowTest { public void imports_the_classpath() { JavaClasses classes = new ClassFileImporter().importClasspath(); - assertThatClasses(classes).contain(ClassFileImporter.class, getClass()); - assertThatClasses(classes).doNotContain(Rule.class); // Default does not import jars - assertThatClasses(classes).doNotContain(File.class); // Default does not import JDK classes + assertThatTypes(classes).contain(ClassFileImporter.class, getClass()); + assertThatTypes(classes).doNotContain(Rule.class); // Default does not import jars + assertThatTypes(classes).doNotContain(File.class); // Default does not import JDK classes classes = new ClassFileImporter().importClasspath(new ImportOptions().with(importJavaBaseOrRtAndJUnitJarAndFilesOnTheClasspath())); - assertThatClasses(classes).contain(ClassFileImporter.class, getClass(), Rule.class, File.class); + assertThatTypes(classes).contain(ClassFileImporter.class, getClass(), Rule.class, File.class); } @Test public void respects_ImportOptions_when_using_the_default_importClasspath_method() { JavaClasses classes = new ClassFileImporter().withImportOption(DO_NOT_INCLUDE_TESTS).importClasspath(); - assertThatClasses(classes).contain(ClassFileImporter.class); - assertThatClasses(classes).doNotContain(getClass(), Rule.class, String.class); + assertThatTypes(classes).contain(ClassFileImporter.class); + assertThatTypes(classes).doNotContain(getClass(), Rule.class, String.class); } @Test public void imports_packages() { JavaClasses classes = new ClassFileImporter().importPackages( getClass().getPackage().getName(), Rule.class.getPackage().getName()); - assertThatClasses(classes).contain(ImmutableSet.of(getClass(), Rule.class)); + assertThatTypes(classes).contain(ImmutableSet.of(getClass(), Rule.class)); classes = new ClassFileImporter().importPackages( ImmutableSet.of(getClass().getPackage().getName(), Rule.class.getPackage().getName())); - assertThatClasses(classes).contain(ImmutableSet.of(getClass(), Rule.class)); + assertThatTypes(classes).contain(ImmutableSet.of(getClass(), Rule.class)); } @Test public void imports_packages_of_classes() { JavaClasses classes = new ClassFileImporter().importPackagesOf(getClass(), Rule.class); - assertThatClasses(classes).contain(ImmutableSet.of(getClass(), Rule.class)); + assertThatTypes(classes).contain(ImmutableSet.of(getClass(), Rule.class)); classes = new ClassFileImporter().importPackagesOf(ImmutableSet.of(getClass(), Rule.class)); - assertThatClasses(classes).contain(ImmutableSet.of(getClass(), Rule.class)); + assertThatTypes(classes).contain(ImmutableSet.of(getClass(), Rule.class)); } @Test public void imports_jars() throws Exception { JavaClasses classes = new ClassFileImporter().importJar(jarFileOf(Rule.class)); - assertThatClasses(classes).contain(Rule.class); - assertThatClasses(classes).doNotContain(Object.class, ImmutableList.class); + assertThatTypes(classes).contain(Rule.class); + assertThatTypes(classes).doNotContain(Object.class, ImmutableList.class); classes = new ClassFileImporter().importJars(jarFileOf(Rule.class), jarFileOf(ImmutableList.class)); - assertThatClasses(classes).contain(Rule.class, ImmutableList.class); - assertThatClasses(classes).doNotContain(Object.class); + assertThatTypes(classes).contain(Rule.class, ImmutableList.class); + assertThatTypes(classes).doNotContain(Object.class); classes = new ClassFileImporter().importJars(ImmutableList.of( jarFileOf(Rule.class), jarFileOf(ImmutableList.class))); - assertThatClasses(classes).contain(Rule.class, ImmutableList.class); - assertThatClasses(classes).doNotContain(Object.class); + assertThatTypes(classes).contain(Rule.class, ImmutableList.class); + assertThatTypes(classes).doNotContain(Object.class); } @Test @@ -106,7 +107,7 @@ public void imports_duplicate_classes() throws IOException { JavaClasses classes = new ClassFileImporter().importPackages(getClass().getPackage().getName()); - assertThat(classes.get(JavaClass.class)).isNotNull(); + assertThatType(classes.get(JavaClass.class)).isNotNull(); } @Test @@ -144,12 +145,12 @@ public void creates_JavaPackages() { .as("Created default package contains 'java'").isTrue(); JavaPackage javaPackage = defaultPackage.getPackage("java.lang"); - assertThatClasses(javaPackage.getClasses()).contain(Object.class, String.class, Integer.class); - assertThatClasses(javaPackage.getAllClasses()).contain(Object.class, Annotation.class, Field.class); + assertThatTypes(javaPackage.getClasses()).contain(Object.class, String.class, Integer.class); + assertThatTypes(javaPackage.getAllClasses()).contain(Object.class, Annotation.class, Field.class); assertThat(javaClasses.containPackage("java.util")) .as("Classes contain package 'java.util'").isTrue(); - assertThatClasses(javaClasses.getPackage("java.util").getClasses()).contain(List.class); + assertThatTypes(javaClasses.getPackage("java.util").getClasses()).contain(List.class); } private JavaClasses importJavaBase() { diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java index a3f4498ee0..76841cd983 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java @@ -223,7 +223,8 @@ import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.archunit.testutil.Assertions.assertThatAccess; import static com.tngtech.archunit.testutil.Assertions.assertThatCall; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static com.tngtech.archunit.testutil.ReflectionTestUtils.constructor; import static com.tngtech.archunit.testutil.ReflectionTestUtils.field; import static com.tngtech.archunit.testutil.ReflectionTestUtils.method; @@ -252,7 +253,7 @@ public void imports_simple_package() throws Exception { Iterable classes = classesIn("testexamples/simpleimport"); - assertThat(namesOf(classes)).isEqualTo(expectedClassNames); + assertThat(namesOf(classes)).containsOnlyElementsOf(expectedClassNames); } @Test @@ -264,7 +265,7 @@ public void imports_simple_class_details() throws Exception { assertThat(javaClass.getSimpleName()).as("simple name").isEqualTo(ClassToImportOne.class.getSimpleName()); assertThat(javaClass.getPackageName()).as("package name").isEqualTo(ClassToImportOne.class.getPackage().getName()); assertThat(javaClass.getModifiers()).as("modifiers").containsOnly(JavaModifier.PUBLIC); - assertThat(javaClass.getSuperClass().get()).as("super class").matches(Object.class); + assertThatType(javaClass.getSuperClass().get()).as("super class").matches(Object.class); assertThat(javaClass.getInterfaces()).as("interfaces").isEmpty(); assertThat(javaClass.isInterface()).as("is interface").isFalse(); assertThat(javaClass.isEnum()).as("is enum").isFalse(); @@ -287,14 +288,14 @@ public void imports_simple_enum() throws Exception { assertThat(javaClass.getSimpleName()).as("simple name").isEqualTo(EnumToImport.class.getSimpleName()); assertThat(javaClass.getPackageName()).as("package name").isEqualTo(EnumToImport.class.getPackage().getName()); assertThat(javaClass.getModifiers()).as("modifiers").containsOnly(JavaModifier.PUBLIC, JavaModifier.FINAL); - assertThat(javaClass.getSuperClass().get()).as("super class").matches(Enum.class); + assertThatType(javaClass.getSuperClass().get()).as("super class").matches(Enum.class); assertThat(javaClass.getInterfaces()).as("interfaces").isEmpty(); - assertThatClasses(javaClass.getAllInterfaces()).matchInAnyOrder(Enum.class.getInterfaces()); + assertThatTypes(javaClass.getAllInterfaces()).matchInAnyOrder(Enum.class.getInterfaces()); assertThat(javaClass.isInterface()).as("is interface").isFalse(); assertThat(javaClass.isEnum()).as("is enum").isTrue(); JavaEnumConstant constant = javaClass.getEnumConstant(EnumToImport.FIRST.name()); - assertThat(constant.getDeclaringClass()).as("declaring class").isEqualTo(javaClass); + assertThatType(constant.getDeclaringClass()).as("declaring class").isEqualTo(javaClass); assertThat(constant.name()).isEqualTo(EnumToImport.FIRST.name()); assertThat(javaClass.getEnumConstants()).extractingResultOf("name").as("enum constant names") .containsOnly(EnumToImport.FIRST.name(), EnumToImport.SECOND.name()); @@ -311,7 +312,7 @@ public void imports_simple_static_nested_class(Class nestedStaticClass) throw ImportedClasses classes = classesIn("testexamples/innerclassimport"); JavaClass staticNestedClass = classes.get(nestedStaticClass); - assertThat(staticNestedClass).matches(nestedStaticClass); + assertThatType(staticNestedClass).matches(nestedStaticClass); assertThat(staticNestedClass.isTopLevelClass()).as("is top level class").isFalse(); assertThat(staticNestedClass.isNestedClass()).as("is nested class").isTrue(); assertThat(staticNestedClass.isMemberClass()).as("is member class").isTrue(); @@ -325,7 +326,7 @@ public void imports_simple_inner_class() throws Exception { ImportedClasses classes = classesIn("testexamples/innerclassimport"); JavaClass innerClass = classes.get(ClassWithInnerClass.Inner.class); - assertThat(innerClass).matches(ClassWithInnerClass.Inner.class); + assertThatType(innerClass).matches(ClassWithInnerClass.Inner.class); assertThat(innerClass.isTopLevelClass()).as("is top level class").isFalse(); assertThat(innerClass.isNestedClass()).as("is nested class").isTrue(); assertThat(innerClass.isMemberClass()).as("is member class").isTrue(); @@ -339,7 +340,7 @@ public void imports_simple_anonymous_class() throws Exception { ImportedClasses classes = classesIn("testexamples/innerclassimport"); JavaClass anonymousClass = classes.get(ClassWithInnerClass.class.getName() + "$1"); - assertThat(anonymousClass).matches(Class.forName(anonymousClass.getName())); + assertThatType(anonymousClass).matches(Class.forName(anonymousClass.getName())); assertThat(anonymousClass.isTopLevelClass()).as("is top level class").isFalse(); assertThat(anonymousClass.isNestedClass()).as("is nested class").isTrue(); assertThat(anonymousClass.isMemberClass()).as("is member class").isFalse(); @@ -353,7 +354,7 @@ public void imports_simple_local_class() throws Exception { ImportedClasses classes = classesIn("testexamples/innerclassimport"); JavaClass localClass = classes.get(ClassWithInnerClass.class.getName() + "$1LocalCaller"); - assertThat(localClass).matches(Class.forName(localClass.getName())); + assertThatType(localClass).matches(Class.forName(localClass.getName())); assertThat(localClass.isTopLevelClass()).as("is top level class").isFalse(); assertThat(localClass.isNestedClass()).as("is nested class").isTrue(); assertThat(localClass.isMemberClass()).as("is member class").isFalse(); @@ -395,7 +396,7 @@ public void imports_interfaces() throws Exception { public void imports_nested_classes() throws Exception { JavaClasses classes = classesIn("testexamples/nestedimport").classes; - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassWithNestedClass.class, ClassWithNestedClass.NestedClass.class, ClassWithNestedClass.StaticNestedClass.class, @@ -433,14 +434,14 @@ public void handles_synthetic_modifiers() throws Exception { public void imports_jdk_classes() { JavaClasses classes = new ClassFileImporter().importClasses(File.class); - assertThatClasses(classes).matchExactly(File.class); + assertThatTypes(classes).matchExactly(File.class); } @Test public void imports_jdk_packages() { JavaClasses classes = new ClassFileImporter().importPackagesOf(File.class); - assertThatClasses(classes).contain(File.class); + assertThatTypes(classes).contain(File.class); } @Test @@ -450,7 +451,7 @@ public void creates_JavaPackages_for_each_JavaClass() { JavaPackage javaPackage = classes.get(SomeClass.class).getPackage(); assertThat(javaPackage.containsClass(SomeEnum.class)).as("Package contains " + SomeEnum.class).isTrue(); - assertThatClasses(javaPackage.getParent().get().getClasses()).contain(getClass()); + assertThatTypes(javaPackage.getParent().get().getClasses()).contain(getClass()); } @DataProvider @@ -489,14 +490,14 @@ public void imports_fields() throws Exception { public void imports_primitive_fields() throws Exception { Set fields = classesIn("testexamples/primitivefieldimport").getFields(); - assertThat(findAnyByName(fields, "aBoolean").getRawType()).matches(boolean.class); - assertThat(findAnyByName(fields, "anInt").getRawType()).matches(int.class); - assertThat(findAnyByName(fields, "aByte").getRawType()).matches(byte.class); - assertThat(findAnyByName(fields, "aChar").getRawType()).matches(char.class); - assertThat(findAnyByName(fields, "aShort").getRawType()).matches(short.class); - assertThat(findAnyByName(fields, "aLong").getRawType()).matches(long.class); - assertThat(findAnyByName(fields, "aFloat").getRawType()).matches(float.class); - assertThat(findAnyByName(fields, "aDouble").getRawType()).matches(double.class); + assertThatType(findAnyByName(fields, "aBoolean").getRawType()).matches(boolean.class); + assertThatType(findAnyByName(fields, "anInt").getRawType()).matches(int.class); + assertThatType(findAnyByName(fields, "aByte").getRawType()).matches(byte.class); + assertThatType(findAnyByName(fields, "aChar").getRawType()).matches(char.class); + assertThatType(findAnyByName(fields, "aShort").getRawType()).matches(short.class); + assertThatType(findAnyByName(fields, "aLong").getRawType()).matches(long.class); + assertThatType(findAnyByName(fields, "aFloat").getRawType()).matches(float.class); + assertThatType(findAnyByName(fields, "aDouble").getRawType()).matches(double.class); } // NOTE: This provokes the scenario where the target type can't be determined uniquely due to a diamond @@ -508,13 +509,13 @@ public void imports_special_target_parameters() throws Exception { Set calls = classes.get(ClassCallingSpecialTarget.class).getMethodCallsFromSelf(); assertThat(targetParametersOf(calls, "primitiveArgs")).matches(byte.class, long.class); - assertThat(returnTypeOf(calls, "primitiveReturnType")).matches(byte.class); + assertThatType(returnTypeOf(calls, "primitiveReturnType")).matches(byte.class); assertThat(targetParametersOf(calls, "arrayArgs")).matches(byte[].class, Object[].class); - assertThat(returnTypeOf(calls, "primitiveArrayReturnType")).matches(short[].class); - assertThat(returnTypeOf(calls, "objectArrayReturnType")).matches(String[].class); + assertThatType(returnTypeOf(calls, "primitiveArrayReturnType")).matches(short[].class); + assertThatType(returnTypeOf(calls, "objectArrayReturnType")).matches(String[].class); assertThat(targetParametersOf(calls, "twoDimArrayArgs")).matches(float[][].class, Object[][].class); - assertThat(returnTypeOf(calls, "primitiveTwoDimArrayReturnType")).matches(double[][].class); - assertThat(returnTypeOf(calls, "objectTwoDimArrayReturnType")).matches(String[][].class); + assertThatType(returnTypeOf(calls, "primitiveTwoDimArrayReturnType")).matches(double[][].class); + assertThatType(returnTypeOf(calls, "objectTwoDimArrayReturnType")).matches(String[][].class); } @Test @@ -523,7 +524,7 @@ public void attaches_correct_owner_to_fields() throws Exception { for (JavaClass clazz : classes) { for (JavaField field : clazz.getFields()) { - assertThat(field.getOwner()).isSameAs(clazz); + assertThatType(field.getOwner()).isSameAs(clazz); } } } @@ -563,7 +564,7 @@ public void imports_annotation_defaults() throws Exception { assertThat(((JavaAnnotation[]) annotationType.getMethod("subAnnotationArrayWithDefault") .getDefaultValue().get())[0].get("value").get()) .as("default of subAnnotationArrayWithDefault()").isEqualTo("first"); - assertThat((JavaClass) annotationType.getMethod("clazzWithDefault") + assertThatType((JavaClass) annotationType.getMethod("clazzWithDefault") .getDefaultValue().get()) .as("default of clazzWithDefault()").matches(String.class); assertThat((JavaClass[]) annotationType.getMethod("classesWithDefault") @@ -577,7 +578,7 @@ public void imports_fields_with_one_annotation_correctly() throws Exception { JavaField field = findAnyByName(classes.getFields(), "stringAnnotatedField"); JavaAnnotation annotation = field.getAnnotationOfType(FieldAnnotationWithStringValue.class.getName()); - assertThat(annotation.getRawType()).isEqualTo(classes.get(FieldAnnotationWithStringValue.class)); + assertThatType(annotation.getRawType()).isEqualTo(classes.get(FieldAnnotationWithStringValue.class)); assertThat(annotation.get("value").get()).isEqualTo("something"); assertThat(annotation.getOwner()).as("owning field").isEqualTo(field); @@ -641,8 +642,8 @@ public void imports_fields_with_complex_annotations_correctly() throws Exception assertThat(subAnnotation.getAnnotatedElement()).isEqualTo(field); assertThat(((JavaAnnotation[]) annotation.get("subAnnotationArrayWithDefault").get())[0].get("value").get()) .isEqualTo("first"); - assertThat((JavaClass) annotation.get("clazz").get()).matches(Map.class); - assertThat((JavaClass) annotation.get("clazzWithDefault").get()).matches(String.class); + assertThatType((JavaClass) annotation.get("clazz").get()).matches(Map.class); + assertThatType((JavaClass) annotation.get("clazzWithDefault").get()).matches(String.class); assertThat((JavaClass[]) annotation.get("classes").get()).matchExactly(Object.class, Serializable.class); assertThat((JavaClass[]) annotation.get("classesWithDefault").get()).matchExactly(Serializable.class, List.class); @@ -719,11 +720,11 @@ public void imports_complex_method_with_correct_parameters() throws Exception { public void imports_methods_with_correct_return_types() throws Exception { Set methods = classesIn("testexamples/methodimport").getCodeUnits(); - assertThat(findAnyByName(methods, "createString").getRawReturnType()) + assertThatType(findAnyByName(methods, "createString").getRawReturnType()) .as("Return type of method 'createString'").matches(String.class); - assertThat(findAnyByName(methods, "consume").getRawReturnType()) + assertThatType(findAnyByName(methods, "consume").getRawReturnType()) .as("Return type of method 'consume'").matches(void.class); - assertThat(findAnyByName(methods, "createSerializable").getRawReturnType()) + assertThatType(findAnyByName(methods, "createSerializable").getRawReturnType()) .as("Return type of method 'createSerializable'").matches(Serializable.class); } @@ -773,7 +774,7 @@ public void imports_methods_with_one_annotation_correctly() throws Exception { JavaMethod method = findAnyByName(clazz.getMethods(), stringAnnotatedMethod); JavaAnnotation annotation = method.getAnnotationOfType(MethodAnnotationWithStringValue.class.getName()); - assertThat(annotation.getRawType()).matches(MethodAnnotationWithStringValue.class); + assertThatType(annotation.getRawType()).matches(MethodAnnotationWithStringValue.class); assertThat(annotation.getOwner()).isEqualTo(method); assertThat(annotation.get("value").get()).isEqualTo("something"); @@ -835,8 +836,8 @@ public void imports_methods_with_complex_annotations_correctly() throws Exceptio assertThat(subAnnotationArray[0].getAnnotatedElement()).isEqualTo(method); assertThat(((JavaAnnotation[]) annotation.get("subAnnotationArrayWithDefault").get())[0].get("value").get()) .isEqualTo("first"); - assertThat((JavaClass) annotation.get("clazz").get()).matches(Map.class); - assertThat((JavaClass) annotation.get("clazzWithDefault").get()).matches(String.class); + assertThatType((JavaClass) annotation.get("clazz").get()).matches(Map.class); + assertThatType((JavaClass) annotation.get("clazzWithDefault").get()).matches(String.class); assertThat((JavaClass[]) annotation.get("classes").get()).matchExactly(Object.class, Serializable.class); assertThat((JavaClass[]) annotation.get("classesWithDefault").get()).matchExactly(Serializable.class, List.class); @@ -885,15 +886,15 @@ public void imports_class_with_one_annotation_correctly() throws Exception { JavaClass clazz = classesIn("testexamples/annotatedclassimport").get(ClassWithOneAnnotation.class); JavaAnnotation annotation = clazz.getAnnotationOfType(SimpleAnnotation.class.getName()); - assertThat(annotation.getRawType()).matches(SimpleAnnotation.class); - assertThat(annotation.getOwner()).isEqualTo(clazz); + assertThatType(annotation.getRawType()).matches(SimpleAnnotation.class); + assertThatType(annotation.getOwner()).isEqualTo(clazz); JavaAnnotation annotationByName = clazz.getAnnotationOfType(SimpleAnnotation.class.getName()); assertThat(annotationByName).isEqualTo(annotation); assertThat(annotation.get("value").get()).isEqualTo("test"); - assertThat(clazz).matches(ClassWithOneAnnotation.class); + assertThatType(clazz).matches(ClassWithOneAnnotation.class); } @Test @@ -927,12 +928,12 @@ public void imports_class_with_complex_annotations_correctly() throws Exception assertThat(subAnnotationArray[0].getAnnotatedElement()).isEqualTo(clazz); assertThat(((JavaAnnotation[]) annotation.get("subAnnotationArrayWithDefault").get())[0].get("value").get()) .isEqualTo("first"); - assertThat((JavaClass) annotation.get("clazz").get()).matches(Serializable.class); - assertThat((JavaClass) annotation.get("clazzWithDefault").get()).matches(String.class); + assertThatType((JavaClass) annotation.get("clazz").get()).matches(Serializable.class); + assertThatType((JavaClass) annotation.get("clazzWithDefault").get()).matches(String.class); assertThat((JavaClass[]) annotation.get("classes").get()).matchExactly(Serializable.class, String.class); assertThat((JavaClass[]) annotation.get("classesWithDefault").get()).matchExactly(Serializable.class, List.class); - assertThat(clazz).matches(ClassWithComplexAnnotations.class); + assertThatType(clazz).matches(ClassWithComplexAnnotations.class); } @Test @@ -1674,7 +1675,7 @@ public void imports_non_unique_targets_for_diamond_scenarios() throws Exception .inLineNumber(ClassCallingDiamond.callInterfaceLineNumber); // NOTE: There is no java.lang.reflect.Method InterfaceD.implementMe(), because the method is inherited assertThat(callToInterface.getTarget().getName()).isEqualTo(InterfaceD.implementMe); - assertThat(callToInterface.getTarget().getOwner()).isEqualTo(diamondPeakInterface); + assertThatType(callToInterface.getTarget().getOwner()).isEqualTo(diamondPeakInterface); assertThat(callToInterface.getTarget().getRawParameterTypes()).isEmpty(); assertThat(callToInterface.getTarget().resolve()).extracting("fullName") .containsOnly( @@ -1694,8 +1695,8 @@ public void imports_method_calls_that_return_Arrays() throws Exception { JavaClass classThatCallsMethodReturningArray = classesIn("testexamples/callimport").get(CallsMethodReturningArray.class); MethodCallTarget target = getOnlyElement(classThatCallsMethodReturningArray.getMethodCallsFromSelf()).getTarget(); - assertThat(target.getOwner()).matches(CallsMethodReturningArray.SomeEnum.class); - assertThat(target.getRawReturnType()).matches(CallsMethodReturningArray.SomeEnum[].class); + assertThatType(target.getOwner()).matches(CallsMethodReturningArray.SomeEnum.class); + assertThatType(target.getRawReturnType()).matches(CallsMethodReturningArray.SomeEnum[].class); } @Test @@ -1907,7 +1908,7 @@ public void classes_know_which_method_throws_clauses_contain_their_type() { JavaClasses classes = new ClassFileImporter().importClasses(ClassWithThrowingMethod.class, FirstCheckedException.class); Set> throwsDeclarations = classes.get(FirstCheckedException.class).getMethodThrowsDeclarationsWithTypeOfSelf(); - assertThat(getOnlyElement(throwsDeclarations).getDeclaringClass()).matches(ClassWithThrowingMethod.class); + assertThatType(getOnlyElement(throwsDeclarations).getDeclaringClass()).matches(ClassWithThrowingMethod.class); assertThat(classes.get(FirstCheckedException.class).getConstructorsWithParameterTypeOfSelf()).isEmpty(); } @@ -1926,7 +1927,7 @@ public void classes_know_which_constructor_throws_clauses_contain_their_type() { Set> throwsDeclarations = classes.get(FirstCheckedException.class).getConstructorsWithThrowsDeclarationTypeOfSelf(); - assertThat(getOnlyElement(throwsDeclarations).getDeclaringClass()).matches(ClassWithThrowingConstructor.class); + assertThatType(getOnlyElement(throwsDeclarations).getDeclaringClass()).matches(ClassWithThrowingConstructor.class); assertThat(classes.get(FirstCheckedException.class).getMethodThrowsDeclarationsWithTypeOfSelf()).isEmpty(); } @@ -1976,7 +1977,7 @@ public void imports_urls_of_folders() throws Exception { JavaClasses javaClasses = new ClassFileImporter().importUrl(testexamplesFolder.toURI().toURL()); - assertThatClasses(javaClasses).contain(SomeClass.class, OtherClass.class); + assertThatTypes(javaClasses).contain(SomeClass.class, OtherClass.class); } @Test @@ -2054,7 +2055,7 @@ public void import_is_resilient_against_broken_class_files() throws Exception { JavaClasses classes = new ClassFileImporter().importPath(folder.toPath()); - assertThatClasses(classes).matchExactly(expectedClass); + assertThatTypes(classes).matchExactly(expectedClass); logTest.assertLogMessage(Level.WARN, "Evil.class"); } @@ -2089,7 +2090,7 @@ public void class_has_source_of_import() throws Exception { public void imports_class_objects() { JavaClasses classes = new ClassFileImporter().importClasses(ClassToImportOne.class, ClassToImportTwo.class); - assertThatClasses(classes).matchInAnyOrder(ClassToImportOne.class, ClassToImportTwo.class); + assertThatTypes(classes).matchInAnyOrder(ClassToImportOne.class, ClassToImportTwo.class); } /** @@ -2124,28 +2125,28 @@ public void imports_paths() throws Exception { JavaClasses classes = new ClassFileImporter() .importPaths(ImmutableList.of(folderOne.toPath(), folderTwo.toPath())); - assertThatClasses(classes).matchInAnyOrder(Class11.class, Class12.class, Class21.class, Class22.class); + assertThatTypes(classes).matchInAnyOrder(Class11.class, Class12.class, Class21.class, Class22.class); classes = new ClassFileImporter().importPaths(folderOne.toPath(), folderTwo.toPath()); - assertThatClasses(classes).matchInAnyOrder(Class11.class, Class12.class, Class21.class, Class22.class); + assertThatTypes(classes).matchInAnyOrder(Class11.class, Class12.class, Class21.class, Class22.class); classes = new ClassFileImporter().importPaths(folderOne.getAbsolutePath(), folderTwo.getAbsolutePath()); - assertThatClasses(classes).matchInAnyOrder(Class11.class, Class12.class, Class21.class, Class22.class); + assertThatTypes(classes).matchInAnyOrder(Class11.class, Class12.class, Class21.class, Class22.class); classes = new ClassFileImporter().importPath(folderOne.toPath()); - assertThatClasses(classes).matchInAnyOrder(Class11.class, Class12.class); + assertThatTypes(classes).matchInAnyOrder(Class11.class, Class12.class); classes = new ClassFileImporter().importPath(folderOne.getAbsolutePath()); - assertThatClasses(classes).matchInAnyOrder(Class11.class, Class12.class); + assertThatTypes(classes).matchInAnyOrder(Class11.class, Class12.class); } @Test public void ImportOptions_are_respected() throws Exception { ClassFileImporter importer = new ClassFileImporter().withImportOption(importOnly(getClass(), Rule.class)); - assertThatClasses(importer.importPath(Paths.get(urlOf(getClass()).toURI()))).matchExactly(getClass()); - assertThatClasses(importer.importUrl(urlOf(getClass()))).matchExactly(getClass()); - assertThatClasses(importer.importJar(jarFileOf(Rule.class))).matchExactly(Rule.class); + assertThatTypes(importer.importPath(Paths.get(urlOf(getClass()).toURI()))).matchExactly(getClass()); + assertThatTypes(importer.importUrl(urlOf(getClass()))).matchExactly(getClass()); + assertThatTypes(importer.importJar(jarFileOf(Rule.class))).matchExactly(Rule.class); } @Test 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 482df81216..bf9d81055c 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 @@ -43,7 +43,8 @@ import static com.tngtech.archunit.lang.conditions.ArchPredicates.have; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; import static com.tngtech.archunit.testutil.Assertions.assertThat; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; public class GivenClassesThatTest { @Rule @@ -54,7 +55,7 @@ public void haveFullyQualifiedName() { List classes = filterResultOf(classes().that().haveFullyQualifiedName(List.class.getName())) .on(List.class, String.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(List.class); + assertThatType(getOnlyElement(classes)).matches(List.class); } @Test @@ -62,7 +63,7 @@ public void doNotHaveFullyQualifiedName() { List classes = filterResultOf(classes().that().doNotHaveFullyQualifiedName(List.class.getName())) .on(List.class, String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(String.class, Iterable.class); } @Test @@ -70,7 +71,7 @@ public void haveSimpleName() { List classes = filterResultOf(classes().that().haveSimpleName(List.class.getSimpleName())) .on(List.class, String.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(List.class); + assertThatType(getOnlyElement(classes)).matches(List.class); } @Test @@ -78,7 +79,7 @@ public void doNotHaveSimpleName() { List classes = filterResultOf(classes().that().doNotHaveSimpleName(List.class.getSimpleName())) .on(List.class, String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(String.class, Iterable.class); } @Test @@ -86,7 +87,7 @@ public void haveNameMatching() { List classes = filterResultOf(classes().that().haveNameMatching(".*List")) .on(List.class, String.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(List.class); + assertThatType(getOnlyElement(classes)).matches(List.class); } @Test @@ -94,7 +95,7 @@ public void haveNameNotMatching() { List classes = filterResultOf(classes().that().haveNameNotMatching(".*List")) .on(List.class, String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(String.class, Iterable.class); } @Test @@ -102,7 +103,7 @@ public void haveSimpleNameStartingWith() { List classes = filterResultOf(classes().that().haveSimpleNameStartingWith("String")) .on(AttributedString.class, String.class, StringBuilder.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class, StringBuilder.class); + assertThatTypes(classes).matchInAnyOrder(String.class, StringBuilder.class); } @Test @@ -110,7 +111,7 @@ public void haveSimpleNameNotStartingWith() { List classes = filterResultOf(classes().that().haveSimpleNameNotStartingWith("String")) .on(AttributedString.class, String.class, StringBuilder.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(AttributedString.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(AttributedString.class, Iterable.class); } @Test @@ -118,7 +119,7 @@ public void haveSimpleNameContaining() { List classes = filterResultOf(classes().that().haveSimpleNameContaining("rin")) .on(List.class, String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class); + assertThatTypes(classes).matchInAnyOrder(String.class); } @Test @@ -126,7 +127,7 @@ public void haveSimpleNameNotContaining() { List classes = filterResultOf(classes().that().haveSimpleNameNotContaining("rin")) .on(List.class, String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(List.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(List.class, Iterable.class); } @Test @@ -134,7 +135,7 @@ public void haveSimpleNameEndingWith() { List classes = filterResultOf(classes().that().haveSimpleNameEndingWith("String")) .on(String.class, AttributedString.class, StringBuilder.class); - assertThatClasses(classes).matchInAnyOrder(String.class, AttributedString.class); + assertThatTypes(classes).matchInAnyOrder(String.class, AttributedString.class); } @Test @@ -142,7 +143,7 @@ public void haveSimpleNameNotEndingWith() { List classes = filterResultOf(classes().that().haveSimpleNameNotEndingWith("String")) .on(String.class, AttributedString.class, StringBuilder.class); - assertThatClasses(classes).matchInAnyOrder(StringBuilder.class); + assertThatTypes(classes).matchInAnyOrder(StringBuilder.class); } @Test @@ -150,7 +151,7 @@ public void resideInAPackage() { List classes = filterResultOf(classes().that().resideInAPackage("..tngtech..")) .on(getClass(), String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(getClass()); + assertThatTypes(classes).matchInAnyOrder(getClass()); } @Test @@ -158,7 +159,7 @@ public void resideOutsideOfPackage() { List classes = filterResultOf(classes().that().resideOutsideOfPackage("..tngtech..")) .on(getClass(), String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(String.class, Iterable.class); } @Test @@ -166,7 +167,7 @@ public void resideInAnyPackage() { List classes = filterResultOf(classes().that().resideInAnyPackage("..tngtech..", "java.lang.reflect")) .on(getClass(), String.class, Constructor.class); - assertThatClasses(classes).matchInAnyOrder(getClass(), Constructor.class); + assertThatTypes(classes).matchInAnyOrder(getClass(), Constructor.class); } @Test @@ -175,7 +176,7 @@ public void resideOutsideOfPackages() { .resideOutsideOfPackages("..tngtech..", "java.lang.reflect") ).on(getClass(), String.class, Constructor.class); - assertThatClasses(classes).matchInAnyOrder(String.class); + assertThatTypes(classes).matchInAnyOrder(String.class); } @Test @@ -183,7 +184,7 @@ public void arePublic() { List classes = filterResultOf(classes().that().arePublic()) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(getClass()); + assertThatTypes(classes).matchInAnyOrder(getClass()); } @Test @@ -191,7 +192,7 @@ public void areNotPublic() { List classes = filterResultOf(classes().that().areNotPublic()) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); + assertThatTypes(classes).matchInAnyOrder(PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); } @Test @@ -199,7 +200,7 @@ public void areProtected() { List classes = filterResultOf(classes().that().areProtected()) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ProtectedClass.class); + assertThatTypes(classes).matchInAnyOrder(ProtectedClass.class); } @Test @@ -207,7 +208,7 @@ public void areNotProtected() { List classes = filterResultOf(classes().that().areNotProtected()) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(getClass(), PrivateClass.class, PackagePrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(getClass(), PrivateClass.class, PackagePrivateClass.class); } @Test @@ -215,7 +216,7 @@ public void arePackagePrivate() { List classes = filterResultOf(classes().that().arePackagePrivate()) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(PackagePrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(PackagePrivateClass.class); } @Test @@ -223,7 +224,7 @@ public void areNotPackagePrivate() { List classes = filterResultOf(classes().that().areNotPackagePrivate()) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(getClass(), PrivateClass.class, ProtectedClass.class); + assertThatTypes(classes).matchInAnyOrder(getClass(), PrivateClass.class, ProtectedClass.class); } @Test @@ -231,7 +232,7 @@ public void arePrivate() { List classes = filterResultOf(classes().that().arePrivate()) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(PrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(PrivateClass.class); } @Test @@ -239,7 +240,7 @@ public void areNotPrivate() { List classes = filterResultOf(classes().that().areNotPrivate()) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(getClass(), PackagePrivateClass.class, ProtectedClass.class); + assertThatTypes(classes).matchInAnyOrder(getClass(), PackagePrivateClass.class, ProtectedClass.class); } @Test @@ -247,7 +248,7 @@ public void haveModifiers() { List classes = filterResultOf(classes().that().haveModifier(PRIVATE)) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(PrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(PrivateClass.class); } @Test @@ -255,7 +256,7 @@ public void doNotHaveModifiers() { List classes = filterResultOf(classes().that().doNotHaveModifier(PRIVATE)) .on(getClass(), PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(getClass(), PackagePrivateClass.class, ProtectedClass.class); + assertThatTypes(classes).matchInAnyOrder(getClass(), PackagePrivateClass.class, ProtectedClass.class); } @Test @@ -263,7 +264,7 @@ public void areAnnotatedWith_type() { List classes = filterResultOf(classes().that().areAnnotatedWith(SomeAnnotation.class)) .on(AnnotatedClass.class, SimpleClass.class); - assertThat(getOnlyElement(classes)).matches(AnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(AnnotatedClass.class); } /** @@ -284,7 +285,7 @@ public void areNotAnnotatedWith_type() { List classes = filterResultOf(classes().that().areNotAnnotatedWith(SomeAnnotation.class)) .on(AnnotatedClass.class, SimpleClass.class); - assertThat(getOnlyElement(classes)).matches(SimpleClass.class); + assertThatType(getOnlyElement(classes)).matches(SimpleClass.class); } /** @@ -305,7 +306,7 @@ public void areAnnotatedWith_typeName() { List classes = filterResultOf(classes().that().areAnnotatedWith(SomeAnnotation.class.getName())) .on(AnnotatedClass.class, SimpleClass.class); - assertThat(getOnlyElement(classes)).matches(AnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(AnnotatedClass.class); } @Test @@ -313,7 +314,7 @@ public void areNotAnnotatedWith_typeName() { List classes = filterResultOf(classes().that().areNotAnnotatedWith(SomeAnnotation.class.getName())) .on(AnnotatedClass.class, SimpleClass.class); - assertThat(getOnlyElement(classes)).matches(SimpleClass.class); + assertThatType(getOnlyElement(classes)).matches(SimpleClass.class); } @Test @@ -322,7 +323,7 @@ public void areAnnotatedWith_predicate() { List classes = filterResultOf(classes().that().areAnnotatedWith(hasNamePredicate)) .on(AnnotatedClass.class, SimpleClass.class); - assertThat(getOnlyElement(classes)).matches(AnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(AnnotatedClass.class); } @Test @@ -331,7 +332,7 @@ public void areNotAnnotatedWith_predicate() { List classes = filterResultOf(classes().that().areNotAnnotatedWith(hasNamePredicate)) .on(AnnotatedClass.class, SimpleClass.class); - assertThat(getOnlyElement(classes)).matches(SimpleClass.class); + assertThatType(getOnlyElement(classes)).matches(SimpleClass.class); } @Test @@ -339,7 +340,7 @@ public void areMetaAnnotatedWith_type() { List classes = filterResultOf(classes().that().areMetaAnnotatedWith(SomeAnnotation.class)) .on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); - assertThat(getOnlyElement(classes)).matches(MetaAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(MetaAnnotatedClass.class); } @Test @@ -347,7 +348,7 @@ public void areNotMetaAnnotatedWith_type() { List classes = filterResultOf(classes().that().areNotMetaAnnotatedWith(SomeAnnotation.class)) .on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); + assertThatTypes(classes).matchInAnyOrder(AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); } @Test @@ -355,7 +356,7 @@ public void areMetaAnnotatedWith_typeName() { List classes = filterResultOf(classes().that().areMetaAnnotatedWith(SomeAnnotation.class.getName())) .on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); - assertThat(getOnlyElement(classes)).matches(MetaAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(MetaAnnotatedClass.class); } @Test @@ -363,7 +364,7 @@ public void areNotMetaAnnotatedWith_typeName() { List classes = filterResultOf(classes().that().areNotMetaAnnotatedWith(SomeAnnotation.class.getName())) .on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); + assertThatTypes(classes).matchInAnyOrder(AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); } @Test @@ -372,7 +373,7 @@ public void areMetaAnnotatedWith_predicate() { List classes = filterResultOf(classes().that().areMetaAnnotatedWith(hasNamePredicate)) .on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); - assertThat(getOnlyElement(classes)).matches(MetaAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(MetaAnnotatedClass.class); } @Test @@ -381,7 +382,7 @@ public void areNotMetaAnnotatedWith_predicate() { List classes = filterResultOf(classes().that().areNotMetaAnnotatedWith(hasNamePredicate)) .on(MetaAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); + assertThatTypes(classes).matchInAnyOrder(AnnotatedClass.class, SimpleClass.class, MetaAnnotatedAnnotation.class); } @Test @@ -389,7 +390,7 @@ public void implement_type() { List classes = filterResultOf(classes().that().implement(Collection.class)) .on(ArrayList.class, List.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(ArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ArrayList.class); classes = filterResultOf(classes().that().implement(Set.class)) .on(ArrayList.class, List.class, Iterable.class); @@ -410,7 +411,7 @@ public void doNotImplement_type() { List classes = filterResultOf(classes().that().doNotImplement(Collection.class)) .on(ArrayList.class, List.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(List.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(List.class, Iterable.class); } @Test @@ -426,7 +427,7 @@ public void implement_typeName() { List classes = filterResultOf(classes().that().implement(Collection.class.getName())) .on(ArrayList.class, List.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(ArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ArrayList.class); classes = filterResultOf(classes().that().implement(AbstractList.class.getName())) .on(ArrayList.class, List.class, Iterable.class); @@ -439,7 +440,7 @@ public void doNotImplement_typeName() { List classes = filterResultOf(classes().that().doNotImplement(Collection.class.getName())) .on(ArrayList.class, List.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(List.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(List.class, Iterable.class); } @Test @@ -447,7 +448,7 @@ public void implement_predicate() { List classes = filterResultOf(classes().that().implement(classWithNameOf(Collection.class))) .on(ArrayList.class, List.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(ArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ArrayList.class); classes = filterResultOf(classes().that().implement(classWithNameOf(AbstractList.class))) .on(ArrayList.class, List.class, Iterable.class); @@ -460,7 +461,7 @@ public void doNotImplement_predicate() { List classes = filterResultOf(classes().that().doNotImplement(classWithNameOf(Collection.class))) .on(ArrayList.class, List.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(List.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(List.class, Iterable.class); } @Test @@ -468,12 +469,12 @@ public void areAssignableTo_type() { List classes = filterResultOf(classes().that().areAssignableTo(Collection.class)) .on(List.class, String.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(List.class); + assertThatType(getOnlyElement(classes)).matches(List.class); classes = filterResultOf(classes().that().areAssignableTo(AbstractList.class)) .on(ArrayList.class, List.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(ArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ArrayList.class); } @Test @@ -481,7 +482,7 @@ public void areNotAssignableTo_type() { List classes = filterResultOf(classes().that().areNotAssignableTo(Collection.class)) .on(List.class, String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(String.class, Iterable.class); } @Test @@ -489,12 +490,12 @@ public void areAssignableTo_typeName() { List classes = filterResultOf(classes().that().areAssignableTo(Collection.class.getName())) .on(List.class, String.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(List.class); + assertThatType(getOnlyElement(classes)).matches(List.class); classes = filterResultOf(classes().that().areAssignableTo(AbstractList.class.getName())) .on(ArrayList.class, List.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(ArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ArrayList.class); } @Test @@ -502,7 +503,7 @@ public void areNotAssignableTo_typeName() { List classes = filterResultOf(classes().that().areNotAssignableTo(Collection.class.getName())) .on(List.class, String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(String.class, Iterable.class); } @Test @@ -510,12 +511,12 @@ public void areAssignableTo_predicate() { List classes = filterResultOf(classes().that().areAssignableTo(classWithNameOf(Collection.class))) .on(List.class, String.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(List.class); + assertThatType(getOnlyElement(classes)).matches(List.class); classes = filterResultOf(classes().that().areAssignableTo(classWithNameOf(AbstractList.class))) .on(ArrayList.class, List.class, Iterable.class); - assertThat(getOnlyElement(classes)).matches(ArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ArrayList.class); } @Test @@ -523,7 +524,7 @@ public void areNotAssignableTo_predicate() { List classes = filterResultOf(classes().that().areNotAssignableTo(classWithNameOf(Collection.class))) .on(List.class, String.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(String.class, Iterable.class); } @Test @@ -531,7 +532,7 @@ public void areAssignableFrom_type() { List classes = filterResultOf(classes().that().areAssignableFrom(Collection.class)) .on(List.class, String.class, Collection.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(Collection.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(Collection.class, Iterable.class); } @Test @@ -539,7 +540,7 @@ public void areNotAssignableFrom_type() { List classes = filterResultOf(classes().that().areNotAssignableFrom(Collection.class)) .on(List.class, String.class, Collection.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(List.class, String.class); + assertThatTypes(classes).matchInAnyOrder(List.class, String.class); } @Test @@ -547,7 +548,7 @@ public void areAssignableFrom_typeName() { List classes = filterResultOf(classes().that().areAssignableFrom(Collection.class.getName())) .on(List.class, String.class, Collection.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(Collection.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(Collection.class, Iterable.class); } @Test @@ -555,7 +556,7 @@ public void areNotAssignableFrom_typeName() { List classes = filterResultOf(classes().that().areNotAssignableFrom(Collection.class.getName())) .on(List.class, String.class, Collection.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(List.class, String.class); + assertThatTypes(classes).matchInAnyOrder(List.class, String.class); } @Test @@ -563,7 +564,7 @@ public void areAssignableFrom_predicate() { List classes = filterResultOf(classes().that().areAssignableFrom(classWithNameOf(Collection.class))) .on(List.class, String.class, Collection.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(Collection.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(Collection.class, Iterable.class); } @Test @@ -571,7 +572,7 @@ public void areNotAssignableFrom_predicate() { List classes = filterResultOf(classes().that().areNotAssignableFrom(classWithNameOf(Collection.class))) .on(List.class, String.class, Collection.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(List.class, String.class); + assertThatTypes(classes).matchInAnyOrder(List.class, String.class); } @Test @@ -579,7 +580,7 @@ public void areInterfaces_predicate() { List classes = filterResultOf(classes().that().areInterfaces()) .on(List.class, String.class, Collection.class, Integer.class); - assertThatClasses(classes).matchInAnyOrder(List.class, Collection.class); + assertThatTypes(classes).matchInAnyOrder(List.class, Collection.class); } @Test @@ -587,7 +588,7 @@ public void areNotInterfaces_predicate() { List classes = filterResultOf(classes().that().areNotInterfaces()) .on(List.class, String.class, Collection.class, Integer.class); - assertThatClasses(classes).matchInAnyOrder(String.class, Integer.class); + assertThatTypes(classes).matchInAnyOrder(String.class, Integer.class); } @Test @@ -595,7 +596,7 @@ public void areEnums_predicate() { List classes = filterResultOf(classes().that().areEnums()) .on(StandardCopyOption.class, StandardOpenOption.class, Collection.class, Integer.class); - assertThatClasses(classes).matchInAnyOrder(StandardCopyOption.class, StandardOpenOption.class); + assertThatTypes(classes).matchInAnyOrder(StandardCopyOption.class, StandardOpenOption.class); } @Test @@ -603,7 +604,7 @@ public void areNotEnums_predicate() { List classes = filterResultOf(classes().that().areNotEnums()) .on(StandardCopyOption.class, StandardOpenOption.class, Collection.class, Integer.class); - assertThatClasses(classes).matchInAnyOrder(Collection.class, Integer.class); + assertThatTypes(classes).matchInAnyOrder(Collection.class, Integer.class); } @Test @@ -613,7 +614,7 @@ public void areTopLevelClasses_predicate() { NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes).matchInAnyOrder(List.class, Map.class); + assertThatTypes(classes).matchInAnyOrder(List.class, Map.class); } @Test @@ -623,7 +624,7 @@ public void areNotTopLevelClasses_predicate() { NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes) + assertThatTypes(classes) .matchInAnyOrder(Map.Entry.class, NestedClassWithSomeMoreClasses.class, NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); @@ -636,7 +637,7 @@ public void areNestedClasses_predicate() { NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes) + assertThatTypes(classes) .matchInAnyOrder(Map.Entry.class, NestedClassWithSomeMoreClasses.class, NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); @@ -649,7 +650,7 @@ public void areNotNestedClasses_predicate() { NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes).matchInAnyOrder(List.class, Map.class); + assertThatTypes(classes).matchInAnyOrder(List.class, Map.class); } @Test @@ -659,7 +660,7 @@ public void areMemberClasses_predicate() { NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes) + assertThatTypes(classes) .matchInAnyOrder(Map.Entry.class, NestedClassWithSomeMoreClasses.class, NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class); } @@ -671,7 +672,7 @@ public void areNotMemberClasses_predicate() { NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes).matchInAnyOrder(List.class, Map.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), + assertThatTypes(classes).matchInAnyOrder(List.class, Map.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); } @@ -682,7 +683,7 @@ public void areInnerClasses_predicate() { NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes) + assertThatTypes(classes) .matchInAnyOrder(NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); } @@ -694,7 +695,7 @@ public void areNotInnerClasses_predicate() { NestedClassWithSomeMoreClasses.StaticNestedClass.class, NestedClassWithSomeMoreClasses.InnerMemberClass.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes).matchInAnyOrder(List.class, Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.class, + assertThatTypes(classes).matchInAnyOrder(List.class, Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.class, NestedClassWithSomeMoreClasses.StaticNestedClass.class); } @@ -703,7 +704,7 @@ public void areAnonymousClasses_predicate() { List classes = filterResultOf(classes().that().areAnonymousClasses()) .on(Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes).matchInAnyOrder(NestedClassWithSomeMoreClasses.getAnonymousClass()); + assertThatTypes(classes).matchInAnyOrder(NestedClassWithSomeMoreClasses.getAnonymousClass()); } @Test @@ -711,7 +712,7 @@ public void areNotAnonymousClasses_predicate() { List classes = filterResultOf(classes().that().areNotAnonymousClasses()) .on(Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes).matchInAnyOrder(Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.getLocalClass()); + assertThatTypes(classes).matchInAnyOrder(Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.getLocalClass()); } @Test @@ -719,7 +720,7 @@ public void areLocalClasses_predicate() { List classes = filterResultOf(classes().that().areLocalClasses()) .on(Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes).matchInAnyOrder(NestedClassWithSomeMoreClasses.getLocalClass()); + assertThatTypes(classes).matchInAnyOrder(NestedClassWithSomeMoreClasses.getLocalClass()); } @Test @@ -727,7 +728,7 @@ public void areNotLocalClasses_predicate() { List classes = filterResultOf(classes().that().areNotLocalClasses()) .on(Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.getAnonymousClass(), NestedClassWithSomeMoreClasses.getLocalClass()); - assertThatClasses(classes).matchInAnyOrder(Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.getAnonymousClass()); + assertThatTypes(classes).matchInAnyOrder(Map.class, Map.Entry.class, NestedClassWithSomeMoreClasses.getAnonymousClass()); } @Test @@ -736,7 +737,7 @@ public void belongToAnyOf() { .on(ClassWithInnerClasses.class, ClassWithInnerClasses.InnerClass.class, ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class, List.class, String.class, Iterable.class, StringBuilder.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassWithInnerClasses.class, ClassWithInnerClasses.InnerClass.class, ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class, String.class); } @@ -749,7 +750,7 @@ public void and_conjunction() { .and().haveNameMatching(".*\\..*n.*")) .on(List.class, String.class, Collection.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(String.class); + assertThatTypes(classes).matchInAnyOrder(String.class); } @Test @@ -760,7 +761,7 @@ public void or_conjunction() { .or().haveSimpleName(Collection.class.getSimpleName())) .on(List.class, String.class, Collection.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(List.class, String.class, Collection.class); + assertThatTypes(classes).matchInAnyOrder(List.class, String.class, Collection.class); } /** @@ -786,7 +787,7 @@ public void conjunctions_aggregate_in_sequence_without_special_precedence() { .or().haveSimpleName(Iterable.class.getSimpleName())) .on(List.class, String.class, Collection.class, Iterable.class); - assertThatClasses(classes).matchInAnyOrder(Collection.class, Iterable.class); + assertThatTypes(classes).matchInAnyOrder(Collection.class, Iterable.class); } private DescribedPredicate classWithNameOf(Class type) { 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 51bd90dd3e..17cd46e133 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 @@ -37,7 +37,8 @@ import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; import static com.tngtech.archunit.lang.syntax.elements.ClassesShouldEvaluator.filterClassesAppearingInFailureReport; import static com.tngtech.archunit.testutil.Assertions.assertThat; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; @RunWith(DataProviderRunner.class) @@ -67,7 +68,7 @@ public void haveFullyQualifiedName(ClassesThat noClass noClassesShouldThatRuleStart.haveFullyQualifiedName(List.class.getName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -77,7 +78,7 @@ public void doNotHaveFullyQualifiedName(ClassesThat no noClassesShouldThatRuleStart.doNotHaveFullyQualifiedName(List.class.getName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -87,7 +88,7 @@ public void haveSimpleName(ClassesThat noClassesShould noClassesShouldThatRuleStart.haveSimpleName(List.class.getSimpleName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -97,7 +98,7 @@ public void doNotHaveSimpleName(ClassesThat noClassesS noClassesShouldThatRuleStart.doNotHaveSimpleName(List.class.getSimpleName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -107,7 +108,7 @@ public void haveNameMatching(ClassesThat noClassesShou noClassesShouldThatRuleStart.haveNameMatching(".*\\.List")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -117,7 +118,7 @@ public void haveNameNotMatching(ClassesThat noClassesS noClassesShouldThatRuleStart.haveNameNotMatching(".*\\.List")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -127,7 +128,7 @@ public void haveSimpleNameStartingWith(ClassesThat noC noClassesShouldThatRuleStart.haveSimpleNameStartingWith("Lis")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -137,7 +138,7 @@ public void haveSimpleNameNotStartingWith(ClassesThat noClassesShouldThatRuleStart.haveSimpleNameNotStartingWith("Lis")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -147,7 +148,7 @@ public void haveSimpleNameContaining(ClassesThat noCla noClassesShouldThatRuleStart.haveSimpleNameContaining("is")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -157,7 +158,7 @@ public void haveSimpleNameNotContaining(ClassesThat no noClassesShouldThatRuleStart.haveSimpleNameNotContaining("is")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -167,7 +168,7 @@ public void haveSimpleNameEndingWith(ClassesThat noCla noClassesShouldThatRuleStart.haveSimpleNameEndingWith("ist")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -177,7 +178,7 @@ public void haveSimpleNameNotEndingWith(ClassesThat no noClassesShouldThatRuleStart.haveSimpleNameNotEndingWith("ist")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -187,7 +188,7 @@ public void resideInAPackage(ClassesThat noClassesShou noClassesShouldThatRuleStart.resideInAPackage("..tngtech..")) .on(ClassAccessingPublicClass.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPublicClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPublicClass.class); } @Test @@ -197,7 +198,7 @@ public void resideOutsideOfPackage(ClassesThat noClass noClassesShouldThatRuleStart.resideOutsideOfPackage("..tngtech..")) .on(ClassAccessingPublicClass.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -207,7 +208,7 @@ public void resideInAnyPackage(ClassesThat noClassesSh noClassesShouldThatRuleStart.resideInAnyPackage("..tngtech..", "java.lang.reflect")) .on(ClassAccessingPublicClass.class, ClassAccessingString.class, ClassAccessingConstructor.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPublicClass.class, ClassAccessingConstructor.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPublicClass.class, ClassAccessingConstructor.class); } @Test @@ -217,7 +218,7 @@ public void resideOutsideOfPackages(ClassesThat noClas noClassesShouldThatRuleStart.resideOutsideOfPackages("..tngtech..", "java.lang.reflect") ).on(ClassAccessingPublicClass.class, ClassAccessingString.class, ClassAccessingConstructor.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class); } @Test @@ -227,7 +228,7 @@ public void arePublic(ClassesThat noClassesShouldThatR .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPublicClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPublicClass.class); } @Test @@ -237,7 +238,7 @@ public void areNotPublic(ClassesThat noClassesShouldTh .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); } @@ -248,7 +249,7 @@ public void areProtected(ClassesThat noClassesShouldTh .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingProtectedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingProtectedClass.class); } @Test @@ -258,7 +259,7 @@ public void areNotProtected(ClassesThat noClassesShoul .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class); } @@ -269,7 +270,7 @@ public void arePackagePrivate(ClassesThat noClassesSho .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPackagePrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPackagePrivateClass.class); } @Test @@ -279,7 +280,7 @@ public void areNotPackagePrivate(ClassesThat noClasses .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingProtectedClass.class); } @@ -290,7 +291,7 @@ public void arePrivate(ClassesThat noClassesShouldThat .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPrivateClass.class); } @Test @@ -300,7 +301,7 @@ public void areNotPrivate(ClassesThat noClassesShouldT .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessingPublicClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); } @@ -311,7 +312,7 @@ public void haveModifier(ClassesThat noClassesShouldTh .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPrivateClass.class); } @Test @@ -321,7 +322,7 @@ public void doNotHaveModifier(ClassesThat noClassesSho .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessingPublicClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); } @@ -332,7 +333,7 @@ public void areAnnotatedWith_type(ClassesThat noClasse noClassesShouldThatRuleStart.areAnnotatedWith(SomeAnnotation.class)) .on(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingAnnotatedClass.class); } @Test @@ -342,7 +343,7 @@ public void areNotAnnotatedWith_type(ClassesThat noCla noClassesShouldThatRuleStart.areNotAnnotatedWith(SomeAnnotation.class)) .on(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); } @Test @@ -352,7 +353,7 @@ public void areAnnotatedWith_typeName(ClassesThat noCl noClassesShouldThatRuleStart.areAnnotatedWith(SomeAnnotation.class.getName())) .on(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingAnnotatedClass.class); } @Test @@ -362,7 +363,7 @@ public void areNotAnnotatedWith_typeName(ClassesThat n noClassesShouldThatRuleStart.areNotAnnotatedWith(SomeAnnotation.class.getName())) .on(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); } @Test @@ -373,7 +374,7 @@ public void areAnnotatedWith_predicate(ClassesThat noC noClassesShouldThatRuleStart.areAnnotatedWith(hasNamePredicate)) .on(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingAnnotatedClass.class); } @Test @@ -384,7 +385,7 @@ public void areNotAnnotatedWith_predicate(ClassesThat noClassesShouldThatRuleStart.areNotAnnotatedWith(hasNamePredicate)) .on(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); } @Test @@ -395,7 +396,7 @@ public void areMetaAnnotatedWith_type(ClassesThat noCl .on(ClassAccessingMetaAnnotatedClass.class, ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingMetaAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingMetaAnnotatedClass.class); } @Test @@ -405,7 +406,7 @@ public void areNotMetaAnnotatedWith_type_access() { .on(ClassAccessingMetaAnnotatedClass.class, ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); } @Test @@ -415,7 +416,7 @@ public void areNotMetaAnnotatedWith_type_dependency() { .on(ClassAccessingMetaAnnotatedClass.class, ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); } @Test @@ -426,7 +427,7 @@ public void areMetaAnnotatedWith_typeName(ClassesThat .on(ClassAccessingMetaAnnotatedClass.class, ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingMetaAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingMetaAnnotatedClass.class); } @Test @@ -436,7 +437,7 @@ public void areNotMetaAnnotatedWith_typeName_access() { .on(ClassAccessingMetaAnnotatedClass.class, ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); } @Test @@ -446,7 +447,7 @@ public void areNotMetaAnnotatedWith_typeName_dependency() { .on(ClassAccessingMetaAnnotatedClass.class, ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); } @Test @@ -458,7 +459,7 @@ public void areMetaAnnotatedWith_predicate(ClassesThat .on(ClassAccessingMetaAnnotatedClass.class, ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingMetaAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingMetaAnnotatedClass.class); } @Test @@ -469,7 +470,7 @@ public void areNotMetaAnnotatedWith_predicate_access() { .on(ClassAccessingMetaAnnotatedClass.class, ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); } @Test @@ -480,7 +481,7 @@ public void areNotMetaAnnotatedWith_predicate_dependency() { .on(ClassAccessingMetaAnnotatedClass.class, ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); } @Test @@ -490,7 +491,7 @@ public void implement_type(ClassesThat noClassesShould noClassesShouldThatRuleStart.implement(Collection.class)) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); } @Test @@ -500,7 +501,7 @@ public void doNotImplement_type(ClassesThat noClassesS noClassesShouldThatRuleStart.doNotImplement(Collection.class)) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); } @Test @@ -510,7 +511,7 @@ public void implement_typeName(ClassesThat noClassesSh noClassesShouldThatRuleStart.implement(Collection.class.getName())) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); } @Test @@ -520,7 +521,7 @@ public void doNotImplement_typeName(ClassesThat noClas noClassesShouldThatRuleStart.doNotImplement(Collection.class.getName())) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); } @Test @@ -530,7 +531,7 @@ public void implement_predicate(ClassesThat noClassesS noClassesShouldThatRuleStart.implement(classWithNameOf(Collection.class))) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); } @Test @@ -540,7 +541,7 @@ public void doNotImplement_predicate(ClassesThat noCla noClassesShouldThatRuleStart.doNotImplement(classWithNameOf(Collection.class))) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); } @Test @@ -550,7 +551,7 @@ public void areAssignableTo_type(ClassesThat noClasses noClassesShouldThatRuleStart.areAssignableTo(Collection.class)) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -560,7 +561,7 @@ public void areNotAssignableTo_type(ClassesThat noClas noClassesShouldThatRuleStart.areNotAssignableTo(Collection.class)) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -570,7 +571,7 @@ public void areAssignableTo_typeName(ClassesThat noCla noClassesShouldThatRuleStart.areAssignableTo(Collection.class.getName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -580,7 +581,7 @@ public void areNotAssignableTo_typeName(ClassesThat no noClassesShouldThatRuleStart.areNotAssignableTo(Collection.class.getName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -590,7 +591,7 @@ public void areAssignableTo_predicate(ClassesThat noCl noClassesShouldThatRuleStart.areAssignableTo(classWithNameOf(Collection.class))) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -600,7 +601,7 @@ public void areNotAssignableTo_predicate(ClassesThat n noClassesShouldThatRuleStart.areNotAssignableTo(classWithNameOf(Collection.class))) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -611,7 +612,7 @@ public void areAssignableFrom_type(ClassesThat noClass .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingCollection.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingCollection.class, ClassAccessingIterable.class); } @Test @@ -622,7 +623,7 @@ public void areNotAssignableFrom_type(ClassesThat noCl .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingString.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingString.class); } @Test @@ -633,7 +634,7 @@ public void areAssignableFrom_typeName(ClassesThat noC .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingCollection.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingCollection.class, ClassAccessingIterable.class); } @Test @@ -644,7 +645,7 @@ public void areNotAssignableFrom_typeName(ClassesThat .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingString.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingString.class); } @Test @@ -655,7 +656,7 @@ public void areAssignableFrom_predicate(ClassesThat no .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingCollection.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingCollection.class, ClassAccessingIterable.class); } @Test @@ -666,7 +667,7 @@ public void areNotAssignableFrom_predicate(ClassesThat .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingString.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingString.class); } @Test @@ -677,7 +678,7 @@ public void areInterfaces_predicate(ClassesThat noClas .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingCollection.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingCollection.class); } @Test @@ -688,7 +689,7 @@ public void areNotInterfaces_predicate(ClassesThat noC .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingSimpleClass.class); } @Test @@ -698,7 +699,7 @@ public void areEnums_predicate(ClassesThat noClassesSh noClassesShouldThatRuleStart.areEnums()) .on(ClassAccessingEnum.class, ClassAccessingString.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingEnum.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingEnum.class); } @Test @@ -708,7 +709,7 @@ public void areNotEnums_predicate(ClassesThat noClasse noClassesShouldThatRuleStart.areNotEnums()) .on(ClassAccessingEnum.class, ClassAccessingString.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class); } @Test @@ -718,7 +719,7 @@ public void areTopLevelClasses_predicate(ClassesThat n noClassesShouldThatRuleStart.areTopLevelClasses()) .on(ClassAccessingTopLevelClass.class, ClassAccessingStaticNestedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); } @Test @@ -728,7 +729,7 @@ public void areNotTopLevelClasses_predicate(ClassesThat noC noClassesShouldThatRuleStart.areNestedClasses()) .on(ClassAccessingStaticNestedClass.class, ClassAccessingTopLevelClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class); } @Test @@ -748,7 +749,7 @@ public void areNotNestedClasses_predicate(ClassesThat noClassesShouldThatRuleStart.areNotNestedClasses()) .on(ClassAccessingStaticNestedClass.class, ClassAccessingTopLevelClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); } @Test @@ -758,7 +759,7 @@ public void areMemberClasses_predicate(ClassesThat noC noClassesShouldThatRuleStart.areMemberClasses()) .on(ClassAccessingStaticNestedClass.class, ClassAccessingTopLevelClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class); } @Test @@ -768,7 +769,7 @@ public void areNotMemberClasses_predicate(ClassesThat noClassesShouldThatRuleStart.areNotMemberClasses()) .on(ClassAccessingStaticNestedClass.class, ClassAccessingTopLevelClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); } @Test @@ -778,7 +779,7 @@ public void areInnerClasses_predicate(ClassesThat noCl noClassesShouldThatRuleStart.areInnerClasses()) .on(ClassAccessingInnerMemberClass.class, ClassAccessingTopLevelClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingInnerMemberClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingInnerMemberClass.class); } @Test @@ -788,7 +789,7 @@ public void areNotInnerClasses_predicate(ClassesThat n noClassesShouldThatRuleStart.areNotInnerClasses()) .on(ClassAccessingInnerMemberClass.class, ClassAccessingTopLevelClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); } @Test @@ -798,7 +799,7 @@ public void areAnonymousClasses_predicate(ClassesThat noClassesShouldThatRuleStart.areAnonymousClasses()) .on(ClassAccessingAnonymousClass.class, ClassAccessingTopLevelClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingAnonymousClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingAnonymousClass.class); } @Test @@ -808,7 +809,7 @@ public void areNotAnonymousClasses_predicate(ClassesThat noCl noClassesShouldThatRuleStart.areLocalClasses()) .on(ClassAccessingLocalClass.class, ClassAccessingTopLevelClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingLocalClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingLocalClass.class); } @Test @@ -828,7 +829,7 @@ public void areNotLocalClasses_predicate(ClassesThat n noClassesShouldThatRuleStart.areNotLocalClasses()) .on(ClassAccessingLocalClass.class, ClassAccessingTopLevelClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingTopLevelClass.class); } @Test @@ -839,7 +840,7 @@ public void belongToAnyOf(ClassesThat noClassesShouldT .on(ClassAccessingNestedInnerClass.class, ClassWithInnerClasses.class, ClassWithInnerClasses.InnerClass.class, ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingNestedInnerClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassAccessingNestedInnerClass.class, ClassWithInnerClasses.class, ClassWithInnerClasses.InnerClass.class, ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class, ClassAccessingString.class); } @@ -851,7 +852,7 @@ public void only_haveFullyQualifiedName(ClassesThat cl classesShouldOnlyThatRuleStart.haveFullyQualifiedName(List.class.getName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -861,7 +862,7 @@ public void only_doNotHaveFullyQualifiedName(ClassesThat classesSho classesShouldOnlyThatRuleStart.haveSimpleName(List.class.getSimpleName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -881,7 +882,7 @@ public void only_doNotHaveSimpleName(ClassesThat class classesShouldOnlyThatRuleStart.doNotHaveSimpleName(List.class.getSimpleName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -891,7 +892,7 @@ public void only_haveNameMatching(ClassesThat classesS classesShouldOnlyThatRuleStart.haveNameMatching(".*\\.List")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -901,7 +902,7 @@ public void only_haveNameNotMatching(ClassesThat class classesShouldOnlyThatRuleStart.haveNameNotMatching(".*\\.List")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -911,7 +912,7 @@ public void only_haveSimpleNameStartingWith(ClassesThat classesShouldOnlyThatRuleStart.haveSimpleNameContaining("is")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -941,7 +942,7 @@ public void only_haveSimpleNameNotContaining(ClassesThat classesShouldOnlyThatRuleStart.haveSimpleNameEndingWith("ist")) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -961,7 +962,7 @@ public void only_haveSimpleNameNotEndingWith(ClassesThat classesS classesShouldOnlyThatRuleStart.resideInAPackage("..tngtech..")) .on(ClassAccessingPublicClass.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -981,7 +982,7 @@ public void only_resideOutsideOfPackage(ClassesThat cl classesShouldOnlyThatRuleStart.resideOutsideOfPackage("..tngtech..")) .on(ClassAccessingPublicClass.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPublicClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPublicClass.class); } @Test @@ -991,7 +992,7 @@ public void only_resideInAnyPackage(ClassesThat classe classesShouldOnlyThatRuleStart.resideInAnyPackage("..tngtech..", "java.lang.reflect")) .on(ClassAccessingPublicClass.class, ClassAccessingString.class, ClassAccessingConstructor.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class); } @Test @@ -1001,7 +1002,7 @@ public void only_resideOutsideOfPackages(ClassesThat c classesShouldOnlyThatRuleStart.resideOutsideOfPackages("..tngtech..", "java.lang.reflect") ).on(ClassAccessingPublicClass.class, ClassAccessingString.class, ClassAccessingConstructor.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPublicClass.class, ClassAccessingConstructor.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPublicClass.class, ClassAccessingConstructor.class); } @Test @@ -1011,7 +1012,7 @@ public void only_arePublic(ClassesThat classesShouldOn .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); } @@ -1022,7 +1023,7 @@ public void only_areNotPublic(ClassesThat classesShoul .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPublicClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPublicClass.class); } @Test @@ -1032,7 +1033,7 @@ public void only_areProtected(ClassesThat classesShoul .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class); } @@ -1043,7 +1044,7 @@ public void only_areNotProtected(ClassesThat classesSh .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingProtectedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingProtectedClass.class); } @Test @@ -1053,7 +1054,7 @@ public void only_arePackagePrivate(ClassesThat classes .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingProtectedClass.class); } @@ -1064,7 +1065,7 @@ public void only_areNotPackagePrivate(ClassesThat clas .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPackagePrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPackagePrivateClass.class); } @Test @@ -1074,7 +1075,7 @@ public void only_arePrivate(ClassesThat classesShouldO .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessingPublicClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); } @@ -1085,7 +1086,7 @@ public void only_areNotPrivate(ClassesThat classesShou .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPrivateClass.class); } @Test @@ -1095,7 +1096,7 @@ public void only_haveModifier(ClassesThat classesShoul .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessingPublicClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); } @@ -1106,7 +1107,7 @@ public void only_doNotHaveModifier(ClassesThat classes .on(ClassAccessingPublicClass.class, ClassAccessingPrivateClass.class, ClassAccessingPackagePrivateClass.class, ClassAccessingProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingPrivateClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingPrivateClass.class); } @Test @@ -1116,7 +1117,7 @@ public void only_areAnnotatedWith_type(ClassesThat cla classesShouldOnlyThatRuleStart.areAnnotatedWith(SomeAnnotation.class)) .on(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); } @Test @@ -1126,7 +1127,7 @@ public void only_areNotAnnotatedWith_type(ClassesThat classesShouldOnlyThatRuleStart.areNotAnnotatedWith(SomeAnnotation.class)) .on(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingAnnotatedClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingAnnotatedClass.class); } @Test @@ -1136,7 +1137,7 @@ public void only_areAnnotatedWith_typeName(ClassesThat classesShouldOnlyThatRuleStart.areAnnotatedWith(SomeAnnotation.class.getName())) .on(ClassAccessingAnnotatedClass.class, ClassAccessingSimpleClass.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingSimpleClass.class); } @Test @@ -1146,7 +1147,7 @@ public void only_areNotAnnotatedWith_typeName(ClassesThat classesSho classesShouldOnlyThatRuleStart.implement(Collection.class)) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); } @Test @@ -1284,7 +1285,7 @@ public void only_doNotImplement_type(ClassesThat class classesShouldOnlyThatRuleStart.doNotImplement(Collection.class)) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); } @Test @@ -1294,7 +1295,7 @@ public void only_implement_typeName(ClassesThat classe classesShouldOnlyThatRuleStart.implement(Collection.class.getName())) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); } @Test @@ -1304,7 +1305,7 @@ public void only_doNotImplement_typeName(ClassesThat c classesShouldOnlyThatRuleStart.doNotImplement(Collection.class.getName())) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); } @Test @@ -1314,7 +1315,7 @@ public void only_implement_predicate(ClassesThat class classesShouldOnlyThatRuleStart.implement(classWithNameOf(Collection.class))) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingIterable.class); } @Test @@ -1324,7 +1325,7 @@ public void only_doNotImplement_predicate(ClassesThat classesShouldOnlyThatRuleStart.doNotImplement(classWithNameOf(Collection.class))) .on(ClassAccessingArrayList.class, ClassAccessingList.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingArrayList.class); } @Test @@ -1334,7 +1335,7 @@ public void only_areAssignableTo_type(ClassesThat clas classesShouldOnlyThatRuleStart.areAssignableTo(Collection.class)) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -1344,7 +1345,7 @@ public void only_areNotAssignableTo_type(ClassesThat c classesShouldOnlyThatRuleStart.areNotAssignableTo(Collection.class)) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThat(getOnlyElement(classes)).matches(ClassAccessingList.class); + assertThatType(getOnlyElement(classes)).matches(ClassAccessingList.class); } @Test @@ -1354,7 +1355,7 @@ public void only_areAssignableTo_typeName(ClassesThat classesShouldOnlyThatRuleStart.areAssignableTo(Collection.class.getName())) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -1364,7 +1365,7 @@ public void only_areNotAssignableTo_typeName(ClassesThat classesShouldOnlyThatRuleStart.areAssignableTo(classWithNameOf(Collection.class))) .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingIterable.class); } @Test @@ -1384,7 +1385,7 @@ public void only_areNotAssignableTo_predicate(ClassesThat cl .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingString.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingList.class, ClassAccessingString.class); } @Test @@ -1406,7 +1407,7 @@ public void only_areNotAssignableFrom_type(ClassesThat .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingIterable.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingCollection.class, ClassAccessingIterable.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingCollection.class, ClassAccessingIterable.class); } @Test @@ -1417,7 +1418,7 @@ public void only_areAssignableFrom_typeName(ClassesThat c .on(ClassAccessingList.class, ClassAccessingString.class, ClassAccessingCollection.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingSimpleClass.class); } @Test @@ -1472,7 +1473,7 @@ public void only_areNotInterfaces_predicate(ClassesThat apply(ArchRule rule) { Set classes = filterClassesInFailureReport.apply( noClasses().should().dependOnClassesThat(are(not(assignableFrom(classWithNameOf(Collection.class)))))); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassHavingFieldOfTypeList.class, ClassHavingMethodParameterOfTypeString.class, ClassHavingReturnTypeArrayList.class, ClassImplementingSerializable.class); @@ -1551,7 +1552,7 @@ public Set apply(ArchRule rule) { Set classes = filterClassesInFailureReport.apply( classes().should().onlyDependOnClassesThat(are(not(assignableFrom(classWithNameOf(Collection.class)))))); - assertThatClasses(classes).matchInAnyOrder(ClassHavingConstructorParameterOfTypeCollection.class); + assertThatTypes(classes).matchInAnyOrder(ClassHavingConstructorParameterOfTypeCollection.class); classes = filterClassesInFailureReport.apply( classes().should().onlyAccessClassesThat(are(not(assignableFrom(classWithNameOf(Collection.class)))))); 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 ce8ad78af7..2a7c81ec32 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 @@ -35,7 +35,7 @@ import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; import static com.tngtech.archunit.lang.syntax.elements.ClassesShouldEvaluator.filterClassesAppearingInFailureReport; import static com.tngtech.archunit.lang.syntax.elements.ClassesShouldEvaluator.filterViolationCausesInFailureReport; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; import static org.assertj.core.api.Assertions.assertThat; @@ -62,7 +62,7 @@ public void haveFullyQualifiedName(ClassesThat classes ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); } @@ -76,7 +76,7 @@ public void doNotHaveFullyQualifiedName(ClassesThat cl ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessedByFoo.class, Foo.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessedByFoo.class, Foo.class); } @Test @@ -88,7 +88,7 @@ public void haveSimpleName(ClassesThat classesShouldOn ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); } @@ -102,7 +102,7 @@ public void doNotHaveSimpleName(ClassesThat classesSho ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessedByFoo.class, Foo.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessedByFoo.class, Foo.class); } @Test @@ -114,7 +114,7 @@ public void haveNameMatching(ClassesThat classesShould ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); } @@ -128,7 +128,7 @@ public void haveNameNotMatching(ClassesThat classesSho ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessedByFoo.class, Foo.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessedByFoo.class, Foo.class); } @Test @@ -140,7 +140,7 @@ public void haveSimpleNameStartingWith(ClassesThat cla ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); } @@ -154,7 +154,7 @@ public void haveSimpleNameNotStartingWith(ClassesThat ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByFoo.class, Foo.class); } @@ -167,7 +167,7 @@ public void haveSimpleNameContaining(ClassesThat class ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); } @@ -181,7 +181,7 @@ public void haveSimpleNameNotContaining(ClassesThat cl ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByFoo.class, Foo.class); } @@ -194,7 +194,7 @@ public void haveSimpleNameEndingWith(ClassesThat class ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); } @@ -208,7 +208,7 @@ public void haveSimpleNameNotEndingWith(ClassesThat cl ClassAccessedByBar.class, Bar.class, ClassAccessedByBaz.class, Baz.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByFoo.class, Foo.class); } @@ -219,7 +219,7 @@ public void resideInAPackage(ClassesThat classesShould classesShouldOnlyBeBy.resideInAPackage("..access..")) .on(ClassAccessingOtherClass.class, ClassAlsoAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAlsoAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAlsoAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); } @Test @@ -229,7 +229,7 @@ public void resideOutsideOfPackage(ClassesThat classes classesShouldOnlyBeBy.resideOutsideOfPackage("..access..")) .on(ClassAccessingOtherClass.class, ClassAlsoAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); } @Test @@ -240,7 +240,7 @@ public void resideInAnyPackage(ClassesThat classesShou .on(ClassAccessingOtherClass.class, ClassAlsoAccessingOtherClass.class, YetAnotherClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(YetAnotherClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); + assertThatTypes(classes).matchInAnyOrder(YetAnotherClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); } @Test @@ -251,7 +251,7 @@ public void resideOutsideOfPackages(ClassesThat classe ).on(ClassAccessingOtherClass.class, ClassAlsoAccessingOtherClass.class, YetAnotherClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessingOtherClass.class, ClassAlsoAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); } @@ -264,7 +264,7 @@ public void arePublic(ClassesThat classesShouldOnlyBeB PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByPrivateClass.class, ClassAccessedByPackagePrivateClass.class, ClassAccessedByProtectedClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); @@ -279,7 +279,7 @@ public void areNotPublic(ClassesThat classesShouldOnly PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByPublicClass.class, PublicClass.class); } @@ -292,7 +292,7 @@ public void areProtected(ClassesThat classesShouldOnly PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByPublicClass.class, ClassAccessedByPrivateClass.class, ClassAccessedByPackagePrivateClass.class, PublicClass.class, PrivateClass.class, PackagePrivateClass.class); @@ -307,7 +307,7 @@ public void areNotProtected(ClassesThat classesShouldO PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByProtectedClass.class, ProtectedClass.class); } @@ -320,7 +320,7 @@ public void arePackagePrivate(ClassesThat classesShoul PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByPublicClass.class, ClassAccessedByPrivateClass.class, ClassAccessedByProtectedClass.class, PublicClass.class, PrivateClass.class, ProtectedClass.class); @@ -335,7 +335,7 @@ public void areNotPackagePrivate(ClassesThat classesSh PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByPackagePrivateClass.class, PackagePrivateClass.class); } @@ -348,7 +348,7 @@ public void arePrivate(ClassesThat classesShouldOnlyBe PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByPublicClass.class, ClassAccessedByPackagePrivateClass.class, ClassAccessedByProtectedClass.class, PublicClass.class, PackagePrivateClass.class, ProtectedClass.class); @@ -363,7 +363,7 @@ public void areNotPrivate(ClassesThat classesShouldOnl PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByPrivateClass.class, PrivateClass.class); } @@ -376,7 +376,7 @@ public void haveModifier(ClassesThat classesShouldOnly PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByPublicClass.class, ClassAccessedByPackagePrivateClass.class, ClassAccessedByProtectedClass.class, PublicClass.class, PackagePrivateClass.class, ProtectedClass.class); @@ -391,7 +391,7 @@ public void doNotHaveModifier(ClassesThat classesShoul PublicClass.class, PrivateClass.class, PackagePrivateClass.class, ProtectedClass.class); - assertThatClasses(classes).matchInAnyOrder( + assertThatTypes(classes).matchInAnyOrder( ClassAccessedByPrivateClass.class, PrivateClass.class); } @@ -403,7 +403,7 @@ public void areAnnotatedWith_type(ClassesThat classesS .on(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -414,7 +414,7 @@ public void areNotAnnotatedWith_type(ClassesThat class .on(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class); } @Test @@ -425,7 +425,7 @@ public void areAnnotatedWith_typeName(ClassesThat clas .on(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -436,7 +436,7 @@ public void areNotAnnotatedWith_typeName(ClassesThat c .on(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class); } @Test @@ -448,7 +448,7 @@ public void areAnnotatedWith_predicate(ClassesThat cla .on(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -460,7 +460,7 @@ public void areNotAnnotatedWith_predicate(ClassesThat .on(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class); } @Test @@ -473,7 +473,7 @@ public void areMetaAnnotatedWith_type(ClassesThat clas SimpleClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, ClassAccessingSimpleClass.class); } @@ -486,7 +486,7 @@ public void areNotMetaAnnotatedWith_type_access() { SimpleClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class); } @Test @@ -498,7 +498,7 @@ public void areNotMetaAnnotatedWith_type_dependency() { SimpleClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class, MetaAnnotatedAnnotation.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class, MetaAnnotatedAnnotation.class); } @Test @@ -511,7 +511,7 @@ public void areMetaAnnotatedWith_typeName(ClassesThat SimpleClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, ClassAccessingSimpleClass.class); } @@ -524,7 +524,7 @@ public void areNotMetaAnnotatedWith_typeName_access() { SimpleClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class); } @Test @@ -536,7 +536,7 @@ public void areNotMetaAnnotatedWith_typeName_dependency() { SimpleClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class, MetaAnnotatedAnnotation.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class, MetaAnnotatedAnnotation.class); } @Test @@ -550,7 +550,7 @@ public void areMetaAnnotatedWith_predicate(ClassesThat SimpleClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByAnnotatedClass.class, AnnotatedClass.class, SimpleClass.class, ClassAccessingSimpleClass.class); } @@ -564,7 +564,7 @@ public void areNotMetaAnnotatedWith_predicate_access() { SimpleClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class); } @Test @@ -577,7 +577,7 @@ public void areNotMetaAnnotatedWith_predicate_dependency() { SimpleClass.class, ClassAccessingSimpleClass.class, MetaAnnotatedAnnotation.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class, MetaAnnotatedAnnotation.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByMetaAnnotatedClass.class, MetaAnnotatedClass.class, MetaAnnotatedAnnotation.class); } @Test @@ -588,7 +588,7 @@ public void implement_type(ClassesThat classesShouldOn .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -599,7 +599,7 @@ public void doNotImplement_type(ClassesThat classesSho .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); + assertThatTypes(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @Test @@ -610,7 +610,7 @@ public void implement_typeName(ClassesThat classesShou .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -621,7 +621,7 @@ public void doNotImplement_typeName(ClassesThat classe .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); + assertThatTypes(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @Test @@ -632,7 +632,7 @@ public void implement_predicate(ClassesThat classesSho .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -643,7 +643,7 @@ public void doNotImplement_predicate(ClassesThat class .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); + assertThatTypes(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @Test @@ -654,7 +654,7 @@ public void areAssignableTo_type(ClassesThat classesSh .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -665,7 +665,7 @@ public void areNotAssignableTo_type(ClassesThat classe .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); + assertThatTypes(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @Test @@ -676,7 +676,7 @@ public void areAssignableTo_typeName(ClassesThat class .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -687,7 +687,7 @@ public void areNotAssignableTo_typeName(ClassesThat cl .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); + assertThatTypes(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @Test @@ -698,7 +698,7 @@ public void areAssignableTo_predicate(ClassesThat clas .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -709,7 +709,7 @@ public void areNotAssignableTo_predicate(ClassesThat c .on(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); + assertThatTypes(classes).matchInAnyOrder(ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @Test @@ -720,7 +720,7 @@ public void areAssignableFrom_type(ClassesThat classes .on(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -731,7 +731,7 @@ public void areNotAssignableFrom_type(ClassesThat clas .on(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassExtendingClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @@ -743,7 +743,7 @@ public void areAssignableFrom_typeName(ClassesThat cla .on(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -754,7 +754,7 @@ public void areNotAssignableFrom_typeName(ClassesThat .on(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassExtendingClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @@ -766,7 +766,7 @@ public void areAssignableFrom_predicate(ClassesThat cl .on(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(SimpleClass.class, ClassAccessingSimpleClass.class); } @Test @@ -777,7 +777,7 @@ public void areNotAssignableFrom_predicate(ClassesThat .on(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassExtendingClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @@ -788,7 +788,7 @@ public void areInterfaces_predicate(ClassesThat classe classesShouldOnlyBeBy.areInterfaces()) .on(ClassAccessingSimpleClass.class, SimpleClass.class, ClassBeingAccessedByInterface.class, InterfaceAccessingAClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingSimpleClass.class, SimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingSimpleClass.class, SimpleClass.class); } @Test @@ -798,7 +798,7 @@ public void areNotInterfaces_predicate(ClassesThat cla classesShouldOnlyBeBy.areNotInterfaces()) .on(ClassAccessingSimpleClass.class, SimpleClass.class, ClassBeingAccessedByInterface.class, InterfaceAccessingAClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByInterface.class, InterfaceAccessingAClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByInterface.class, InterfaceAccessingAClass.class); } @Test @@ -808,7 +808,7 @@ public void areEnums_predicate(ClassesThat classesShou classesShouldOnlyBeBy.areEnums()) .on(ClassAccessingSimpleClass.class, SimpleClass.class, ClassBeingAccessedByEnum.class, EnumAccessingAClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingSimpleClass.class, SimpleClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingSimpleClass.class, SimpleClass.class); } @Test @@ -818,7 +818,7 @@ public void areNotEnums_predicate(ClassesThat classesS classesShouldOnlyBeBy.areNotEnums()) .on(ClassAccessingSimpleClass.class, SimpleClass.class, ClassBeingAccessedByEnum.class, EnumAccessingAClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByEnum.class, EnumAccessingAClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByEnum.class, EnumAccessingAClass.class); } @Test @@ -829,7 +829,7 @@ public void areTopLevelClasses_predicate(ClassesThat c .on(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class, ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class); } @Test @@ -840,7 +840,7 @@ public void areNotTopLevelClasses_predicate(ClassesThat cla .on(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class, ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); } @Test @@ -862,7 +862,7 @@ public void areNotNestedClasses_predicate(ClassesThat .on(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class, ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class); } @Test @@ -873,7 +873,7 @@ public void areMemberClasses_predicate(ClassesThat cla .on(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class, ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); } @Test @@ -884,7 +884,7 @@ public void areNotMemberClasses_predicate(ClassesThat .on(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class, ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingStaticNestedClass.class, StaticNestedClassBeingAccessed.class); } @Test @@ -895,7 +895,7 @@ public void areInnerClasses_predicate(ClassesThat clas .on(ClassAccessingInnerMemberClass.class, InnerMemberClassBeingAccessed.class, ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); } @Test @@ -906,7 +906,7 @@ public void areNotInnerClasses_predicate(ClassesThat c .on(ClassAccessingInnerMemberClass.class, InnerMemberClassBeingAccessed.class, ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingInnerMemberClass.class, InnerMemberClassBeingAccessed.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingInnerMemberClass.class, InnerMemberClassBeingAccessed.class); } @Test @@ -917,7 +917,7 @@ public void areAnonymousClasses_predicate(ClassesThat .on(ClassAccessingAnonymousClass.class, anonymousClassBeingAccessed.getClass(), ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); } @Test @@ -928,7 +928,7 @@ public void areNotAnonymousClasses_predicate(ClassesThat clas .on(ClassBeingAccessedByLocalClass.class, ClassBeingAccessedByLocalClass.getLocalClass(), ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); } @Test @@ -950,7 +950,7 @@ public void areNotLocalClasses_predicate(ClassesThat c .on(ClassBeingAccessedByLocalClass.class, ClassBeingAccessedByLocalClass.getLocalClass(), ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByLocalClass.class, ClassBeingAccessedByLocalClass.getLocalClass()); + assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByLocalClass.class, ClassBeingAccessedByLocalClass.getLocalClass()); } @Test @@ -964,7 +964,7 @@ public void belongToAnyOf(ClassesThat classesShouldOnl AnotherClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class, ClassBeingAccessedByInnerClass.class); - assertThatClasses(classes).matchInAnyOrder(AnotherClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class); + assertThatTypes(classes).matchInAnyOrder(AnotherClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class); } @DataProvider @@ -981,7 +981,7 @@ public void classesThat_predicate(ArchRule rule) { .on(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class, SimpleClass.class, ClassAccessingSimpleClass.class); - assertThatClasses(classes).matchInAnyOrder(ClassExtendingClass.class, + assertThatTypes(classes).matchInAnyOrder(ClassExtendingClass.class, ClassImplementingSomeInterface.class, ClassBeingAccessedByClassImplementingSomeInterface.class); } @@ -1003,7 +1003,7 @@ public Set apply(ArchRule rule) { Set classes = filterViolationOriginsInFailureReport.apply( classes().should().onlyHaveDependentClassesThat(have(not(nameMatching(".*DependingVia.*"))))); - assertThatClasses(classes).matchInAnyOrder(ClassDependingViaFieldType.class, + assertThatTypes(classes).matchInAnyOrder(ClassDependingViaFieldType.class, ClassDependingViaMethodParameter.class, ClassDependingViaImplementing.class); classes = filterViolationOriginsInFailureReport.apply( diff --git a/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SlicesTest.java b/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SlicesTest.java index f2a5b464a7..e0017b7905 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SlicesTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SlicesTest.java @@ -23,7 +23,7 @@ import static com.tngtech.archunit.core.domain.TestUtils.dependencyFrom; import static com.tngtech.archunit.core.domain.TestUtils.importClassesWithContext; import static com.tngtech.archunit.core.domain.TestUtils.simulateCall; -import static com.tngtech.archunit.testutil.Assertions.assertThatClasses; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static org.assertj.core.api.Assertions.assertThat; public class SlicesTest { @@ -100,8 +100,8 @@ public void slices_from_identifier() { assertThat(slices.getDescription()).isEqualTo("slices assigned from some description"); assertThat(slices).extractingResultOf("getDescription").containsOnly("Any Lang - $2", "Any Adjusted - Util"); assertThat(slices).hasSize(2); - assertThatClasses(getSliceOf(Object.class, slices)).contain(Number.class); - assertThatClasses(getSliceOf(List.class, slices)).contain(Collection.class); + assertThatTypes(getSliceOf(Object.class, slices)).contain(Number.class); + assertThatTypes(getSliceOf(List.class, slices)).contain(Collection.class); Assertions.assertThat(tryGetSliceOf(File.class, slices)) .as("Slice of class java.io.File (which should be missing from the assignment)") .isAbsent(); @@ -141,4 +141,4 @@ public SliceIdentifier getIdentifierOf(JavaClass javaClass) { } }; } -} \ No newline at end of file +} diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java b/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java index 7d194e4062..9b4d8595d7 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java @@ -1,19 +1,12 @@ package com.tngtech.archunit.testutil; -import java.util.Arrays; import java.util.Collection; -import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.domain.AccessTarget; @@ -34,8 +27,8 @@ import com.tngtech.archunit.core.domain.JavaMember; import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.core.domain.JavaMethodCall; -import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.core.domain.JavaPackage; +import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.ThrowsClause; import com.tngtech.archunit.core.domain.ThrowsDeclaration; import com.tngtech.archunit.lang.ArchCondition; @@ -47,6 +40,7 @@ import com.tngtech.archunit.testutil.assertion.DependenciesAssertion; import com.tngtech.archunit.testutil.assertion.DependencyAssertion; import com.tngtech.archunit.testutil.assertion.DescribedPredicateAssertion; +import com.tngtech.archunit.testutil.assertion.JavaClassDescriptorAssertion; import com.tngtech.archunit.testutil.assertion.JavaCodeUnitAssertion; import com.tngtech.archunit.testutil.assertion.JavaConstructorAssertion; import com.tngtech.archunit.testutil.assertion.JavaFieldAssertion; @@ -56,22 +50,20 @@ import com.tngtech.archunit.testutil.assertion.JavaMethodAssertion; import com.tngtech.archunit.testutil.assertion.JavaMethodsAssertion; import com.tngtech.archunit.testutil.assertion.JavaPackagesAssertion; +import com.tngtech.archunit.testutil.assertion.JavaTypeAssertion; +import com.tngtech.archunit.testutil.assertion.JavaTypesAssertion; import org.assertj.core.api.AbstractIterableAssert; import org.assertj.core.api.AbstractListAssert; import org.assertj.core.api.AbstractObjectAssert; import org.assertj.core.api.Condition; import org.assertj.core.api.ObjectAssert; import org.assertj.core.api.ObjectAssertFactory; -import org.objectweb.asm.Type; import static com.tngtech.archunit.core.domain.Formatters.formatMethodSimple; import static com.tngtech.archunit.core.domain.JavaClass.namesOf; import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; import static com.tngtech.archunit.core.domain.TestUtils.resolvedTargetFrom; import static com.tngtech.archunit.core.domain.TestUtils.targetFrom; -import static com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion.propertiesOf; -import static com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion.runtimePropertiesOf; -import static com.tngtech.archunit.testutil.assertion.JavaPackagesAssertion.sortByName; public class Assertions extends org.assertj.core.api.Assertions { public static ArchConditionAssertion assertThat(ArchCondition archCondition) { @@ -94,12 +86,12 @@ public static DescribedPredicateAssertion assertThat(DescribedPredicate(predicate); } - public static JavaClassAssertion assertThat(JavaClass javaClass) { - return new JavaClassAssertion(javaClass); + public static JavaTypeAssertion assertThatType(JavaType javaType) { + return new JavaTypeAssertion(javaType); } - public static JavaClassesAssertion assertThatClasses(Iterable javaClasses) { - return new JavaClassesAssertion(javaClasses); + public static JavaTypesAssertion assertThatTypes(Iterable javaTypes) { + return new JavaTypesAssertion(javaTypes); } public static JavaPackagesAssertion assertThatPackages(Iterable javaPackages) { @@ -134,8 +126,8 @@ public static JavaFieldsAssertion assertThatFields(Iterable fields) { return new JavaFieldsAssertion(fields); } - public static JavaClassesAssertion assertThat(JavaClass[] javaClasses) { - return new JavaClassesAssertion(javaClasses); + public static JavaTypesAssertion assertThat(JavaType[] javaTypes) { + return new JavaTypesAssertion(javaTypes); } public static JavaClassListAssertion assertThat(JavaClassList javaClasses) { @@ -195,7 +187,7 @@ public Step2 from(Class originClass, String codeUnitName) { return new Step2(originClass, codeUnitName); } - public class Step2 { + public static class Step2 { private final Class originClass; private final String originCodeUnitName; @@ -249,104 +241,6 @@ public static ConstructorCallAssertion assertThatCall(JavaConstructorCall call) return new ConstructorCallAssertion(call); } - public static class JavaClassesAssertion extends AbstractObjectAssert { - private JavaClassesAssertion(JavaClass[] actual) { - super(actual, JavaClassesAssertion.class); - } - - private JavaClassesAssertion(Iterable actual) { - super(sort(actual), JavaClassesAssertion.class); - } - - private static JavaClass[] sort(Iterable actual) { - JavaClass[] result = Iterables.toArray(actual, JavaClass.class); - sortByName(result); - return result; - } - - public void matchInAnyOrder(Iterable> classes) { - Class[] sorted = Iterables.toArray(classes, Class.class); - Arrays.sort(sorted, new Comparator>() { - @Override - public int compare(Class o1, Class o2) { - return o1.getName().compareTo(o2.getName()); - } - }); - matchExactly(sorted); - } - - public void matchInAnyOrder(Class... classes) { - matchInAnyOrder(ImmutableSet.copyOf(classes)); - } - - public void matchExactly(Class... classes) { - assertThat(TestUtils.namesOf(actual)).as("classes").containsExactlyElementsOf(namesOf(classes)); - for (int i = 0; i < actual.length; i++) { - assertThat(actual[i]).as("Element %d", i).matches(classes[i]); - } - } - - public JavaClassesAssertion contain(Class... classes) { - contain(ImmutableSet.copyOf(classes)); - return this; - } - - public void doNotContain(Class... classes) { - assertThat(actualNames()).doesNotContainAnyElementsOf(JavaClass.namesOf(classes)); - } - - public void contain(Iterable> classes) { - List expectedNames = JavaClass.namesOf(Lists.newArrayList(classes)); - assertThat(actualNames()).as("actual classes").containsAll(expectedNames); - } - - private Set actualNames() { - Set actualNames = new HashSet<>(); - for (JavaClass javaClass : actual) { - actualNames.add(javaClass.getName()); - } - return actualNames; - } - } - - public static class JavaClassAssertion extends AbstractObjectAssert { - private static final Pattern ARRAY_PATTERN = Pattern.compile("(\\[+)(.*)"); - - private JavaClassAssertion(JavaClass javaClass) { - super(javaClass, JavaClassAssertion.class); - } - - public void matches(Class clazz) { - assertThat(actual.getName()).as("Name of " + actual) - .isEqualTo(clazz.getName()); - assertThat(actual.getSimpleName()).as("Simple name of " + actual) - .isEqualTo(ensureArrayName(clazz.getSimpleName())); - assertThat(actual.getPackage().getName()).as("Package of " + actual) - .isEqualTo(getExpectedPackageName(clazz)); - assertThat(actual.getPackageName()).as("Package name of " + actual) - .isEqualTo(getExpectedPackageName(clazz)); - assertThat(actual.getModifiers()).as("Modifiers of " + actual) - .isEqualTo(JavaModifier.getModifiersForClass(clazz.getModifiers())); - assertThat(actual.isArray()).as(actual + " is array").isEqualTo(clazz.isArray()); - assertThat(runtimePropertiesOf(actual.getAnnotations())).as("Annotations of " + actual) - .isEqualTo(propertiesOf(clazz.getAnnotations())); - - if (clazz.isArray()) { - new JavaClassAssertion(actual.getComponentType()).matches(clazz.getComponentType()); - } - } - - private String ensureArrayName(String name) { - String suffix = ""; - Matcher matcher = ARRAY_PATTERN.matcher(name); - if (matcher.matches()) { - name = Type.getType(matcher.group(2)).getClassName(); - suffix = Strings.repeat("[]", matcher.group(1).length()); - } - return name + suffix; - } - } - public static class JavaClassListAssertion extends AbstractListAssert, JavaClass, ObjectAssert> { private JavaClassListAssertion(JavaClassList javaClasses) { @@ -356,7 +250,7 @@ private JavaClassListAssertion(JavaClassList javaClasses) { public void matches(Class... classes) { assertThat(actual).as("JavaClasses").hasSize(classes.length); for (int i = 0; i < actual.size(); i++) { - assertThat(actual.get(i)).as("Element %d", i).matches(classes[i]); + assertThatType(actual.get(i)).as("Element %d", i).matches(classes[i]); } } @@ -397,7 +291,7 @@ private ThrowsDeclarationAssertion(ThrowsDeclaration throwsDeclaration) { } public void matches(Class clazz) { - assertThat(actual.getRawType()).as("Type of " + actual) + assertThatType(actual.getRawType()).as("Type of " + actual) .matches(clazz); } } @@ -541,23 +435,4 @@ protected ConstructorCallAssertion newAssertion(JavaConstructorCall call) { } } - public static class JavaClassDescriptorAssertion extends AbstractObjectAssert { - private JavaClassDescriptorAssertion(JavaClassDescriptor actual) { - super(actual, JavaClassDescriptorAssertion.class); - } - - public void isEquivalentTo(Class clazz) { - assertThat(actual.getFullyQualifiedClassName()).as("name").isEqualTo(clazz.getName()); - assertThat(actual.getSimpleClassName()).as("simple name").isEqualTo(clazz.getSimpleName()); - String expectedPackageName = getExpectedPackageName(clazz); - assertThat(actual.getPackageName()).as("package").isEqualTo(expectedPackageName); - } - } - - private static String getExpectedPackageName(Class clazz) { - if (!clazz.isArray()) { - return clazz.getPackage() != null ? clazz.getPackage().getName() : ""; - } - return getExpectedPackageName(clazz.getComponentType()); - } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/TestUtils.java b/archunit/src/test/java/com/tngtech/archunit/testutil/TestUtils.java index 5bcabd9d79..e056b4663e 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/TestUtils.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/TestUtils.java @@ -2,12 +2,14 @@ import java.io.File; import java.lang.reflect.Method; -import java.util.LinkedHashSet; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; import java.util.Properties; import java.util.Random; -import java.util.Set; -import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableList; import com.tngtech.archunit.core.domain.properties.HasName; import org.assertj.core.util.Files; @@ -56,15 +58,24 @@ public static Properties properties(String... keyValues) { return result; } - public static Set namesOf(HasName[] thingsWithNames) { - return namesOf(ImmutableSet.copyOf(thingsWithNames)); + public static List namesOf(HasName[] thingsWithNames) { + return namesOf(ImmutableList.copyOf(thingsWithNames)); } - public static Set namesOf(Iterable thingsWithNames) { - Set result = new LinkedHashSet<>(); + public static List namesOf(Iterable thingsWithNames) { + List result = new ArrayList<>(); for (HasName hasName : thingsWithNames) { result.add(hasName.getName()); } return result; } + + public static void sortByName(T[] result) { + Arrays.sort(result, new Comparator() { + @Override + public int compare(HasName o1, HasName o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaClassDescriptorAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaClassDescriptorAssertion.java new file mode 100644 index 0000000000..705454b11b --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaClassDescriptorAssertion.java @@ -0,0 +1,20 @@ +package com.tngtech.archunit.testutil.assertion; + +import com.tngtech.archunit.core.domain.JavaClassDescriptor; +import org.assertj.core.api.AbstractObjectAssert; + +import static com.tngtech.archunit.testutil.assertion.JavaTypeAssertion.getExpectedPackageName; +import static org.assertj.core.api.Assertions.assertThat; + +public class JavaClassDescriptorAssertion extends AbstractObjectAssert { + public JavaClassDescriptorAssertion(JavaClassDescriptor actual) { + super(actual, JavaClassDescriptorAssertion.class); + } + + public void isEquivalentTo(Class clazz) { + assertThat(actual.getFullyQualifiedClassName()).as("name").isEqualTo(clazz.getName()); + assertThat(actual.getSimpleClassName()).as("simple name").isEqualTo(clazz.getSimpleName()); + String expectedPackageName = getExpectedPackageName(clazz); + assertThat(actual.getPackageName()).as("package").isEqualTo(expectedPackageName); + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaCodeUnitAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaCodeUnitAssertion.java index 1bf4943487..5497552980 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaCodeUnitAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaCodeUnitAssertion.java @@ -6,6 +6,7 @@ import com.tngtech.archunit.core.domain.JavaCodeUnit; import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; public class JavaCodeUnitAssertion> extends JavaMemberAssertion { @@ -17,12 +18,12 @@ public JavaCodeUnitAssertion(T javaMember, Class selfType) { public void isEquivalentTo(Method method) { super.isEquivalentTo(method); assertThat(actual.getRawParameterTypes()).matches(method.getParameterTypes()); - assertThat(actual.getRawReturnType()).matches(method.getReturnType()); + assertThatType(actual.getRawReturnType()).matches(method.getReturnType()); } public void isEquivalentTo(Constructor constructor) { super.isEquivalentTo(constructor); assertThat(actual.getRawParameterTypes()).matches(constructor.getParameterTypes()); - assertThat(actual.getRawReturnType()).matches(void.class); + assertThatType(actual.getRawReturnType()).matches(void.class); } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaConstructorAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaConstructorAssertion.java index 4710c1115e..221d0db073 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaConstructorAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaConstructorAssertion.java @@ -7,6 +7,7 @@ import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; import static com.tngtech.archunit.testutil.assertion.JavaMemberAssertion.getExpectedNameOf; public class JavaConstructorAssertion extends AbstractObjectAssert { @@ -19,6 +20,6 @@ public void isEquivalentTo(Constructor constructor) { assertThat(actual.getName()).isEqualTo(CONSTRUCTOR_NAME); assertThat(actual.getFullName()).isEqualTo(getExpectedNameOf(constructor, CONSTRUCTOR_NAME)); assertThat(actual.getRawParameterTypes()).matches(constructor.getParameterTypes()); - assertThat(actual.getRawReturnType()).matches(void.class); + assertThatType(actual.getRawReturnType()).matches(void.class); } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaFieldAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaFieldAssertion.java index 53df47aaa3..c82d45f264 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaFieldAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaFieldAssertion.java @@ -4,7 +4,7 @@ import com.tngtech.archunit.core.domain.JavaField; -import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; public class JavaFieldAssertion extends JavaMemberAssertion { public JavaFieldAssertion(JavaField javaField) { @@ -13,6 +13,6 @@ public JavaFieldAssertion(JavaField javaField) { public void isEquivalentTo(Field field) { super.isEquivalentTo(field); - assertThat(actual.getRawType()).matches(field.getType()); + assertThatType(actual.getRawType()).matches(field.getType()); } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMethodAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMethodAssertion.java index 53fdedb7b1..12f7990938 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMethodAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaMethodAssertion.java @@ -6,8 +6,9 @@ import org.assertj.core.api.AbstractObjectAssert; import static com.tngtech.archunit.testutil.Assertions.assertThat; -import static com.tngtech.archunit.testutil.assertion.JavaMembersAssertion.assertEquivalent; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; import static com.tngtech.archunit.testutil.assertion.JavaMemberAssertion.getExpectedNameOf; +import static com.tngtech.archunit.testutil.assertion.JavaMembersAssertion.assertEquivalent; public class JavaMethodAssertion extends AbstractObjectAssert { public JavaMethodAssertion(JavaMethod javaMethod) { @@ -19,12 +20,12 @@ public JavaMethodAssertion isEquivalentTo(Method method) { assertThat(actual.getFullName()).isEqualTo(getExpectedNameOf(method, method.getName())); assertThat(actual.getName()).isEqualTo(method.getName()); assertThat(actual.getRawParameterTypes()).matches(method.getParameterTypes()); - assertThat(actual.getRawReturnType()).matches(method.getReturnType()); + assertThatType(actual.getRawReturnType()).matches(method.getReturnType()); return this; } public JavaMethodAssertion isEquivalentTo(Class owner, String methodName, Class... parameterTypes) { - assertThat(actual.getOwner()).matches(owner); + assertThatType(actual.getOwner()).matches(owner); assertThat(actual.getName()).isEqualTo(methodName); assertThat(actual.getRawParameterTypes()).matches(parameterTypes); return this; diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaPackagesAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaPackagesAssertion.java index a9de2f0968..a3ea7903a1 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaPackagesAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaPackagesAssertion.java @@ -1,7 +1,5 @@ package com.tngtech.archunit.testutil.assertion; -import java.util.Arrays; -import java.util.Comparator; import java.util.HashSet; import java.util.Set; @@ -9,7 +7,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.tngtech.archunit.core.domain.JavaPackage; -import com.tngtech.archunit.core.domain.properties.HasName; +import com.tngtech.archunit.testutil.TestUtils; import org.assertj.core.api.AbstractObjectAssert; import static com.tngtech.archunit.base.Guava.toGuava; @@ -24,7 +22,7 @@ public JavaPackagesAssertion(Iterable javaPackages) { private static JavaPackage[] sort(Iterable actual) { JavaPackage[] result = Iterables.toArray(actual, JavaPackage.class); - sortByName(result); + TestUtils.sortByName(result); return result; } @@ -60,13 +58,4 @@ private ImmutableSet getActualNames() { private ImmutableSet getActualRelativeNames() { return FluentIterable.from(actual).transform(toGuava(GET_RELATIVE_NAME)).toSet(); } - - public static void sortByName(T[] result) { - Arrays.sort(result, new Comparator() { - @Override - public int compare(HasName o1, HasName o2) { - return o1.getName().compareTo(o2.getName()); - } - }); - } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java new file mode 100644 index 0000000000..81c3ed072a --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java @@ -0,0 +1,67 @@ +package com.tngtech.archunit.testutil.assertion; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.google.common.base.Strings; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaModifier; +import com.tngtech.archunit.core.domain.JavaType; +import org.assertj.core.api.AbstractObjectAssert; +import org.objectweb.asm.Type; + +import static com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion.propertiesOf; +import static com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion.runtimePropertiesOf; +import static org.assertj.core.api.Assertions.assertThat; + +public class JavaTypeAssertion extends AbstractObjectAssert { + private static final Pattern ARRAY_PATTERN = Pattern.compile("(\\[+)(.*)"); + + public JavaTypeAssertion(JavaType javaType) { + super(javaType, JavaTypeAssertion.class); + } + + public static String getExpectedPackageName(Class clazz) { + if (!clazz.isArray()) { + return clazz.getPackage() != null ? clazz.getPackage().getName() : ""; + } + return getExpectedPackageName(clazz.getComponentType()); + } + + public void matches(Class clazz) { + JavaClass javaClass = actualClass(); + + assertThat(javaClass.getName()).as("Name of " + javaClass) + .isEqualTo(clazz.getName()); + assertThat(javaClass.getSimpleName()).as("Simple name of " + javaClass) + .isEqualTo(ensureArrayName(clazz.getSimpleName())); + assertThat(javaClass.getPackage().getName()).as("Package of " + javaClass) + .isEqualTo(getExpectedPackageName(clazz)); + assertThat(javaClass.getPackageName()).as("Package name of " + javaClass) + .isEqualTo(getExpectedPackageName(clazz)); + assertThat(javaClass.getModifiers()).as("Modifiers of " + javaClass) + .isEqualTo(JavaModifier.getModifiersForClass(clazz.getModifiers())); + assertThat(javaClass.isArray()).as(javaClass + " is array").isEqualTo(clazz.isArray()); + assertThat(runtimePropertiesOf(javaClass.getAnnotations())).as("Annotations of " + javaClass) + .isEqualTo(propertiesOf(clazz.getAnnotations())); + + if (clazz.isArray()) { + new JavaTypeAssertion(javaClass.getComponentType()).matches(clazz.getComponentType()); + } + } + + private JavaClass actualClass() { + assertThat(actual).isInstanceOf(JavaClass.class); + return (JavaClass) actual; + } + + private String ensureArrayName(String name) { + String suffix = ""; + Matcher matcher = ARRAY_PATTERN.matcher(name); + if (matcher.matches()) { + name = Type.getType(matcher.group(2)).getClassName(); + suffix = Strings.repeat("[]", matcher.group(1).length()); + } + return name + suffix; + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypesAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypesAssertion.java new file mode 100644 index 0000000000..c0c108eb83 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypesAssertion.java @@ -0,0 +1,89 @@ +package com.tngtech.archunit.testutil.assertion; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaType; +import com.tngtech.archunit.testutil.TestUtils; +import org.assertj.core.api.AbstractObjectAssert; + +import static com.google.common.collect.Iterables.toArray; +import static com.tngtech.archunit.core.domain.JavaClass.namesOf; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.TestUtils.sortByName; +import static java.util.Arrays.sort; +import static org.assertj.core.api.Assertions.assertThat; + +public class JavaTypesAssertion extends AbstractObjectAssert { + public JavaTypesAssertion(JavaType[] actual) { + super(actual, JavaTypesAssertion.class); + } + + public JavaTypesAssertion(Iterable actual) { + super(toArray(actual, JavaType.class), JavaTypesAssertion.class); + } + + public void matchInAnyOrder(Iterable> classes) { + assertThat(TestUtils.namesOf(actual)).as("classes").containsOnlyElementsOf(namesOf(classes)); + + JavaType[] actualSorted = sortedJavaTypes(actual); + Class[] expectedSorted = sortedClasses(classes); + for (int i = 0; i < actualSorted.length; i++) { + assertThatType(actualSorted[i]).as("Element %d", i).matches(expectedSorted[i]); + } + } + + public void matchInAnyOrder(Class... classes) { + matchInAnyOrder(ImmutableSet.copyOf(classes)); + } + + public void matchExactly(Class... classes) { + assertThat(TestUtils.namesOf(actual)).as("classes").containsExactlyElementsOf(namesOf(classes)); + matchInAnyOrder(classes); + } + + public JavaTypesAssertion contain(Class... classes) { + contain(ImmutableSet.copyOf(classes)); + return this; + } + + public void doNotContain(Class... classes) { + assertThat(actualNames()).doesNotContainAnyElementsOf(JavaClass.namesOf(classes)); + } + + public void contain(Iterable> classes) { + List expectedNames = JavaClass.namesOf(Lists.newArrayList(classes)); + assertThat(actualNames()).as("actual classes").containsAll(expectedNames); + } + + private Set actualNames() { + Set actualNames = new HashSet<>(); + for (JavaType javaClass : actual) { + actualNames.add(javaClass.getName()); + } + return actualNames; + } + + private JavaType[] sortedJavaTypes(JavaType[] javaTypes) { + JavaType[] result = Arrays.copyOf(javaTypes, javaTypes.length); + sortByName(result); + return result; + } + + private Class[] sortedClasses(Iterable> classes) { + Class[] sorted = toArray(classes, Class.class); + sort(sorted, new Comparator>() { + @Override + public int compare(Class o1, Class o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + return sorted; + } +} From 550d20176b7f75436913d0574d03a7f7ae21b803 Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Sun, 1 Mar 2020 22:30:47 +0100 Subject: [PATCH 032/115] add support for simple class type parameters Introduce `JavaTypeVariable` to model type parameters like `MyClass`. For now the model is quite simple, it only supports type bounds that are not generic themselves. In fact the current implementation will behave wrongly, as soon as there are types like `MyClass>`. However the current state already shows correct behavior for simple cases like `MyClass`. Signed-off-by: Peter Gafert --- .../domain/DomainObjectCreationContext.java | 9 ++ .../archunit/core/domain/ImportContext.java | 3 + .../archunit/core/domain/JavaClass.java | 11 ++ .../core/domain/JavaTypeVariable.java | 92 ++++++++++++ .../core/importer/ClassFileImportRecord.java | 12 ++ .../core/importer/ClassFileProcessor.java | 22 +-- .../core/importer/ClassGraphCreator.java | 20 ++- .../core/importer/DomainBuilders.java | 36 +++++ .../core/importer/JavaClassProcessor.java | 25 ++-- .../importer/JavaGenericTypeImporter.java | 74 ++++++++++ .../core/domain/JavaTypeVariableTest.java | 122 ++++++++++++++++ .../ClassFileImporterGenericClassesTest.java | 133 ++++++++++++++++++ .../core/importer/ImportTestUtils.java | 6 + .../tngtech/archunit/testutil/Assertions.java | 6 + .../testutil/assertion/JavaTypeAssertion.java | 40 ++++++ .../assertion/JavaTypeVariableAssertion.java | 16 +++ 16 files changed, 605 insertions(+), 22 deletions(-) create mode 100644 archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java create mode 100644 archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java index 56d5ddaaa7..4f48a03c51 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java @@ -45,6 +45,7 @@ import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodCallBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaStaticInitializerBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeVariableBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.MethodCallTargetBuilder; /** @@ -75,6 +76,10 @@ public static void completeClassHierarchy(JavaClass javaClass, ImportContext imp javaClass.completeClassHierarchyFrom(importContext); } + public static void completeTypeParameters(JavaClass javaClass, ImportContext importContext) { + javaClass.completeTypeParametersFrom(importContext); + } + public static void completeMembers(JavaClass javaClass, ImportContext importContext) { javaClass.completeMembers(importContext); } @@ -143,6 +148,10 @@ public static ThrowsClause createThr return ThrowsClause.from(codeUnit, types); } + public static JavaTypeVariable createTypeVariable(JavaTypeVariableBuilder builder) { + return new JavaTypeVariable(builder); + } + static class AccessContext { final SetMultimap fieldAccessesByTarget = HashMultimap.create(); final SetMultimap methodCallsByTarget = HashMultimap.create(); diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java index 3b5b4fff9d..da1b1906a2 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java @@ -15,6 +15,7 @@ */ package com.tngtech.archunit.core.domain; +import java.util.List; import java.util.Map; import java.util.Set; @@ -27,6 +28,8 @@ public interface ImportContext { Set createInterfaces(JavaClass owner); + List createTypeParameters(JavaClass owner); + Set createFields(JavaClass owner); Set createMethods(JavaClass owner); diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java index 7e4456b20f..7409460257 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java @@ -58,6 +58,7 @@ import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Utils.toAnnotationOfType; import static com.tngtech.archunit.core.domain.properties.HasName.Functions.GET_NAME; import static com.tngtech.archunit.core.domain.properties.HasType.Functions.GET_RAW_TYPE; +import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; @@ -71,6 +72,7 @@ public class JavaClass implements JavaType, HasName.AndFullName, HasAnnotations< private final boolean isAnonymousClass; private final boolean isMemberClass; private final Set modifiers; + private List typeParameters = emptyList(); private final Supplier> reflectSupplier; private Set fields = emptySet(); private Set codeUnits = emptySet(); @@ -566,6 +568,11 @@ public Optional> tryGetAnnotationOfType(String typeNam return Optional.fromNullable(annotations.get(typeName)); } + @PublicAPI(usage = ACCESS) + public List getTypeParameters() { + return typeParameters; + } + @PublicAPI(usage = ACCESS) public Optional getSuperClass() { return superClass; @@ -1179,6 +1186,10 @@ private void completeInterfacesFrom(ImportContext context) { } } + void completeTypeParametersFrom(ImportContext context) { + typeParameters = context.createTypeParameters(this); + } + void completeMembers(final ImportContext context) { fields = context.createFields(this); methods = context.createMethods(this); diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java new file mode 100644 index 0000000000..1e203836f8 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java @@ -0,0 +1,92 @@ +/* + * Copyright 2014-2020 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.core.domain; + +import java.util.List; + +import com.google.common.base.Joiner; +import com.google.common.collect.FluentIterable; +import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeVariableBuilder; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; +import static com.tngtech.archunit.base.Guava.toGuava; +import static com.tngtech.archunit.core.domain.properties.HasName.Functions.GET_NAME; + +/** + * Represents a type variable used by generic types and members.
+ * E.g. {@code class MyClass} would have one {@link JavaTypeVariable} with name "T" + * and unbound, i.e. only bound by {@link Object}.
+ * A type variable can have several bounds, where only one bound may be a class bound + * while all further bounds must be interfaces (compare the JLS).
+ * Example: {@code class MyClass} + * would declare one {@link JavaTypeVariable} {@code T} which is bound by {@code SomeClass}, + * {@code SomeInterfaceOne} and {@code SomeInterfaceTwo}. I.e. any concrete class + * substituted for the type variable must extend {@code SomeClass} and implement + * {@code SomeInterfaceOne} and {@code SomeInterfaceTwo}. + */ +@PublicAPI(usage = ACCESS) +public final class JavaTypeVariable implements JavaType { + private final String name; + private final List bounds; + + JavaTypeVariable(JavaTypeVariableBuilder builder) { + name = builder.getName(); + bounds = builder.getBounds(); + } + + /** + * @return The name of this {@link JavaTypeVariable}, e.g. for {@code class MyClass} + * the name would be "T" + */ + @Override + @PublicAPI(usage = ACCESS) + public String getName() { + return name; + } + + /** + * @return All bounds of this {@link JavaTypeVariable}, i.e. super types any substitution + * of this variable must extend. E.g. for + * {@code class MyClass} the bounds would be + * {@code SomeClass} and {@code SomeInterface} + */ + @PublicAPI(usage = ACCESS) + public List getBounds() { + return bounds; + } + + @Override + public String toString() { + String bounds = printExtendsClause() ? " extends " + joinTypeNames(this.bounds) : ""; + return getClass().getSimpleName() + '{' + getName() + bounds + '}'; + } + + private boolean printExtendsClause() { + if (bounds.isEmpty()) { + return false; + } + if (bounds.size() > 1) { + return true; + } + return !getOnlyElement(bounds).getName().equals(Object.class.getName()); + } + + private String joinTypeNames(List types) { + return FluentIterable.from(types).transform(toGuava(GET_NAME)).join(Joiner.on(" & ")); + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java index dc992cec88..1b854bce61 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java @@ -18,11 +18,14 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; +import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ListMultimap; import com.google.common.collect.SetMultimap; import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.domain.JavaClass; @@ -38,6 +41,7 @@ class ClassFileImportRecord { private final Map superClassNamesByOwner = new HashMap<>(); private final SetMultimap interfaceNamesByOwner = HashMultimap.create(); + private final ListMultimap typeParameterBuildersByOwner = ArrayListMultimap.create(); private final SetMultimap fieldBuildersByOwner = HashMultimap.create(); private final SetMultimap methodBuildersByOwner = HashMultimap.create(); private final SetMultimap constructorBuildersByOwner = HashMultimap.create(); @@ -60,6 +64,10 @@ void addInterfaces(String ownerName, Set interfaceNames) { interfaceNamesByOwner.putAll(ownerName, interfaceNames); } + public void addTypeParameters(String ownerName, List builders) { + typeParameterBuildersByOwner.putAll(ownerName, builders); + } + void addField(String ownerName, DomainBuilders.JavaFieldBuilder fieldBuilder) { fieldBuildersByOwner.put(ownerName, fieldBuilder); } @@ -95,6 +103,10 @@ Set getInterfaceNamesFor(String ownerName) { return interfaceNamesByOwner.get(ownerName); } + List getTypeParameterBuildersFor(String ownerName) { + return typeParameterBuildersByOwner.get(ownerName); + } + Set getFieldBuildersFor(String ownerName) { return fieldBuildersByOwner.get(ownerName); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java index 26f1d9dd35..b12d14ca93 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java @@ -17,6 +17,7 @@ import java.io.InputStream; import java.net.URI; +import java.util.List; import java.util.Set; import com.tngtech.archunit.ArchConfiguration; @@ -86,29 +87,34 @@ public void onNewClass(String className, Optional superClassName, Set typeVariableBuilders) { + importRecord.addTypeParameters(ownerName, typeVariableBuilders); + } + @Override public void onDeclaredField(DomainBuilders.JavaFieldBuilder fieldBuilder) { importRecord.addField(ownerName, fieldBuilder); } @Override - public void onDeclaredConstructor(DomainBuilders.JavaConstructorBuilder builder) { - importRecord.addConstructor(ownerName, builder); + public void onDeclaredConstructor(DomainBuilders.JavaConstructorBuilder constructorBuilder) { + importRecord.addConstructor(ownerName, constructorBuilder); } @Override - public void onDeclaredMethod(DomainBuilders.JavaMethodBuilder builder) { - importRecord.addMethod(ownerName, builder); + public void onDeclaredMethod(DomainBuilders.JavaMethodBuilder methodBuilder) { + importRecord.addMethod(ownerName, methodBuilder); } @Override - public void onDeclaredStaticInitializer(DomainBuilders.JavaStaticInitializerBuilder builder) { - importRecord.setStaticInitializer(ownerName, builder); + public void onDeclaredStaticInitializer(DomainBuilders.JavaStaticInitializerBuilder staticInitializerBuilder) { + importRecord.setStaticInitializer(ownerName, staticInitializerBuilder); } @Override - public void onDeclaredAnnotations(Set annotations) { - importRecord.addAnnotations(ownerName, annotations); + public void onDeclaredAnnotations(Set annotationBuilders) { + importRecord.addAnnotations(ownerName, annotationBuilders); } @Override diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java index d4c13f3ed2..1b80ad0ef2 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java @@ -16,10 +16,12 @@ package com.tngtech.archunit.core.importer; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; import com.google.common.collect.SetMultimap; @@ -44,6 +46,7 @@ import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.core.domain.JavaMethodCall; import com.tngtech.archunit.core.domain.JavaStaticInitializer; +import com.tngtech.archunit.core.domain.JavaTypeVariable; import com.tngtech.archunit.core.domain.ThrowsDeclaration; import com.tngtech.archunit.core.importer.AccessRecord.FieldAccessRecord; import com.tngtech.archunit.core.importer.DomainBuilders.JavaConstructorCallBuilder; @@ -97,7 +100,7 @@ public Set apply(JavaClass input) { JavaClasses complete() { ensureCallTargetsArePresent(); ensureClassHierarchies(); - completeMembers(); + completeTypeParametersAndMembers(); completeAnnotations(); for (RawAccessRecord.ForField fieldAccessRecord : importRecord.getRawFieldAccessRecords()) { tryProcess(fieldAccessRecord, AccessRecord.Factory.forFieldAccessRecord(), processedFieldAccessRecords); @@ -140,8 +143,9 @@ private void resolveInheritance(String currentTypeName, Function createInterfaces(JavaClass owner) { return result.build(); } + @Override + public List createTypeParameters(JavaClass owner) { + ImmutableList.Builder result = ImmutableList.builder(); + for (DomainBuilders.JavaTypeVariableBuilder builder : importRecord.getTypeParameterBuildersFor(owner.getName())) { + result.add(builder.build(classes.byTypeName())); + } + return result.build(); + } + @Override public Set createFields(JavaClass owner) { Set fields = build(importRecord.getFieldBuildersFor(owner.getName()), owner, classes.byTypeName()); @@ -289,7 +302,8 @@ public Optional createStaticInitializer(JavaClass owner) @Override public Map> createAnnotations(JavaClass owner) { - Map> annotations = buildAnnotations(owner, importRecord.getAnnotationsFor(owner.getName()), classes.byTypeName()); + Map> annotations = + buildAnnotations(owner, importRecord.getAnnotationsFor(owner.getName()), classes.byTypeName()); memberDependenciesByTarget.registerAnnotations(annotations.values()); return annotations; } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java index 2d14e5c79b..26c7d0cc08 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java @@ -15,6 +15,7 @@ */ package com.tngtech.archunit.core.importer; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; @@ -52,6 +53,8 @@ import com.tngtech.archunit.core.domain.JavaMethodCall; import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.core.domain.JavaStaticInitializer; +import com.tngtech.archunit.core.domain.JavaType; +import com.tngtech.archunit.core.domain.JavaTypeVariable; import com.tngtech.archunit.core.domain.Source; import com.tngtech.archunit.core.domain.ThrowsClause; import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder.ValueBuilder; @@ -60,6 +63,7 @@ import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createJavaClassList; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createSource; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createThrowsClause; +import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createTypeVariable; import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; @Internal @@ -502,6 +506,38 @@ JavaStaticInitializer construct(JavaStaticInitializerBuilder builder, ClassesByT } } + @Internal + public static final class JavaTypeVariableBuilder { + private final String name; + private final List boundNames = new ArrayList<>(); + private ClassesByTypeName importedClasses; + + JavaTypeVariableBuilder(String name) { + this.name = checkNotNull(name); + } + + public String getName() { + return name; + } + + void addBound(String name) { + boundNames.add(name); + } + + public List getBounds() { + ImmutableList.Builder result = ImmutableList.builder(); + for (String boundName : boundNames) { + result.add(importedClasses.get(boundName)); + } + return result.build(); + } + + public JavaTypeVariable build(ClassesByTypeName importedClasses) { + this.importedClasses = importedClasses; + return createTypeVariable(this); + } + } + @Internal interface BuilderWithBuildParameter { VALUE build(PARAMETER parameter, ClassesByTypeName importedClasses); diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java index 70b344ee23..ea22695a1b 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java @@ -88,13 +88,6 @@ Optional createJavaClass() { return javaClassBuilder != null ? Optional.of(javaClassBuilder.build()) : Optional.absent(); } - @Override - public void visitSource(String source, String debug) { - if (!importAborted() && source != null) { - javaClassBuilder.withSourceFileName(source); - } - } - @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { LOG.debug("Analyzing class '{}'", name); @@ -119,6 +112,7 @@ public void visit(int version, int access, String name, String signature, String className = descriptor.getFullyQualifiedClassName(); declarationHandler.onNewClass(className, superClassName, interfaceNames); + JavaGenericTypeImporter.parseAsmTypeSignature(signature, declarationHandler); } private boolean alreadyImported(JavaClassDescriptor descriptor) { @@ -137,6 +131,13 @@ private boolean importAborted() { return javaClassBuilder == null; } + @Override + public void visitSource(String source, String debug) { + if (!importAborted() && source != null) { + javaClassBuilder.withSourceFileName(source); + } + } + @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { if (importAborted()) { @@ -411,15 +412,17 @@ interface DeclarationHandler { void onNewClass(String className, Optional superClassName, Set interfaceNames); + void onDeclaredTypeParameters(List typeVariableBuilders); + void onDeclaredField(DomainBuilders.JavaFieldBuilder fieldBuilder); - void onDeclaredConstructor(DomainBuilders.JavaConstructorBuilder builder); + void onDeclaredConstructor(DomainBuilders.JavaConstructorBuilder constructorBuilder); - void onDeclaredMethod(DomainBuilders.JavaMethodBuilder builder); + void onDeclaredMethod(DomainBuilders.JavaMethodBuilder methodBuilder); - void onDeclaredStaticInitializer(DomainBuilders.JavaStaticInitializerBuilder builder); + void onDeclaredStaticInitializer(DomainBuilders.JavaStaticInitializerBuilder staticInitializerBuilder); - void onDeclaredAnnotations(Set annotations); + void onDeclaredAnnotations(Set annotationBuilders); void registerEnclosingClass(String ownerName, String enclosingClassName); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java new file mode 100644 index 0000000000..b05e81225a --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java @@ -0,0 +1,74 @@ +/* + * Copyright 2014-2020 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.core.importer; + +import java.util.ArrayList; +import java.util.List; + +import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeVariableBuilder; +import com.tngtech.archunit.core.importer.JavaClassProcessor.DeclarationHandler; +import org.objectweb.asm.signature.SignatureReader; +import org.objectweb.asm.signature.SignatureVisitor; + +import static com.tngtech.archunit.core.importer.ClassFileProcessor.ASM_API_VERSION; + +class JavaGenericTypeImporter { + + static void parseAsmTypeSignature(String signature, DeclarationHandler declarationHandler) { + if (signature == null) { + return; + } + + JavaTypeVariableProcessor typeVariableProcessor = new JavaTypeVariableProcessor(); + new SignatureReader(signature).accept(typeVariableProcessor); + declarationHandler.onDeclaredTypeParameters(typeVariableProcessor.typeVariableBuilders); + } + + private static class JavaTypeVariableProcessor extends SignatureVisitor { + private final List typeVariableBuilders = new ArrayList<>(); + private JavaTypeVariableBuilder currentlyVisiting; + + JavaTypeVariableProcessor() { + super(ASM_API_VERSION); + } + + @Override + public void visitFormalTypeParameter(String name) { + currentlyVisiting = new JavaTypeVariableBuilder(name); + typeVariableBuilders.add(currentlyVisiting); + } + + @Override + public SignatureVisitor visitClassBound() { + return new SignatureVisitor(ASM_API_VERSION) { + @Override + public void visitClassType(String internalObjectName) { + currentlyVisiting.addBound(JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName).getFullyQualifiedClassName()); + } + }; + } + + @Override + public SignatureVisitor visitInterfaceBound() { + return new SignatureVisitor(ASM_API_VERSION) { + @Override + public void visitClassType(String internalObjectName) { + currentlyVisiting.addBound(JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName).getFullyQualifiedClassName()); + } + }; + } + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java new file mode 100644 index 0000000000..008f64cb93 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java @@ -0,0 +1,122 @@ +package com.tngtech.archunit.core.domain; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; + +import com.tngtech.archunit.core.importer.ClassFileImporter; +import org.junit.Test; + +import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; + +public class JavaTypeVariableTest { + + @Test + public void type_variable_name() { + @SuppressWarnings("unused") + class ClassWithUnboundTypeParameter { + } + + JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithUnboundTypeParameter.class).getTypeParameters().get(0); + + assertThat(type.getName()).isEqualTo("SOME_NAME"); + } + + @Test + public void type_variable_upper_bounds() { + @SuppressWarnings("unused") + class ClassWithUnboundTypeParameter & Serializable> { + } + + JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithUnboundTypeParameter.class).getTypeParameters().get(0); + + assertThatTypes(type.getBounds()).matchExactly(HashMap.class, Serializable.class); + assertThatTypes(type.getUpperBounds()).matchExactly(HashMap.class, Serializable.class); + } + + @Test + public void erased_unbound_type_variable_is_java_lang_Object() { + @SuppressWarnings("unused") + class ClassWithUnboundTypeParameter { + } + + JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithUnboundTypeParameter.class).getTypeParameters().get(0); + + assertThatType(type.toErasure()).matches(Object.class); + } + + @Test + public void erased_type_variable_bound_by_single_class_is_this_class() { + @SuppressWarnings("unused") + class ClassWithBoundTypeParameterWithSingleClassBound { + } + + JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithSingleClassBound.class).getTypeParameters().get(0); + + assertThatType(type.toErasure()).matches(Serializable.class); + } + + @Test + public void erased_type_variable_bound_by_single_generic_class_is_the_erasure_of_this_class() { + @SuppressWarnings("unused") + class ClassWithBoundTypeParameterWithSingleGenericClassBound> { + } + + JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithSingleGenericClassBound.class).getTypeParameters().get(0); + + assertThatType(type.toErasure()).matches(List.class); + } + + @Test + public void erased_type_variable_bound_by_multiple_generic_classes_and_interfaces_is_the_erasure_of_the_leftmost_bound() { + @SuppressWarnings("unused") + class ClassWithBoundTypeParameterWithMultipleGenericClassAndInterfaceBounds & Iterable & Serializable> { + } + + JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithMultipleGenericClassAndInterfaceBounds.class).getTypeParameters().get(0); + + assertThatType(type.toErasure()).matches(HashMap.class); + } + + @Test + public void toString_unbounded() { + @SuppressWarnings("unused") + class Unbounded { + } + + JavaTypeVariable typeVariable = new ClassFileImporter().importClass(Unbounded.class).getTypeParameters().get(0); + + assertThat(typeVariable.toString()) + .contains(JavaTypeVariable.class.getSimpleName()) + .contains("NAME") + .doesNotContain("extends"); + } + + @Test + public void toString_upper_bounded_by_single_bound() { + @SuppressWarnings("unused") + class BoundedBySingleBound { + } + + JavaTypeVariable typeVariable = new ClassFileImporter().importClass(BoundedBySingleBound.class).getTypeParameters().get(0); + + assertThat(typeVariable.toString()) + .contains(JavaTypeVariable.class.getSimpleName()) + .contains("NAME extends java.lang.String"); + } + + @Test + public void toString_upper_bounded_by_multiple_bounds() { + @SuppressWarnings("unused") + class BoundedByMultipleBounds { + } + + JavaTypeVariable typeVariable = new ClassFileImporter().importClass(BoundedByMultipleBounds.class).getTypeParameters().get(0); + + assertThat(typeVariable.toString()) + .contains(JavaTypeVariable.class.getSimpleName()) + .contains("NAME extends java.lang.String & java.io.Serializable"); + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java new file mode 100644 index 0000000000..87b1d87ae3 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java @@ -0,0 +1,133 @@ +package com.tngtech.archunit.core.importer; + +import java.io.Closeable; +import java.io.File; +import java.io.Serializable; + +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.domain.JavaTypeVariable; +import com.tngtech.archunit.testutil.ArchConfigurationRule; +import org.junit.Rule; +import org.junit.Test; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; + +public class ClassFileImporterGenericClassesTest { + + @Rule + public final ArchConfigurationRule configurationRule = new ArchConfigurationRule().resolveAdditionalDependenciesFromClassPath(false); + + @Test + public void imports_empty_list_of_type_parameters_for_non_generic_class() { + JavaClass javaClass = new ClassFileImporter().importClass(getClass()); + + assertThat(javaClass.getTypeParameters()).as("type parameters of non generic class").isEmpty(); + } + + @Test + public void imports_single_generic_type_parameter_of_class() { + @SuppressWarnings("unused") + class ClassWithSingleTypeParameterWithoutBound { + } + + JavaClass javaClass = new ClassFileImporter().importClasses(ClassWithSingleTypeParameterWithoutBound.class, Object.class) + .get(ClassWithSingleTypeParameterWithoutBound.class); + + JavaTypeVariable typeVariable = getOnlyElement(javaClass.getTypeParameters()); + + assertThat(typeVariable.getName()).as("type variable name").isEqualTo("T"); + assertThatTypes(typeVariable.getBounds()).as("type variable bounds").matchExactly(Object.class); + } + + @Test + public void imports_multiple_generic_type_parameters_of_class() { + @SuppressWarnings("unused") + class ClassWithThreeTypeParametersWithoutBounds { + } + + JavaClass javaClass = new ClassFileImporter().importClass(ClassWithThreeTypeParametersWithoutBounds.class); + + assertThatType(javaClass).hasTypeParameters("A", "B", "C"); + } + + @Test + public void imports_simple_class_bound_of_type_variable() { + @SuppressWarnings("unused") + class ClassWithSingleTypeParameterWithSimpleClassBound { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithSingleTypeParameterWithSimpleClassBound.class, String.class); + + JavaClass javaClass = classes.get(ClassWithSingleTypeParameterWithSimpleClassBound.class); + + assertThatType(javaClass).hasOnlyTypeParameter("T").withBoundsMatching(String.class); + } + + @Test + public void imports_single_simple_class_bounds_of_multiple_type_variables() { + @SuppressWarnings("unused") + class ClassWithThreeTypeParametersWithSimpleClassBounds { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithThreeTypeParametersWithSimpleClassBounds.class, + String.class, System.class, File.class); + + JavaClass javaClass = classes.get(ClassWithThreeTypeParametersWithSimpleClassBounds.class); + + assertThatType(javaClass) + .hasTypeParameters("A", "B", "C") + .hasTypeParameter("A").withBoundsMatching(String.class) + .hasTypeParameter("B").withBoundsMatching(System.class) + .hasTypeParameter("C").withBoundsMatching(File.class); + } + + @Test + public void imports_simple_interface_bound_of_single_type_variable() { + @SuppressWarnings("unused") + class ClassWithSingleTypeParameterWithSimpleInterfaceBound { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithSingleTypeParameterWithSimpleInterfaceBound.class, Serializable.class); + + JavaClass javaClass = classes.get(ClassWithSingleTypeParameterWithSimpleInterfaceBound.class); + + assertThatType(javaClass).hasOnlyTypeParameter("T").withBoundsMatching(Serializable.class); + } + + @Test + public void imports_multiple_simple_bounds_of_single_type_variable() { + @SuppressWarnings("unused") + class ClassWithSingleTypeParameterWithMultipleSimpleClassAndInterfaceBounds { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithSingleTypeParameterWithMultipleSimpleClassAndInterfaceBounds.class, + String.class, Serializable.class, Runnable.class); + + JavaClass javaClass = classes.get(ClassWithSingleTypeParameterWithMultipleSimpleClassAndInterfaceBounds.class); + + assertThatType(javaClass).hasOnlyTypeParameter("T").withBoundsMatching(String.class, Serializable.class, Runnable.class); + } + + @Test + public void imports_multiple_simple_bounds_of_multiple_type_variables() { + @SuppressWarnings("unused") + class ClassWithThreeTypeParametersWithMultipleSimpleClassAndInterfaceBounds< + A extends String & Serializable, B extends System & Runnable, C extends File & Serializable & Closeable> { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithThreeTypeParametersWithMultipleSimpleClassAndInterfaceBounds.class, + String.class, Serializable.class, System.class, Runnable.class, File.class, Serializable.class, Closeable.class); + + JavaClass javaClass = classes.get(ClassWithThreeTypeParametersWithMultipleSimpleClassAndInterfaceBounds.class); + + assertThatType(javaClass) + .hasTypeParameters("A", "B", "C") + .hasTypeParameter("A").withBoundsMatching(String.class, Serializable.class) + .hasTypeParameter("B").withBoundsMatching(System.class, Runnable.class) + .hasTypeParameter("C").withBoundsMatching(File.class, Serializable.class, Closeable.class); + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java index 9bc3b45e3f..312875deff 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java @@ -37,6 +37,7 @@ import com.tngtech.archunit.core.domain.JavaMethodCall; import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.core.domain.JavaStaticInitializer; +import com.tngtech.archunit.core.domain.JavaTypeVariable; import com.tngtech.archunit.core.domain.ThrowsDeclaration; import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodCallBuilder; import org.objectweb.asm.Type; @@ -355,6 +356,11 @@ public Set createInterfaces(JavaClass owner) { return Collections.emptySet(); } + @Override + public List createTypeParameters(JavaClass owner) { + return Collections.emptyList(); + } + @Override public Set createFields(JavaClass owner) { return Collections.emptySet(); diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java b/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java index 9b4d8595d7..874922491e 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java @@ -29,6 +29,7 @@ import com.tngtech.archunit.core.domain.JavaMethodCall; import com.tngtech.archunit.core.domain.JavaPackage; import com.tngtech.archunit.core.domain.JavaType; +import com.tngtech.archunit.core.domain.JavaTypeVariable; import com.tngtech.archunit.core.domain.ThrowsClause; import com.tngtech.archunit.core.domain.ThrowsDeclaration; import com.tngtech.archunit.lang.ArchCondition; @@ -51,6 +52,7 @@ import com.tngtech.archunit.testutil.assertion.JavaMethodsAssertion; import com.tngtech.archunit.testutil.assertion.JavaPackagesAssertion; import com.tngtech.archunit.testutil.assertion.JavaTypeAssertion; +import com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion; import com.tngtech.archunit.testutil.assertion.JavaTypesAssertion; import org.assertj.core.api.AbstractIterableAssert; import org.assertj.core.api.AbstractListAssert; @@ -90,6 +92,10 @@ public static JavaTypeAssertion assertThatType(JavaType javaType) { return new JavaTypeAssertion(javaType); } + public static JavaTypeVariableAssertion assertThatTypeVariable(JavaTypeVariable typeVariable) { + return new JavaTypeVariableAssertion(typeVariable); + } + public static JavaTypesAssertion assertThatTypes(Iterable javaTypes) { return new JavaTypesAssertion(javaTypes); } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java index 81c3ed072a..de7c6787b4 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java @@ -1,18 +1,27 @@ package com.tngtech.archunit.testutil.assertion; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.google.common.base.Optional; import com.google.common.base.Strings; +import com.google.common.collect.FluentIterable; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.core.domain.JavaType; +import com.tngtech.archunit.core.domain.JavaTypeVariable; import org.assertj.core.api.AbstractObjectAssert; import org.objectweb.asm.Type; +import static com.tngtech.archunit.base.Guava.toGuava; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypeVariable; +import static com.tngtech.archunit.testutil.TestUtils.namesOf; import static com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion.propertiesOf; import static com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion.runtimePropertiesOf; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.guava.api.Assertions.assertThat; public class JavaTypeAssertion extends AbstractObjectAssert { private static final Pattern ARRAY_PATTERN = Pattern.compile("(\\[+)(.*)"); @@ -50,6 +59,26 @@ public void matches(Class clazz) { } } + public JavaTypeAssertion hasTypeParameters(String... names) { + assertThat(namesOf(actualClass().getTypeParameters())).as("names of type parameters").containsExactly(names); + return this; + } + + @SuppressWarnings("OptionalGetWithoutIsPresent") // checked via AssertJ + public JavaTypeVariableOfClassAssertion hasTypeParameter(String name) { + List typeVariables = actualClass().getTypeParameters(); + + Optional variable = FluentIterable.from(typeVariables).firstMatch(toGuava(name(name))); + assertThat(variable).as("Type variable with name '%s'", name).isPresent(); + + return new JavaTypeVariableOfClassAssertion(variable.get()); + } + + public JavaTypeVariableOfClassAssertion hasOnlyTypeParameter(String name) { + assertThat(actualClass().getTypeParameters()).as("Type parameters").hasSize(1); + return hasTypeParameter(name); + } + private JavaClass actualClass() { assertThat(actual).isInstanceOf(JavaClass.class); return (JavaClass) actual; @@ -64,4 +93,15 @@ private String ensureArrayName(String name) { } return name + suffix; } + + public class JavaTypeVariableOfClassAssertion extends AbstractObjectAssert { + private JavaTypeVariableOfClassAssertion(JavaTypeVariable actual) { + super(actual, JavaTypeVariableOfClassAssertion.class); + } + + public JavaTypeAssertion withBoundsMatching(Class... bounds) { + assertThatTypeVariable(actual).hasBoundsMatching(bounds); + return JavaTypeAssertion.this; + } + } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java new file mode 100644 index 0000000000..e8242a2364 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java @@ -0,0 +1,16 @@ +package com.tngtech.archunit.testutil.assertion; + +import com.tngtech.archunit.core.domain.JavaTypeVariable; +import org.assertj.core.api.AbstractObjectAssert; + +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; + +public class JavaTypeVariableAssertion extends AbstractObjectAssert { + public JavaTypeVariableAssertion(JavaTypeVariable actual) { + super(actual, JavaTypeVariableAssertion.class); + } + + public void hasBoundsMatching(Class... bounds) { + assertThatTypes(actual.getBounds()).as("Type variable bounds").matchExactly(bounds); + } +} From afaa0093338ca0681a07392d91b10f69b2e4a62f Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Fri, 6 Mar 2020 17:34:30 +0100 Subject: [PATCH 033/115] add support for first level of generic type parameter bounds We now support importing the first level of generic bounds of type parameters where type parameters are assigned to concrete classes. E.g. `SomeClass & Bar>`. For now we still do not support this in a generic way, i.e. `SomeClass>` is not supported correctly (bounds nested more than one level will be ignored, in this case `Integer`). Signed-off-by: Peter Gafert --- .../archunit/core/domain/AccessTarget.java | 6 ++ .../archunit/core/domain/JavaAnnotation.java | 5 ++ .../archunit/core/domain/JavaClass.java | 6 ++ .../archunit/core/domain/JavaField.java | 10 +++ .../core/domain/JavaParameterizedType.java | 38 +++++++++ .../archunit/core/domain/JavaType.java | 16 ++++ .../core/domain/JavaTypeVariable.java | 8 ++ .../core/domain/ThrowsDeclaration.java | 6 ++ .../core/domain/properties/HasType.java | 6 +- .../core/importer/DomainBuilders.java | 71 ++++++++++++++-- .../importer/JavaGenericTypeImporter.java | 27 ++++-- .../archunit/core/domain/JavaClassTest.java | 10 +++ .../core/domain/JavaTypeVariableTest.java | 1 - .../core/domain/properties/HasTypeTest.java | 6 ++ .../ClassFileImporterGenericClassesTest.java | 83 +++++++++++++++++-- .../testutil/assertion/JavaTypeAssertion.java | 50 +++++++---- .../assertion/JavaTypeVariableAssertion.java | 83 ++++++++++++++++++- 17 files changed, 392 insertions(+), 40 deletions(-) create mode 100644 archunit/src/main/java/com/tngtech/archunit/core/domain/JavaParameterizedType.java diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/AccessTarget.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/AccessTarget.java index 9206752cf4..a0cf420e3a 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/AccessTarget.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/AccessTarget.java @@ -251,6 +251,12 @@ public static final class FieldAccessTarget extends AccessTarget implements HasT this.field = Suppliers.memoize(builder.getField()); } + @Override + @PublicAPI(usage = ACCESS) + public JavaType getType() { + return type; + } + @Override @PublicAPI(usage = ACCESS) public JavaClass getRawType() { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAnnotation.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAnnotation.java index 25ad9806a7..724997bf3e 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAnnotation.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAnnotation.java @@ -119,6 +119,11 @@ private String startWithLowerCase(HasDescription annotatedElement) { return annotatedElement.getDescription().substring(0, 1).toLowerCase() + annotatedElement.getDescription().substring(1); } + @Override + public JavaType getType() { + return type; + } + @Override @PublicAPI(usage = ACCESS) public JavaClass getRawType() { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java index 7409460257..1f917105e5 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java @@ -573,6 +573,12 @@ public List getTypeParameters() { return typeParameters; } + @Override + @PublicAPI(usage = ACCESS) + public JavaClass toErasure() { + return this; + } + @PublicAPI(usage = ACCESS) public Optional getSuperClass() { return superClass; diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaField.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaField.java index 2b7b2dc54b..634ea73e49 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaField.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaField.java @@ -52,6 +52,16 @@ public String getFullName() { return getOwner().getName() + "." + getName(); } + /** + * Note: This is still work in progress and thus does not support generic types at the moment. + * In the future the result can possibly also be a {@link JavaParameterizedType} or {@link JavaTypeVariable} + */ + @Override + @PublicAPI(usage = ACCESS) + public JavaType getType() { + return type; + } + @Override @PublicAPI(usage = ACCESS) public JavaClass getRawType() { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaParameterizedType.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaParameterizedType.java new file mode 100644 index 0000000000..7aa12185a8 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaParameterizedType.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014-2020 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.core.domain; + +import java.util.List; + +import com.tngtech.archunit.PublicAPI; + +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + +/** + * A {@link JavaParameterizedType} represents a concrete parameterization of a generic type. + * Consider the generic type {@code List}, then {@code List} would be a parameterized type + * where the concrete type {@code java.lang.String} has been assigned to the type variable {@code T}. + * The concrete type {@code java.lang.String} is then an "actual type argument" of this {@link JavaParameterizedType} + * (see {@link JavaParameterizedType#getActualTypeArguments()}). + */ +@PublicAPI(usage = ACCESS) +public interface JavaParameterizedType extends JavaType { + /** + * @return The actual type arguments of this parameterized type (compare {@link JavaParameterizedType}). + */ + @PublicAPI(usage = ACCESS) + List getActualTypeArguments(); +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java index 0fbd514a13..2dec6444f6 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java @@ -15,7 +15,23 @@ */ package com.tngtech.archunit.core.domain; +import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.core.domain.properties.HasName; +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + +@PublicAPI(usage = ACCESS) public interface JavaType extends HasName { + /** + * Converts this {@link JavaType} into the erased type + * (compare the Java Language Specification). + * In particular this will result in + *
    + *
  • the class itself, if this type is a {@link JavaClass}
  • + *
  • the {@link JavaClass} equivalent to {@link Object}, if this type is an unbound {@link JavaTypeVariable}
  • + *
  • the {@link JavaClass} equivalent to the erasure of the left most bound, if this type is a bound {@link JavaTypeVariable}
  • + *
+ */ + @PublicAPI(usage = ACCESS) + JavaClass toErasure(); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java index 1e203836f8..31b2ec18f1 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java @@ -43,10 +43,12 @@ public final class JavaTypeVariable implements JavaType { private final String name; private final List bounds; + private final JavaClass erasure; JavaTypeVariable(JavaTypeVariableBuilder builder) { name = builder.getName(); bounds = builder.getBounds(); + erasure = bounds.size() > 0 ? bounds.get(0).toErasure() : builder.getUnboundErasureType(); } /** @@ -70,6 +72,12 @@ public List getBounds() { return bounds; } + @Override + @PublicAPI(usage = ACCESS) + public JavaClass toErasure() { + return erasure; + } + @Override public String toString() { String bounds = printExtendsClause() ? " extends " + joinTypeNames(this.bounds) : ""; diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/ThrowsDeclaration.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/ThrowsDeclaration.java index cefcfe42c1..3895845675 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/ThrowsDeclaration.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/ThrowsDeclaration.java @@ -93,6 +93,12 @@ public JavaClass getDeclaringClass() { return getLocation().getOwner(); } + @Override + @PublicAPI(usage = ACCESS) + public JavaType getType() { + return type; + } + /** * @return The type of this {@link ThrowsDeclaration}, e.g. for a method *
void method() throws SomeException {...}
diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasType.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasType.java index d835f9ba98..66f7d82dae 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasType.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasType.java @@ -19,6 +19,7 @@ import com.tngtech.archunit.base.ChainableFunction; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaType; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; import static com.tngtech.archunit.base.DescribedPredicate.equalTo; @@ -27,6 +28,9 @@ public interface HasType { + @PublicAPI(usage = ACCESS) + JavaType getType(); + @PublicAPI(usage = ACCESS) JavaClass getRawType(); @@ -48,7 +52,6 @@ public static DescribedPredicate rawType(String typeName) { public static DescribedPredicate rawType(DescribedPredicate predicate) { return GET_RAW_TYPE.is(predicate).as("raw type " + predicate.getDescription()); } - } final class Functions { @@ -62,6 +65,5 @@ public JavaClass apply(HasType input) { return input.getRawType(); } }; - } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java index 26c7d0cc08..ca3330c8ad 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Set; +import com.google.common.base.Joiner; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; @@ -52,6 +53,7 @@ import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.core.domain.JavaMethodCall; import com.tngtech.archunit.core.domain.JavaModifier; +import com.tngtech.archunit.core.domain.JavaParameterizedType; import com.tngtech.archunit.core.domain.JavaStaticInitializer; import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.JavaTypeVariable; @@ -509,7 +511,7 @@ JavaStaticInitializer construct(JavaStaticInitializerBuilder builder, ClassesByT @Internal public static final class JavaTypeVariableBuilder { private final String name; - private final List boundNames = new ArrayList<>(); + private final List bounds = new ArrayList<>(); private ClassesByTypeName importedClasses; JavaTypeVariableBuilder(String name) { @@ -520,22 +522,47 @@ public String getName() { return name; } - void addBound(String name) { - boundNames.add(name); + void addBound(JavaParameterizedTypeBuilder builder) { + bounds.add(builder); } public List getBounds() { ImmutableList.Builder result = ImmutableList.builder(); - for (String boundName : boundNames) { - result.add(importedClasses.get(boundName)); + for (JavaParameterizedTypeBuilder bound : bounds) { + result.add(bound.build(importedClasses)); } return result.build(); } + public JavaClass getUnboundErasureType() { + return importedClasses.get(Object.class.getName()); + } + public JavaTypeVariable build(ClassesByTypeName importedClasses) { this.importedClasses = importedClasses; return createTypeVariable(this); } + + static class JavaParameterizedTypeBuilder { + private final JavaClassDescriptor type; + private final List typeArgumentBuilders = new ArrayList<>(); + + JavaParameterizedTypeBuilder(JavaClassDescriptor type) { + this.type = type; + } + + void addTypeArgument(JavaClassDescriptor type) { + typeArgumentBuilders.add(new JavaParameterizedTypeBuilder(type)); + } + + public JavaParameterizedType build(ClassesByTypeName classes) { + ImmutableList.Builder typeArguments = ImmutableList.builder(); + for (JavaParameterizedTypeBuilder typeArgumentBuilder : typeArgumentBuilders) { + typeArguments.add(typeArgumentBuilder.build(classes)); + } + return new ImportedParameterizedType(classes.get(type.getFullyQualifiedClassName()), typeArguments.build()); + } + } } @Internal @@ -783,4 +810,38 @@ MethodCallTarget build() { return DomainObjectCreationContext.createMethodCallTarget(this); } } + + private static class ImportedParameterizedType implements JavaParameterizedType { + private final JavaType type; + private final List typeArguments; + + ImportedParameterizedType(JavaType type, List typeArguments) { + this.type = type; + this.typeArguments = typeArguments; + } + + @Override + public String getName() { + return type.getName(); + } + + @Override + public JavaClass toErasure() { + return type.toErasure(); + } + + @Override + public List getActualTypeArguments() { + return typeArguments; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + type.getName() + formatTypeArguments() + '}'; + } + + private String formatTypeArguments() { + return typeArguments.isEmpty() ? "" : "<" + Joiner.on(", ").join(typeArguments) + ">"; + } + } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java index b05e81225a..84b2e4f828 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java @@ -19,6 +19,7 @@ import java.util.List; import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeVariableBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeVariableBuilder.JavaParameterizedTypeBuilder; import com.tngtech.archunit.core.importer.JavaClassProcessor.DeclarationHandler; import org.objectweb.asm.signature.SignatureReader; import org.objectweb.asm.signature.SignatureVisitor; @@ -53,20 +54,32 @@ public void visitFormalTypeParameter(String name) { @Override public SignatureVisitor visitClassBound() { - return new SignatureVisitor(ASM_API_VERSION) { - @Override - public void visitClassType(String internalObjectName) { - currentlyVisiting.addBound(JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName).getFullyQualifiedClassName()); - } - }; + return visitBound(); } @Override public SignatureVisitor visitInterfaceBound() { + return visitBound(); + } + + private SignatureVisitor visitBound() { return new SignatureVisitor(ASM_API_VERSION) { + private JavaParameterizedTypeBuilder bound; + @Override public void visitClassType(String internalObjectName) { - currentlyVisiting.addBound(JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName).getFullyQualifiedClassName()); + bound = new JavaParameterizedTypeBuilder(JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName)); + currentlyVisiting.addBound(bound); + } + + @Override + public SignatureVisitor visitTypeArgument(char wildcard) { + return new SignatureVisitor(ASM_API_VERSION) { + @Override + public void visitClassType(String internalObjectName) { + bound.addTypeArgument(JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName)); + } + }; } }; } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java index fedd522efc..36ce3213cb 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java @@ -115,6 +115,16 @@ public void finds_multidimensional_array_type() { assertThatType(twoDimArray.tryGetComponentType().get().tryGetComponentType().get()).isEqualTo(type); } + @Test + public void erased_type_of_class_is_the_class_itself() { + class SimpleClass { + } + + JavaType type = new ClassFileImporter().importClass(SimpleClass.class); + + assertThat(type.toErasure()).isEqualTo(type); + } + @Test public void finds_component_type_chain_of_otherwise_unreferenced_component_type() { class OnlyReferencingMultiDimArray { diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java index 008f64cb93..7a7c828248 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java @@ -33,7 +33,6 @@ class ClassWithUnboundTypeParameter & Serializ JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithUnboundTypeParameter.class).getTypeParameters().get(0); assertThatTypes(type.getBounds()).matchExactly(HashMap.class, Serializable.class); - assertThatTypes(type.getUpperBounds()).matchExactly(HashMap.class, Serializable.class); } @Test diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasTypeTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasTypeTest.java index 44cf4301a7..76501337b1 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasTypeTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/properties/HasTypeTest.java @@ -2,6 +2,7 @@ import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; @@ -52,6 +53,11 @@ public void function_getType() { private HasType newHasType(final Class owner) { return new HasType() { + @Override + public JavaType getType() { + return getRawType(); + } + @Override public JavaClass getRawType() { return importClassWithContext(owner); diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java index 87b1d87ae3..7e4e5ac1f2 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java @@ -3,18 +3,18 @@ import java.io.Closeable; import java.io.File; import java.io.Serializable; +import java.util.Map; +import com.tngtech.archunit.base.Function; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; -import com.tngtech.archunit.core.domain.JavaTypeVariable; import com.tngtech.archunit.testutil.ArchConfigurationRule; import org.junit.Rule; import org.junit.Test; -import static com.google.common.collect.Iterables.getOnlyElement; import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.archunit.testutil.Assertions.assertThatType; -import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; +import static com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.typeVariable; public class ClassFileImporterGenericClassesTest { @@ -34,13 +34,11 @@ public void imports_single_generic_type_parameter_of_class() { class ClassWithSingleTypeParameterWithoutBound { } - JavaClass javaClass = new ClassFileImporter().importClasses(ClassWithSingleTypeParameterWithoutBound.class, Object.class) - .get(ClassWithSingleTypeParameterWithoutBound.class); + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithSingleTypeParameterWithoutBound.class, Object.class); - JavaTypeVariable typeVariable = getOnlyElement(javaClass.getTypeParameters()); + JavaClass javaClass = classes.get(ClassWithSingleTypeParameterWithoutBound.class); - assertThat(typeVariable.getName()).as("type variable name").isEqualTo("T"); - assertThatTypes(typeVariable.getBounds()).as("type variable bounds").matchExactly(Object.class); + assertThatType(javaClass).hasOnlyTypeParameter("T").withBoundsMatching(Object.class); } @Test @@ -130,4 +128,73 @@ class ClassWithThreeTypeParametersWithMultipleSimpleClassAndInterfaceBounds< .hasTypeParameter("B").withBoundsMatching(System.class, Runnable.class) .hasTypeParameter("C").withBoundsMatching(File.class, Serializable.class, Closeable.class); } + + @Test + public void imports_single_class_bound_with_single_type_parameter_assigned_to_concrete_class() { + @SuppressWarnings("unused") + class ClassWithSingleTypeParameterWithGenericClassBoundAssignedToConcreteClass> { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithSingleTypeParameterWithGenericClassBoundAssignedToConcreteClass.class, + ClassParameterWithSingleTypeParameter.class, String.class); + + JavaClass javaClass = classes.get(ClassWithSingleTypeParameterWithGenericClassBoundAssignedToConcreteClass.class); + + assertThatType(javaClass).hasOnlyTypeParameter("T") + .withBoundsMatching(typeVariable(ClassParameterWithSingleTypeParameter.class).withTypeArguments(String.class)); + } + + @Test + public void imports_multiple_class_bounds_with_single_type_parameters_assigned_to_concrete_types() { + @SuppressWarnings("unused") + class ClassWithMultipleTypeParametersWithGenericClassOrInterfaceBoundsAssignedToConcreteTypes< + A extends ClassParameterWithSingleTypeParameter, + B extends InterfaceParameterWithSingleTypeParameter, + C extends InterfaceParameterWithSingleTypeParameter> { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithMultipleTypeParametersWithGenericClassOrInterfaceBoundsAssignedToConcreteTypes.class, + ClassParameterWithSingleTypeParameter.class, File.class, InterfaceParameterWithSingleTypeParameter.class, Serializable.class, String.class); + + JavaClass javaClass = classes.get(ClassWithMultipleTypeParametersWithGenericClassOrInterfaceBoundsAssignedToConcreteTypes.class); + + assertThatType(javaClass).hasTypeParameters("A", "B", "C") + .hasTypeParameter("A").withBoundsMatching(typeVariable(ClassParameterWithSingleTypeParameter.class).withTypeArguments(File.class)) + .hasTypeParameter("B").withBoundsMatching(typeVariable(InterfaceParameterWithSingleTypeParameter.class).withTypeArguments(Serializable.class)) + .hasTypeParameter("C").withBoundsMatching(typeVariable(InterfaceParameterWithSingleTypeParameter.class).withTypeArguments(String.class)); + } + + @Test + public void imports_multiple_class_bounds_with_multiple_type_parameters_assigned_to_concrete_types() { + @SuppressWarnings("unused") + class ClassWithTwoTypeParametersWithMultipleGenericClassAndInterfaceBoundsAssignedToConcreteTypes< + A extends ClassParameterWithSingleTypeParameter & InterfaceParameterWithSingleTypeParameter, + B extends Map & Iterable & Function> { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithTwoTypeParametersWithMultipleGenericClassAndInterfaceBoundsAssignedToConcreteTypes.class, + ClassParameterWithSingleTypeParameter.class, InterfaceParameterWithSingleTypeParameter.class, + Map.class, Iterable.class, Function.class, String.class, Serializable.class, File.class, Integer.class, Long.class); + + JavaClass javaClass = classes.get(ClassWithTwoTypeParametersWithMultipleGenericClassAndInterfaceBoundsAssignedToConcreteTypes.class); + + assertThatType(javaClass).hasTypeParameters("A", "B") + .hasTypeParameter("A") + .withBoundsMatching( + typeVariable(ClassParameterWithSingleTypeParameter.class).withTypeArguments(String.class), + typeVariable(InterfaceParameterWithSingleTypeParameter.class).withTypeArguments(Serializable.class)) + .hasTypeParameter("B") + .withBoundsMatching( + typeVariable(Map.class).withTypeArguments(String.class, Serializable.class), + typeVariable(Iterable.class).withTypeArguments(File.class), + typeVariable(Function.class).withTypeArguments(Integer.class, Long.class)); + } + + @SuppressWarnings("unused") + public static class ClassParameterWithSingleTypeParameter { + } + + @SuppressWarnings("unused") + public interface InterfaceParameterWithSingleTypeParameter { + } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java index de7c6787b4..b4dd72810d 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java @@ -11,9 +11,12 @@ import com.tngtech.archunit.core.domain.JavaModifier; import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.JavaTypeVariable; +import com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.ExpectedConcreteType; import org.assertj.core.api.AbstractObjectAssert; import org.objectweb.asm.Type; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Strings.isNullOrEmpty; import static com.tngtech.archunit.base.Guava.toGuava; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; import static com.tngtech.archunit.testutil.Assertions.assertThatTypeVariable; @@ -30,35 +33,41 @@ public JavaTypeAssertion(JavaType javaType) { super(javaType, JavaTypeAssertion.class); } - public static String getExpectedPackageName(Class clazz) { - if (!clazz.isArray()) { - return clazz.getPackage() != null ? clazz.getPackage().getName() : ""; - } - return getExpectedPackageName(clazz.getComponentType()); + public void matches(java.lang.reflect.Type type) { + checkArgument(type instanceof Class, "Only %s implemented so far, please extend", Class.class.getName()); + matches((Class) type); } public void matches(Class clazz) { JavaClass javaClass = actualClass(); - assertThat(javaClass.getName()).as("Name of " + javaClass) + assertThat(javaClass.getName()).as(describeAssertion("Name of " + javaClass)) .isEqualTo(clazz.getName()); - assertThat(javaClass.getSimpleName()).as("Simple name of " + javaClass) + assertThat(javaClass.getSimpleName()).as(describeAssertion("Simple name of " + javaClass)) .isEqualTo(ensureArrayName(clazz.getSimpleName())); - assertThat(javaClass.getPackage().getName()).as("Package of " + javaClass) + assertThat(javaClass.getPackage().getName()).as(describeAssertion("Package of " + javaClass)) .isEqualTo(getExpectedPackageName(clazz)); - assertThat(javaClass.getPackageName()).as("Package name of " + javaClass) + assertThat(javaClass.getPackageName()).as(describeAssertion("Package name of " + javaClass)) .isEqualTo(getExpectedPackageName(clazz)); - assertThat(javaClass.getModifiers()).as("Modifiers of " + javaClass) + assertThat(javaClass.getModifiers()).as(describeAssertion("Modifiers of " + javaClass)) .isEqualTo(JavaModifier.getModifiersForClass(clazz.getModifiers())); - assertThat(javaClass.isArray()).as(javaClass + " is array").isEqualTo(clazz.isArray()); - assertThat(runtimePropertiesOf(javaClass.getAnnotations())).as("Annotations of " + javaClass) + assertThat(javaClass.isArray()).as(describeAssertion(javaClass + " is array")).isEqualTo(clazz.isArray()); + assertThat(runtimePropertiesOf(javaClass.getAnnotations())).as(describeAssertion("Annotations of " + javaClass)) .isEqualTo(propertiesOf(clazz.getAnnotations())); if (clazz.isArray()) { - new JavaTypeAssertion(javaClass.getComponentType()).matches(clazz.getComponentType()); + new JavaTypeAssertion(javaClass.getComponentType()) + .as(describeAssertion(String.format("Component type of %s: ", javaClass.getSimpleName()))) + .matches(clazz.getComponentType()); } } + private String describeAssertion(String partialAssertionDescription) { + return isNullOrEmpty(descriptionText()) + ? partialAssertionDescription + : descriptionText() + ": " + partialAssertionDescription; + } + public JavaTypeAssertion hasTypeParameters(String... names) { assertThat(namesOf(actualClass().getTypeParameters())).as("names of type parameters").containsExactly(names); return this; @@ -80,8 +89,7 @@ public JavaTypeVariableOfClassAssertion hasOnlyTypeParameter(String name) { } private JavaClass actualClass() { - assertThat(actual).isInstanceOf(JavaClass.class); - return (JavaClass) actual; + return actual instanceof JavaClass ? (JavaClass) actual : actual.toErasure(); } private String ensureArrayName(String name) { @@ -94,6 +102,13 @@ private String ensureArrayName(String name) { return name + suffix; } + public static String getExpectedPackageName(Class clazz) { + if (!clazz.isArray()) { + return clazz.getPackage() != null ? clazz.getPackage().getName() : ""; + } + return getExpectedPackageName(clazz.getComponentType()); + } + public class JavaTypeVariableOfClassAssertion extends AbstractObjectAssert { private JavaTypeVariableOfClassAssertion(JavaTypeVariable actual) { super(actual, JavaTypeVariableOfClassAssertion.class); @@ -103,5 +118,10 @@ public JavaTypeAssertion withBoundsMatching(Class... bounds) { assertThatTypeVariable(actual).hasBoundsMatching(bounds); return JavaTypeAssertion.this; } + + public JavaTypeAssertion withBoundsMatching(ExpectedConcreteType... bounds) { + assertThatTypeVariable(actual).hasBoundsMatching(bounds); + return JavaTypeAssertion.this; + } } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java index e8242a2364..4cf55686d8 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java @@ -1,9 +1,20 @@ package com.tngtech.archunit.testutil.assertion; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.tngtech.archunit.core.domain.JavaParameterizedType; +import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.JavaTypeVariable; import org.assertj.core.api.AbstractObjectAssert; +import org.junit.Assert; -import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; +import static com.tngtech.archunit.core.domain.Formatters.ensureSimpleName; +import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; public class JavaTypeVariableAssertion extends AbstractObjectAssert { public JavaTypeVariableAssertion(JavaTypeVariable actual) { @@ -11,6 +22,74 @@ public JavaTypeVariableAssertion(JavaTypeVariable actual) { } public void hasBoundsMatching(Class... bounds) { - assertThatTypes(actual.getBounds()).as("Type variable bounds").matchExactly(bounds); + hasBoundsMatching(ExpectedConcreteType.wrap(bounds)); + } + + public void hasBoundsMatching(ExpectedConcreteType... bounds) { + assertConcreteTypesMatch(actual.getName(), actual.getBounds(), bounds); + } + + private void assertConcreteTypesMatch(String name, List actual, ExpectedConcreteType[] expected) { + assertConcreteTypesMatch(name, actual, ImmutableList.copyOf(expected)); + } + + private void assertConcreteTypesMatch(String context, List actual, List expected) { + assertThat(actual).as("Concrete type variable bounds of " + context).hasSize(expected.size()); + for (int i = 0; i < actual.size(); i++) { + assertConcreteTypeMatches(context, actual.get(i), expected.get(i)); + } + } + + private void assertConcreteTypeMatches(String context, JavaType actual, ExpectedConcreteType expected) { + String name = ensureSimpleName(actual.getName()); + String newContext = context + "->" + name; + assertThatType(actual).as("Concrete type " + newContext).matches(expected.type); + + assertTypeParametersMatch(actual, expected, newContext); + } + + private void assertTypeParametersMatch(JavaType actual, ExpectedConcreteType expected, String newContext) { + if (!expected.typeParameters.isEmpty() && !(actual instanceof JavaParameterizedType)) { + Assert.fail(String.format("Type %s is not parameterized, but expected to have type parameters %s", actual.getName(), expected.typeParameters)); + } + List actualTypeParameters = ((JavaParameterizedType) actual).getActualTypeArguments(); + assertConcreteTypesMatch(newContext, actualTypeParameters, expected.typeParameters); + } + + public static ExpectedConcreteType typeVariable(Class expectedType) { + return new ExpectedConcreteType(expectedType); + } + + public static class ExpectedConcreteType { + private final Type type; + private final List typeParameters = new ArrayList<>(); + + private ExpectedConcreteType(Type type) { + this.type = type; + } + + public ExpectedConcreteType withTypeArguments(Type... type) { + for (Type t : type) { + typeParameters.add(new ExpectedConcreteType(t)); + } + return this; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + type + formatTypeParameters() + '}'; + } + + private String formatTypeParameters() { + return !typeParameters.isEmpty() ? "<" + Joiner.on(", ").join(typeParameters) + ">" : ""; + } + + static ExpectedConcreteType[] wrap(Class... types) { + ExpectedConcreteType[] result = new ExpectedConcreteType[types.length]; + for (int i = 0; i < types.length; i++) { + result[i] = new ExpectedConcreteType(types[i]); + } + return result; + } } } From 5ef72ace706166d433f2d676b5778e2942904a9c Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Tue, 7 Apr 2020 22:18:56 +0200 Subject: [PATCH 034/115] add support for wildcard type parameters of bounds We now support wildcards at the first level of parameterized type bounds. E.g. wildcards in bounds like `T extends List`. To support quick localizations of errors in assertions I have added a `DescriptionContext` which can trace the steps (i.e. upper bound of second parameter of third type parameter) through the assertion and print the necessary details if a recursive assertion fails. Signed-off-by: Peter Gafert --- .../domain/DomainObjectCreationContext.java | 4 + .../core/domain/JavaTypeVariable.java | 34 ++- .../core/domain/JavaWildcardType.java | 112 +++++++++ .../domain/properties/HasUpperBounds.java | 29 +++ .../core/importer/DomainBuilders.java | 118 ++++++--- .../importer/JavaGenericTypeImporter.java | 26 +- .../core/domain/JavaTypeVariableTest.java | 1 + .../core/domain/JavaWildcardTypeTest.java | 130 ++++++++++ .../ClassFileImporterGenericClassesTest.java | 128 +++++++++- .../assertion/JavaTypeVariableAssertion.java | 228 +++++++++++++++--- 10 files changed, 725 insertions(+), 85 deletions(-) create mode 100644 archunit/src/main/java/com/tngtech/archunit/core/domain/JavaWildcardType.java create mode 100644 archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasUpperBounds.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java index 4f48a03c51..091fced3b2 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java @@ -152,6 +152,10 @@ public static JavaTypeVariable createTypeVariable(JavaTypeVariableBuilder builde return new JavaTypeVariable(builder); } + public static JavaWildcardType createWildcardType(DomainBuilders.JavaWildcardTypeBuilder builder) { + return new JavaWildcardType(builder); + } + static class AccessContext { final SetMultimap fieldAccessesByTarget = HashMultimap.create(); final SetMultimap methodCallsByTarget = HashMultimap.create(); diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java index 31b2ec18f1..f24713625a 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java @@ -20,6 +20,7 @@ import com.google.common.base.Joiner; import com.google.common.collect.FluentIterable; import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.core.domain.properties.HasUpperBounds; import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeVariableBuilder; import static com.google.common.collect.Iterables.getOnlyElement; @@ -40,15 +41,15 @@ * {@code SomeInterfaceOne} and {@code SomeInterfaceTwo}. */ @PublicAPI(usage = ACCESS) -public final class JavaTypeVariable implements JavaType { +public final class JavaTypeVariable implements JavaType, HasUpperBounds { private final String name; - private final List bounds; + private final List upperBounds; private final JavaClass erasure; JavaTypeVariable(JavaTypeVariableBuilder builder) { name = builder.getName(); - bounds = builder.getBounds(); - erasure = bounds.size() > 0 ? bounds.get(0).toErasure() : builder.getUnboundErasureType(); + upperBounds = builder.getUpperBounds(); + erasure = builder.getUnboundErasureType(upperBounds); } /** @@ -62,14 +63,23 @@ public String getName() { } /** - * @return All bounds of this {@link JavaTypeVariable}, i.e. super types any substitution + * @see #getUpperBounds() + */ + @PublicAPI(usage = ACCESS) + public List getBounds() { + return getUpperBounds(); + } + + /** + * @return All upper bounds of this {@link JavaTypeVariable}, i.e. super types any substitution * of this variable must extend. E.g. for - * {@code class MyClass} the bounds would be + * {@code class MyClass} the upper bounds would be * {@code SomeClass} and {@code SomeInterface} */ + @Override @PublicAPI(usage = ACCESS) - public List getBounds() { - return bounds; + public List getUpperBounds() { + return upperBounds; } @Override @@ -80,18 +90,18 @@ public JavaClass toErasure() { @Override public String toString() { - String bounds = printExtendsClause() ? " extends " + joinTypeNames(this.bounds) : ""; + String bounds = printExtendsClause() ? " extends " + joinTypeNames(upperBounds) : ""; return getClass().getSimpleName() + '{' + getName() + bounds + '}'; } private boolean printExtendsClause() { - if (bounds.isEmpty()) { + if (upperBounds.isEmpty()) { return false; } - if (bounds.size() > 1) { + if (upperBounds.size() > 1) { return true; } - return !getOnlyElement(bounds).getName().equals(Object.class.getName()); + return !getOnlyElement(upperBounds).getName().equals(Object.class.getName()); } private String joinTypeNames(List types) { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaWildcardType.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaWildcardType.java new file mode 100644 index 0000000000..ed18ae613a --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaWildcardType.java @@ -0,0 +1,112 @@ +/* + * Copyright 2014-2020 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.core.domain; + +import java.lang.reflect.WildcardType; +import java.util.List; + +import com.google.common.base.Joiner; +import com.google.common.collect.FluentIterable; +import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.core.domain.properties.HasUpperBounds; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaWildcardTypeBuilder; + +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; +import static com.tngtech.archunit.base.Guava.toGuava; +import static com.tngtech.archunit.core.domain.properties.HasName.Functions.GET_NAME; + +/** + * Represents a wildcard type in a type signature (compare the JLS). + * Consider the generic type {@code List}, then the parameterized type + * {@code List} would have the wildcard {@code ?} as its type argument + * (also see {@link JavaParameterizedType}).
+ * According to the JLS a wildcard may have upper and lower bounds.
+ * An upper bound denotes a common supertype any substitution of this wildcard must + * be assignable to. It is denoted by {@code ? extends SomeType}.
+ * A lower bound denotes a common subtype that must be assignable to all substitutions + * of this wildcard type. It is denoted by {@code ? super SomeType}. + */ +public class JavaWildcardType implements JavaType, HasUpperBounds { + private static final String WILDCARD_TYPE_NAME = "?"; + + private final List upperBounds; + private final List lowerBounds; + private final JavaClass erasure; + + JavaWildcardType(JavaWildcardTypeBuilder builder) { + upperBounds = builder.getUpperBounds(); + lowerBounds = builder.getLowerBounds(); + erasure = builder.getUnboundErasureType(upperBounds); + } + + /** + * @return The name of this {@link JavaWildcardType}, which is always "?" + */ + @Override + @PublicAPI(usage = ACCESS) + public String getName() { + return WILDCARD_TYPE_NAME; + } + + /** + * @return All upper bounds of this {@link JavaWildcardType}, i.e. supertypes any substitution + * of this variable must extend. E.g. for + * {@code List} the upper bounds would be {@code [SomeClass]}
+ * Note that the JLS currently only allows a single upper bound for a wildcard type, + * but we follow the Reflection API here and support a collection + * (compare {@link WildcardType#getUpperBounds()}). + */ + @Override + @PublicAPI(usage = ACCESS) + public List getUpperBounds() { + return upperBounds; + } + + /** + * @return All lower bounds of this {@link JavaWildcardType}, i.e. any substitution for this + * {@link JavaWildcardType} must be a supertype of all lower bounds. E.g. for + * {@code Handler>} the lower bounds would be {@code [SomeClass]}.
+ * Note that the JLS currently only allows a single lower bound for a wildcard type, + * but we follow the Reflection API here and support a collection + * (compare {@link WildcardType#getLowerBounds()}). + */ + @PublicAPI(usage = ACCESS) + public List getLowerBounds() { + return lowerBounds; + } + + @Override + @PublicAPI(usage = ACCESS) + public JavaClass toErasure() { + return erasure; + } + + @Override + public String toString() { + String bounds = boundsToString(); + return getClass().getSimpleName() + '{' + getName() + bounds + '}'; + } + + private String boundsToString() { + String upperBoundsString = !upperBounds.isEmpty() ? " extends " + joinTypeNames(upperBounds) : ""; + String lowerBoundsString = !lowerBounds.isEmpty() ? " super " + joinTypeNames(lowerBounds) : ""; + return upperBoundsString + lowerBoundsString; + } + + private String joinTypeNames(List types) { + return FluentIterable.from(types).transform(toGuava(GET_NAME)).join(Joiner.on(" & ")); + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasUpperBounds.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasUpperBounds.java new file mode 100644 index 0000000000..6027e8a443 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasUpperBounds.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014-2020 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.core.domain.properties; + +import java.util.List; + +import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.core.domain.JavaType; + +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + +@PublicAPI(usage = ACCESS) +public interface HasUpperBounds { + @PublicAPI(usage = ACCESS) + List getUpperBounds(); +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java index ca3330c8ad..b5648cff31 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java @@ -57,6 +57,7 @@ import com.tngtech.archunit.core.domain.JavaStaticInitializer; import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.JavaTypeVariable; +import com.tngtech.archunit.core.domain.JavaWildcardType; import com.tngtech.archunit.core.domain.Source; import com.tngtech.archunit.core.domain.ThrowsClause; import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder.ValueBuilder; @@ -66,6 +67,7 @@ import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createSource; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createThrowsClause; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createTypeVariable; +import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createWildcardType; import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; @Internal @@ -508,63 +510,121 @@ JavaStaticInitializer construct(JavaStaticInitializerBuilder builder, ClassesByT } } + private interface JavaTypeBuilder { + JavaType build(ClassesByTypeName classes); + } + @Internal - public static final class JavaTypeVariableBuilder { + public static final class JavaTypeVariableBuilder implements JavaTypeBuilder { private final String name; - private final List bounds = new ArrayList<>(); + private final List upperBounds = new ArrayList<>(); private ClassesByTypeName importedClasses; JavaTypeVariableBuilder(String name) { this.name = checkNotNull(name); } + void addBound(JavaParameterizedTypeBuilder builder) { + upperBounds.add(builder); + } + + @Override + public JavaTypeVariable build(ClassesByTypeName importedClasses) { + this.importedClasses = importedClasses; + return createTypeVariable(this); + } + public String getName() { return name; } - void addBound(JavaParameterizedTypeBuilder builder) { - bounds.add(builder); + public List getUpperBounds() { + return buildJavaTypes(upperBounds, importedClasses); } - public List getBounds() { - ImmutableList.Builder result = ImmutableList.builder(); - for (JavaParameterizedTypeBuilder bound : bounds) { - result.add(bound.build(importedClasses)); - } - return result.build(); + public JavaClass getUnboundErasureType(List upperBounds) { + return DomainBuilders.getUnboundErasureType(upperBounds, importedClasses); + } + } + + @Internal + public static final class JavaWildcardTypeBuilder implements JavaTypeBuilder { + private final List lowerBounds = new ArrayList<>(); + private final List upperBounds = new ArrayList<>(); + private ClassesByTypeName importedClasses; + + JavaWildcardTypeBuilder() { } - public JavaClass getUnboundErasureType() { - return importedClasses.get(Object.class.getName()); + public void addLowerBound(JavaTypeBuilder boundBuilder) { + lowerBounds.add(boundBuilder); } - public JavaTypeVariable build(ClassesByTypeName importedClasses) { + public void addUpperBound(JavaTypeBuilder boundBuilder) { + upperBounds.add(boundBuilder); + } + + @Override + public JavaWildcardType build(ClassesByTypeName importedClasses) { this.importedClasses = importedClasses; - return createTypeVariable(this); + return createWildcardType(this); } - static class JavaParameterizedTypeBuilder { - private final JavaClassDescriptor type; - private final List typeArgumentBuilders = new ArrayList<>(); + public List getUpperBounds() { + return buildJavaTypes(upperBounds, importedClasses); + } - JavaParameterizedTypeBuilder(JavaClassDescriptor type) { - this.type = type; - } + public List getLowerBounds() { + return buildJavaTypes(lowerBounds, importedClasses); + } - void addTypeArgument(JavaClassDescriptor type) { - typeArgumentBuilders.add(new JavaParameterizedTypeBuilder(type)); - } + public JavaClass getUnboundErasureType(List upperBounds) { + return DomainBuilders.getUnboundErasureType(upperBounds, importedClasses); + } + } - public JavaParameterizedType build(ClassesByTypeName classes) { - ImmutableList.Builder typeArguments = ImmutableList.builder(); - for (JavaParameterizedTypeBuilder typeArgumentBuilder : typeArgumentBuilders) { - typeArguments.add(typeArgumentBuilder.build(classes)); - } - return new ImportedParameterizedType(classes.get(type.getFullyQualifiedClassName()), typeArguments.build()); + static class JavaParameterizedTypeBuilder implements JavaTypeBuilder { + private final JavaClassDescriptor type; + private final List typeArgumentBuilders = new ArrayList<>(); + + JavaParameterizedTypeBuilder(JavaClassDescriptor type) { + this.type = type; + } + + void addTypeArgument(JavaClassDescriptor type) { + typeArgumentBuilders.add(new JavaParameterizedTypeBuilder(type)); + } + + public JavaWildcardTypeBuilder addWildcardTypeParameter() { + JavaWildcardTypeBuilder wildcardTypeBuilder = new JavaWildcardTypeBuilder(); + typeArgumentBuilders.add(wildcardTypeBuilder); + return wildcardTypeBuilder; + } + + @Override + public JavaParameterizedType build(ClassesByTypeName classes) { + ImmutableList.Builder typeArguments = ImmutableList.builder(); + for (JavaTypeBuilder typeArgumentBuilder : typeArgumentBuilders) { + typeArguments.add(typeArgumentBuilder.build(classes)); } + return new ImportedParameterizedType(classes.get(type.getFullyQualifiedClassName()), typeArguments.build()); } } + private static List buildJavaTypes(List javaTypeBuilders, ClassesByTypeName importedClasses) { + ImmutableList.Builder result = ImmutableList.builder(); + for (JavaTypeBuilder builder : javaTypeBuilders) { + result.add(builder.build(importedClasses)); + } + return result.build(); + } + + private static JavaClass getUnboundErasureType(List upperBounds, ClassesByTypeName importedClasses) { + return upperBounds.size() > 0 + ? upperBounds.get(0).toErasure() + : importedClasses.get(Object.class.getName()); + } + @Internal interface BuilderWithBuildParameter { VALUE build(PARAMETER parameter, ClassesByTypeName importedClasses); diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java index 84b2e4f828..5bd4dd9020 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java @@ -18,8 +18,9 @@ import java.util.ArrayList; import java.util.List; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaParameterizedTypeBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeVariableBuilder; -import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeVariableBuilder.JavaParameterizedTypeBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaWildcardTypeBuilder; import com.tngtech.archunit.core.importer.JavaClassProcessor.DeclarationHandler; import org.objectweb.asm.signature.SignatureReader; import org.objectweb.asm.signature.SignatureVisitor; @@ -72,8 +73,31 @@ public void visitClassType(String internalObjectName) { currentlyVisiting.addBound(bound); } + @Override + public void visitTypeArgument() { + bound.addWildcardTypeParameter(); + } + @Override public SignatureVisitor visitTypeArgument(char wildcard) { + if (wildcard == '+') { + final JavaWildcardTypeBuilder javaWildcardTypeBuilder = bound.addWildcardTypeParameter(); + return new SignatureVisitor(ASM_API_VERSION) { + @Override + public void visitClassType(String internalObjectName) { + javaWildcardTypeBuilder.addUpperBound(new JavaParameterizedTypeBuilder(JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName))); + } + }; + } + if (wildcard == '-') { + final JavaWildcardTypeBuilder javaWildcardTypeBuilder = bound.addWildcardTypeParameter(); + return new SignatureVisitor(ASM_API_VERSION) { + @Override + public void visitClassType(String internalObjectName) { + javaWildcardTypeBuilder.addLowerBound(new JavaParameterizedTypeBuilder(JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName))); + } + }; + } return new SignatureVisitor(ASM_API_VERSION) { @Override public void visitClassType(String internalObjectName) { diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java index 7a7c828248..008f64cb93 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java @@ -33,6 +33,7 @@ class ClassWithUnboundTypeParameter & Serializ JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithUnboundTypeParameter.class).getTypeParameters().get(0); assertThatTypes(type.getBounds()).matchExactly(HashMap.class, Serializable.class); + assertThatTypes(type.getUpperBounds()).matchExactly(HashMap.class, Serializable.class); } @Test diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java new file mode 100644 index 0000000000..731185c169 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java @@ -0,0 +1,130 @@ +package com.tngtech.archunit.core.domain; + +import java.io.Serializable; +import java.util.List; + +import com.tngtech.archunit.core.importer.ClassFileImporter; +import org.junit.Test; + +import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; + +public class JavaWildcardTypeTest { + + @Test + public void wildcard_name() { + @SuppressWarnings("unused") + class ClassWithUnboundTypeParameter> { + } + + JavaWildcardType type = importWildcardTypeOf(ClassWithUnboundTypeParameter.class); + + assertThat(type.getName()).isEqualTo("?"); + } + + @Test + public void wildcard_upper_bounds() { + @SuppressWarnings("unused") + class ClassWithUnboundTypeParameter> { + } + + JavaWildcardType type = importWildcardTypeOf(ClassWithUnboundTypeParameter.class); + + assertThatTypes(type.getUpperBounds()).matchExactly(Serializable.class); + } + + @Test + public void wildcard_lower_bounds() { + @SuppressWarnings("unused") + class ClassWithUnboundTypeParameter> { + } + + JavaWildcardType type = importWildcardTypeOf(ClassWithUnboundTypeParameter.class); + + assertThatTypes(type.getLowerBounds()).matchExactly(Serializable.class); + } + + @Test + public void erased_unbound_wildcard_is_java_lang_Object() { + @SuppressWarnings("unused") + class ClassWithUnboundWildcard> { + } + + JavaWildcardType type = importWildcardTypeOf(ClassWithUnboundWildcard.class); + + assertThatType(type.toErasure()).matches(Object.class); + } + + @Test + public void erased_wildcard_bound_by_single_class_is_this_class() { + @SuppressWarnings("unused") + class ClassWithBoundTypeParameterWithSingleClassBound> { + } + + JavaWildcardType type = importWildcardTypeOf(ClassWithBoundTypeParameterWithSingleClassBound.class); + + assertThatType(type.toErasure()).matches(Serializable.class); + } + + @Test + public void erased_wildcard_bound_by_single_generic_class_is_the_erasure_of_this_class() { + @SuppressWarnings("unused") + class ClassWithBoundTypeParameterWithSingleGenericClassBound>> { + } + + JavaWildcardType type = importWildcardTypeOf(ClassWithBoundTypeParameterWithSingleGenericClassBound.class); + + assertThatType(type.toErasure()).matches(List.class); + } + + @Test + public void toString_unbounded() { + @SuppressWarnings("unused") + class Unbounded> { + } + + JavaWildcardType wildcardType = importWildcardTypeOf(Unbounded.class); + + assertThat(wildcardType.toString()) + .contains(JavaWildcardType.class.getSimpleName()) + .contains("?") + .doesNotContain("extends") + .doesNotContain("super"); + } + + @Test + public void toString_upper_bounded() { + @SuppressWarnings("unused") + class UpperBounded> { + } + + JavaWildcardType wildcardType = importWildcardTypeOf(UpperBounded.class); + + assertThat(wildcardType.toString()) + .contains(JavaWildcardType.class.getSimpleName()) + .contains("? extends java.lang.String") + .doesNotContain("super"); + } + + @Test + public void toString_lower_bounded() { + @SuppressWarnings("unused") + class LowerBounded> { + } + + JavaWildcardType wildcardType = importWildcardTypeOf(LowerBounded.class); + + assertThat(wildcardType.toString()) + .contains(JavaWildcardType.class.getSimpleName()) + .contains("? super java.lang.String") + .doesNotContain("extends"); + } + + private JavaWildcardType importWildcardTypeOf(Class clazz) { + JavaType listType = new ClassFileImporter().importClass(clazz).getTypeParameters().get(0) + .getUpperBounds().get(0); + JavaType wildcardType = ((JavaParameterizedType) listType).getActualTypeArguments().get(0); + return (JavaWildcardType) wildcardType; + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java index 7e4e5ac1f2..d744020d2d 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java @@ -3,19 +3,29 @@ import java.io.Closeable; import java.io.File; import java.io.Serializable; +import java.lang.ref.Reference; +import java.util.List; import java.util.Map; import com.tngtech.archunit.base.Function; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.testutil.ArchConfigurationRule; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.archunit.testutil.Assertions.assertThatType; -import static com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.typeVariable; +import static com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.ExpectedConcreteWildcardType.wildcardType; +import static com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.parameterizedType; +import static com.tngtech.java.junit.dataprovider.DataProviders.$; +import static com.tngtech.java.junit.dataprovider.DataProviders.$$; +@RunWith(DataProviderRunner.class) public class ClassFileImporterGenericClassesTest { @Rule @@ -141,7 +151,7 @@ class ClassWithSingleTypeParameterWithGenericClassBoundAssignedToConcreteClass> { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithSingleTypeParameterBoundByTypeWithUnboundWildcard.class, List.class, String.class); + + JavaClass javaClass = classes.get(ClassWithSingleTypeParameterBoundByTypeWithUnboundWildcard.class); + + assertThatType(javaClass) + .hasTypeParameter("T").withBoundsMatching(parameterizedType(List.class).withWildcardTypeParameter()); + } + + @DataProvider + public static Object[][] single_type_bound_with_upper_bound_wildcard() { + @SuppressWarnings("unused") + class ClassWithSingleTypeParameterBoundByTypeWithWildcardWithUpperClassBound> { + } + @SuppressWarnings("unused") + class ClassWithSingleTypeParameterBoundByTypeWithWildcardWithUpperInterfaceBound> { + } + + return $$( + $(ClassWithSingleTypeParameterBoundByTypeWithWildcardWithUpperClassBound.class, String.class), + $(ClassWithSingleTypeParameterBoundByTypeWithWildcardWithUpperInterfaceBound.class, Serializable.class) + ); + } + + @Test + @UseDataProvider("single_type_bound_with_upper_bound_wildcard") + public void imports_single_type_bound_with_upper_bound_wildcard(Class classWithWildcard, Class expectedUpperBound) { + JavaClasses classes = new ClassFileImporter().importClasses(classWithWildcard, List.class, expectedUpperBound); + + JavaClass javaClass = classes.get(classWithWildcard); + + assertThatType(javaClass) + .hasTypeParameter("T").withBoundsMatching(parameterizedType(List.class).withWildcardTypeParameterWithUpperBound(expectedUpperBound)); + } + + @DataProvider + public static Object[][] single_type_bound_with_lower_bound_wildcard() { + @SuppressWarnings("unused") + class ClassWithSingleTypeParameterBoundByTypeWithWildcardWithLowerClassBound> { + } + @SuppressWarnings("unused") + class ClassWithSingleTypeParameterBoundByTypeWithWildcardWithLowerInterfaceBound> { + } + + return $$( + $(ClassWithSingleTypeParameterBoundByTypeWithWildcardWithLowerClassBound.class, String.class), + $(ClassWithSingleTypeParameterBoundByTypeWithWildcardWithLowerInterfaceBound.class, Serializable.class) + ); + } + + @Test + @UseDataProvider("single_type_bound_with_lower_bound_wildcard") + public void imports_single_type_bound_with_lower_bound_wildcard(Class classWithWildcard, Class expectedLowerBound) { + JavaClasses classes = new ClassFileImporter().importClasses(classWithWildcard, List.class, expectedLowerBound); + + JavaClass javaClass = classes.get(classWithWildcard); + + assertThatType(javaClass) + .hasTypeParameter("T").withBoundsMatching(parameterizedType(List.class).withWildcardTypeParameterWithLowerBound(expectedLowerBound)); + } + + @Test + public void imports_multiple_type_bounds_with_multiple_wildcards_with_various_bounds() { + @SuppressWarnings("unused") + class ClassWithMultipleTypeParametersBoundByTypesWithDifferentBounds, B extends Reference & Map> { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithMultipleTypeParametersBoundByTypesWithDifferentBounds.class, + Map.class, Serializable.class, File.class, Reference.class, String.class); + + JavaClass javaClass = classes.get(ClassWithMultipleTypeParametersBoundByTypesWithDifferentBounds.class); + + assertThatType(javaClass).hasTypeParameters("A", "B") + .hasTypeParameter("A") + .withBoundsMatching( + parameterizedType(Map.class) + .withWildcardTypeParameters( + wildcardType().withUpperBound(Serializable.class), + wildcardType().withLowerBound(File.class) + )) + .hasTypeParameter("B") + .withBoundsMatching( + parameterizedType(Reference.class) + .withWildcardTypeParameters( + wildcardType().withLowerBound(String.class) + ), + parameterizedType(Map.class) + .withWildcardTypeParameters( + wildcardType(), + wildcardType() + )); } @SuppressWarnings("unused") diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java index 4cf55686d8..3ed9b90f84 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java @@ -5,16 +5,21 @@ import java.util.List; import com.google.common.base.Joiner; +import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.tngtech.archunit.core.domain.JavaParameterizedType; import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.JavaTypeVariable; +import com.tngtech.archunit.core.domain.JavaWildcardType; import org.assertj.core.api.AbstractObjectAssert; import org.junit.Assert; +import static com.google.common.collect.Iterables.cycle; +import static com.google.common.collect.Iterables.limit; import static com.tngtech.archunit.core.domain.Formatters.ensureSimpleName; import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.ExpectedConcreteWildcardType.wildcardType; public class JavaTypeVariableAssertion extends AbstractObjectAssert { public JavaTypeVariableAssertion(JavaTypeVariable actual) { @@ -22,59 +27,87 @@ public JavaTypeVariableAssertion(JavaTypeVariable actual) { } public void hasBoundsMatching(Class... bounds) { - hasBoundsMatching(ExpectedConcreteType.wrap(bounds)); + hasBoundsMatching(ExpectedConcreteParameterizedType.wrap(bounds)); } public void hasBoundsMatching(ExpectedConcreteType... bounds) { - assertConcreteTypesMatch(actual.getName(), actual.getBounds(), bounds); + DescriptionContext context = new DescriptionContext(actual.getName()).step("bounds").describeUpperBounds(); + assertConcreteTypesMatch(context, actual.getBounds(), ImmutableList.copyOf(bounds)); } - private void assertConcreteTypesMatch(String name, List actual, ExpectedConcreteType[] expected) { - assertConcreteTypesMatch(name, actual, ImmutableList.copyOf(expected)); - } - - private void assertConcreteTypesMatch(String context, List actual, List expected) { - assertThat(actual).as("Concrete type variable bounds of " + context).hasSize(expected.size()); + private static void assertConcreteTypesMatch(DescriptionContext context, List actual, List expected) { + assertThat(actual).as(context.describeElements(actual.size()).toString()).hasSize(expected.size()); for (int i = 0; i < actual.size(); i++) { - assertConcreteTypeMatches(context, actual.get(i), expected.get(i)); + DescriptionContext elementContext = context.describeElement(i, actual.size()); + expected.get(i).assertMatchWith(actual.get(i), elementContext); } } - private void assertConcreteTypeMatches(String context, JavaType actual, ExpectedConcreteType expected) { - String name = ensureSimpleName(actual.getName()); - String newContext = context + "->" + name; - assertThatType(actual).as("Concrete type " + newContext).matches(expected.type); - - assertTypeParametersMatch(actual, expected, newContext); - } - - private void assertTypeParametersMatch(JavaType actual, ExpectedConcreteType expected, String newContext) { - if (!expected.typeParameters.isEmpty() && !(actual instanceof JavaParameterizedType)) { - Assert.fail(String.format("Type %s is not parameterized, but expected to have type parameters %s", actual.getName(), expected.typeParameters)); - } - List actualTypeParameters = ((JavaParameterizedType) actual).getActualTypeArguments(); - assertConcreteTypesMatch(newContext, actualTypeParameters, expected.typeParameters); + public static ExpectedConcreteParameterizedType parameterizedType(Class expectedType) { + return new ExpectedConcreteParameterizedType(expectedType); } - public static ExpectedConcreteType typeVariable(Class expectedType) { - return new ExpectedConcreteType(expectedType); + public interface ExpectedConcreteType { + void assertMatchWith(JavaType actual, DescriptionContext context); } - public static class ExpectedConcreteType { - private final Type type; + public static class ExpectedConcreteParameterizedType implements ExpectedConcreteType { + private Type type; private final List typeParameters = new ArrayList<>(); - private ExpectedConcreteType(Type type) { + private ExpectedConcreteParameterizedType(Type type) { this.type = type; } public ExpectedConcreteType withTypeArguments(Type... type) { - for (Type t : type) { - typeParameters.add(new ExpectedConcreteType(t)); - } + return withTypeArguments(ExpectedConcreteParameterizedType.wrap(type)); + } + + public ExpectedConcreteType withTypeArguments(ExpectedConcreteType... type) { + typeParameters.addAll(ImmutableList.copyOf(type)); return this; } + public ExpectedConcreteType withWildcardTypeParameter() { + return withTypeArguments(new ExpectedConcreteWildcardType()); + } + + public ExpectedConcreteType withWildcardTypeParameterWithUpperBound(Class bound) { + return withWildcardTypeParameterWithUpperBound(new ExpectedConcreteParameterizedType(bound)); + } + + public ExpectedConcreteType withWildcardTypeParameterWithUpperBound(ExpectedConcreteType bound) { + return withTypeArguments(wildcardType().withUpperBound(bound)); + } + + public ExpectedConcreteType withWildcardTypeParameterWithLowerBound(Class bound) { + return withWildcardTypeParameterWithLowerBound(new ExpectedConcreteParameterizedType(bound)); + } + + public ExpectedConcreteType withWildcardTypeParameterWithLowerBound(ExpectedConcreteType bound) { + return withTypeArguments(wildcardType().withLowerBound(bound)); + } + + public ExpectedConcreteType withWildcardTypeParameters(ExpectedConcreteWildcardType... wildcardTypes) { + return withTypeArguments(wildcardTypes); + } + + @Override + public void assertMatchWith(JavaType actual, DescriptionContext context) { + DescriptionContext newContext = context.describe(ensureSimpleName(actual.getName())); + assertThatType(actual).as(newContext.toString()).matches(type); + assertTypeParametersMatch(actual, newContext); + } + + private void assertTypeParametersMatch(JavaType actual, DescriptionContext context) { + DescriptionContext parameterContext = context.step("type parameters").describeTypeParameters(); + if (!typeParameters.isEmpty() && !(actual instanceof JavaParameterizedType)) { + Assert.fail(String.format("%s: Not parameterized, but expected to have type parameters %s", parameterContext, typeParameters)); + } + List actualTypeParameters = ((JavaParameterizedType) actual).getActualTypeArguments(); + assertConcreteTypesMatch(parameterContext, actualTypeParameters, typeParameters); + } + @Override public String toString() { return getClass().getSimpleName() + "{" + type + formatTypeParameters() + '}'; @@ -84,12 +117,141 @@ private String formatTypeParameters() { return !typeParameters.isEmpty() ? "<" + Joiner.on(", ").join(typeParameters) + ">" : ""; } - static ExpectedConcreteType[] wrap(Class... types) { + static ExpectedConcreteType[] wrap(Type... types) { ExpectedConcreteType[] result = new ExpectedConcreteType[types.length]; for (int i = 0; i < types.length; i++) { - result[i] = new ExpectedConcreteType(types[i]); + result[i] = new ExpectedConcreteParameterizedType(types[i]); } return result; } } + + public static class ExpectedConcreteWildcardType implements ExpectedConcreteType { + private final List upperBounds = new ArrayList<>(); + private final List lowerBounds = new ArrayList<>(); + + private ExpectedConcreteWildcardType() { + } + + @Override + public void assertMatchWith(JavaType actual, DescriptionContext context) { + context = context.describe(actual.getName()); + + assertThat(actual).as(context.toString()).isInstanceOf(JavaWildcardType.class); + JavaWildcardType wildcardType = (JavaWildcardType) actual; + assertThat(wildcardType.getName()).as(context.toString()).isEqualTo("?"); + + assertUpperBoundsMatch(wildcardType, context); + assertLowerBoundMatch(wildcardType, context); + } + + private void assertUpperBoundsMatch(JavaWildcardType actual, DescriptionContext context) { + context = context.step("upper bounds"); + assertThat(actual.getUpperBounds()).as(context.toString()).hasSameSizeAs(upperBounds); + context = context.describeUpperBounds(); + assertBoundsMatch(actual.getUpperBounds(), upperBounds, context); + } + + private void assertLowerBoundMatch(JavaWildcardType actual, DescriptionContext context) { + context = context.step("lower bounds"); + assertThat(actual.getLowerBounds()).as(context.toString()).hasSameSizeAs(lowerBounds); + context = context.describeLowerBounds(); + assertBoundsMatch(actual.getLowerBounds(), lowerBounds, context); + } + + private void assertBoundsMatch(List actualBounds, List expectedBounds, DescriptionContext context) { + for (int i = 0; i < expectedBounds.size(); i++) { + expectedBounds.get(i).assertMatchWith(actualBounds.get(i), context); + } + } + + public ExpectedConcreteWildcardType withUpperBound(Class bound) { + return withUpperBound(new ExpectedConcreteParameterizedType(bound)); + } + + public ExpectedConcreteWildcardType withUpperBound(ExpectedConcreteType bound) { + upperBounds.add(bound); + return this; + } + + public ExpectedConcreteWildcardType withLowerBound(Class bound) { + return withLowerBound(new ExpectedConcreteParameterizedType(bound)); + } + + public ExpectedConcreteWildcardType withLowerBound(ExpectedConcreteType bound) { + lowerBounds.add(bound); + return this; + } + + public static ExpectedConcreteWildcardType wildcardType() { + return new ExpectedConcreteWildcardType(); + } + } + + private static class DescriptionContext { + private static final String MARKER = "##MARKER##"; + private static final String PLACEHOLDER = "_"; + + private final String context; + private final String description; + private final String currentElement; + private final String joinString; + + DescriptionContext(String context) { + this(context + MARKER, "assertion", "object", ", "); + } + + private DescriptionContext(String context, String description, String currentElement, String joinString) { + this.context = context; + this.description = description; + this.currentElement = currentElement; + this.joinString = joinString; + } + + public DescriptionContext describe(String part) { + return new DescriptionContext(context.replace(MARKER, part + MARKER), description, part, joinString); + } + + public DescriptionContext describeUpperBounds() { + String newContext = context.replace(MARKER, " extends " + MARKER); + return new DescriptionContext(newContext, description, currentElement, " & "); + } + + public DescriptionContext describeLowerBounds() { + String newContext = context.replace(MARKER, " super " + MARKER); + return new DescriptionContext(newContext, description, currentElement, " & "); + } + + public DescriptionContext describeElements(int number) { + return new DescriptionContext(context.replace(MARKER, joinedPlaceHolders(number)), description, currentElement, joinString); + } + + public DescriptionContext describeElement(int index, int totalSize) { + int maxIndex = totalSize - 1; + String prefix = index > 0 ? joinedPlaceHolders(index) + joinString : ""; + String suffix = index < maxIndex ? joinString + joinedPlaceHolders(maxIndex - index) : ""; + String newContext = context.replace(MARKER, prefix + MARKER + suffix); + String newCurrentElement = this.currentElement + "[" + index + "]"; + return new DescriptionContext(newContext, description, newCurrentElement, joinString); + } + + private String joinedPlaceHolders(int number) { + return FluentIterable.from(limit(cycle(PLACEHOLDER), number)).join(Joiner.on(joinString)); + } + + public DescriptionContext step(String description) { + return new DescriptionContext(context, description, currentElement, joinString); + } + + public DescriptionContext describeTypeParameters() { + String newContext = context.replace(MARKER, "<" + MARKER + ">"); + String newJoinString = ", "; + return new DescriptionContext(newContext, description, currentElement, newJoinString); + } + + @Override + public String toString() { + return "\"" + description + "\"[" + currentElement + "] -> " + context.replace(MARKER, ""); + } + } } From a8ba955e002c27fdfdafd8ff44d6a14a04e619bf Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Sun, 19 Apr 2020 14:57:35 +0200 Subject: [PATCH 035/115] support type variables as upper bounds of other type variables We now support signatures like `Foo`, where one type parameter is bound by another type parameter. What makes this difficult is the fact that `extends S` in this case is a reference to the original type parameter `S` (same holds for the Java Reflection API). I.e. in a case like `Foo` it must be possible to retrieve `typeParameters[1].upperBounds[0].upperBounds[0]` to be `java.lang.String`. So to build the type parameter `T`, we need the type parameter `S` already built so we can reference it. To support this, I have split the creation of type parameters into `build()`, which will skip the bounds (since we need the built type parameters for this), and `complete` where the bounds are finished and set into the type parameters (thus unfortunately removing immutability, but I do not think you can have this in such a chicken and egg scenario with Java capabilities; in any way the mutability is package private, from an API point of view this object will be immutable like all ArchUnit domain objects). Unfortunately this is a fairly recursive thing, so each builder in the type parameter bounds tree needs all constructed type parameters, because it might have bounds or assigned type parameters somewhere down the line that are type parameters themselves. Within the import process we now need one more level of indirection, because we must decide if this is a new type to build (like a parameterized type) or if it simply references another type parameter and thus needs to return an already constructed one. Therefore there is now `JavaTypeCreationProcess` to encapsulate these two cases (technically it would have been possible to hack `TypeVariableBuilder` to return an existing type variable on build, but I have decided against it, since it would be a violation of the builder pattern and thus surprising). Signed-off-by: Peter Gafert --- .../domain/DomainObjectCreationContext.java | 12 ++- .../core/domain/JavaTypeVariable.java | 18 ++-- .../core/importer/ClassFileImportRecord.java | 22 +++-- .../core/importer/ClassFileProcessor.java | 6 +- .../core/importer/ClassGraphCreator.java | 9 +- .../core/importer/DomainBuilders.java | 98 +++++++++++-------- .../core/importer/JavaClassProcessor.java | 2 +- .../importer/JavaGenericTypeImporter.java | 80 ++++++++++++--- .../ClassFileImporterGenericClassesTest.java | 30 ++++++ .../assertion/JavaTypeVariableAssertion.java | 43 +++++++- 10 files changed, 232 insertions(+), 88 deletions(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java index 091fced3b2..2d6c9b2db6 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java @@ -45,7 +45,7 @@ import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodCallBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaStaticInitializerBuilder; -import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeVariableBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaWildcardTypeBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.MethodCallTargetBuilder; /** @@ -148,11 +148,15 @@ public static ThrowsClause createThr return ThrowsClause.from(codeUnit, types); } - public static JavaTypeVariable createTypeVariable(JavaTypeVariableBuilder builder) { - return new JavaTypeVariable(builder); + public static JavaTypeVariable createTypeVariable(String name, JavaClass erasure) { + return new JavaTypeVariable(name, erasure); } - public static JavaWildcardType createWildcardType(DomainBuilders.JavaWildcardTypeBuilder builder) { + public static void completeTypeVariable(JavaTypeVariable variable, List upperBounds) { + variable.setUpperBounds(upperBounds); + } + + public static JavaWildcardType createWildcardType(JavaWildcardTypeBuilder builder) { return new JavaWildcardType(builder); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java index f24713625a..9b1c940917 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java @@ -21,12 +21,12 @@ import com.google.common.collect.FluentIterable; import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.core.domain.properties.HasUpperBounds; -import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeVariableBuilder; import static com.google.common.collect.Iterables.getOnlyElement; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; import static com.tngtech.archunit.base.Guava.toGuava; import static com.tngtech.archunit.core.domain.properties.HasName.Functions.GET_NAME; +import static java.util.Collections.emptyList; /** * Represents a type variable used by generic types and members.
@@ -43,13 +43,17 @@ @PublicAPI(usage = ACCESS) public final class JavaTypeVariable implements JavaType, HasUpperBounds { private final String name; - private final List upperBounds; - private final JavaClass erasure; + private List upperBounds = emptyList(); + private JavaClass erasure; - JavaTypeVariable(JavaTypeVariableBuilder builder) { - name = builder.getName(); - upperBounds = builder.getUpperBounds(); - erasure = builder.getUnboundErasureType(upperBounds); + JavaTypeVariable(String name, JavaClass erasure) { + this.name = name; + this.erasure = erasure; + } + + void setUpperBounds(List upperBounds) { + this.upperBounds = upperBounds; + erasure = upperBounds.isEmpty() ? erasure : upperBounds.get(0).toErasure(); } /** diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java index 1b854bce61..44d1c3d1d8 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java @@ -16,19 +16,19 @@ package com.tngtech.archunit.core.importer; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; -import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ListMultimap; import com.google.common.collect.SetMultimap; import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeParameterBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.TypeParametersBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,11 +37,14 @@ class ClassFileImportRecord { private static final Logger LOG = LoggerFactory.getLogger(ClassFileImportRecord.class); + private static final TypeParametersBuilder NO_TYPE_PARAMETERS = + new TypeParametersBuilder(Collections.emptySet()); + private final Map classes = new HashMap<>(); private final Map superClassNamesByOwner = new HashMap<>(); private final SetMultimap interfaceNamesByOwner = HashMultimap.create(); - private final ListMultimap typeParameterBuildersByOwner = ArrayListMultimap.create(); + private final Map typeParametersBuilderByOwner = new HashMap<>(); private final SetMultimap fieldBuildersByOwner = HashMultimap.create(); private final SetMultimap methodBuildersByOwner = HashMultimap.create(); private final SetMultimap constructorBuildersByOwner = HashMultimap.create(); @@ -64,8 +67,8 @@ void addInterfaces(String ownerName, Set interfaceNames) { interfaceNamesByOwner.putAll(ownerName, interfaceNames); } - public void addTypeParameters(String ownerName, List builders) { - typeParameterBuildersByOwner.putAll(ownerName, builders); + public void addTypeParameters(String ownerName, TypeParametersBuilder builder) { + typeParametersBuilderByOwner.put(ownerName, builder); } void addField(String ownerName, DomainBuilders.JavaFieldBuilder fieldBuilder) { @@ -103,8 +106,11 @@ Set getInterfaceNamesFor(String ownerName) { return interfaceNamesByOwner.get(ownerName); } - List getTypeParameterBuildersFor(String ownerName) { - return typeParameterBuildersByOwner.get(ownerName); + TypeParametersBuilder getTypeParameterBuildersFor(String ownerName) { + if (!typeParametersBuilderByOwner.containsKey(ownerName)) { + return NO_TYPE_PARAMETERS; + } + return typeParametersBuilderByOwner.get(ownerName); } Set getFieldBuildersFor(String ownerName) { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java index b12d14ca93..b49669c272 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java @@ -17,7 +17,6 @@ import java.io.InputStream; import java.net.URI; -import java.util.List; import java.util.Set; import com.tngtech.archunit.ArchConfiguration; @@ -25,6 +24,7 @@ import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.domain.JavaFieldAccess.AccessType; +import com.tngtech.archunit.core.importer.DomainBuilders.TypeParametersBuilder; import com.tngtech.archunit.core.importer.JavaClassProcessor.AccessHandler; import com.tngtech.archunit.core.importer.JavaClassProcessor.DeclarationHandler; import com.tngtech.archunit.core.importer.RawAccessRecord.CodeUnit; @@ -88,8 +88,8 @@ public void onNewClass(String className, Optional superClassName, Set typeVariableBuilders) { - importRecord.addTypeParameters(ownerName, typeVariableBuilders); + public void onDeclaredTypeParameters(TypeParametersBuilder typeParametersBuilder) { + importRecord.addTypeParameters(ownerName, typeParametersBuilder); } @Override diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java index 1b80ad0ef2..186edbc91f 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java @@ -21,7 +21,6 @@ import java.util.Set; import com.google.common.collect.HashMultimap; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; import com.google.common.collect.SetMultimap; @@ -52,6 +51,7 @@ import com.tngtech.archunit.core.importer.DomainBuilders.JavaConstructorCallBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaFieldAccessBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodCallBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.TypeParametersBuilder; import com.tngtech.archunit.core.importer.resolvers.ClassResolver; import static com.google.common.collect.Iterables.concat; @@ -264,11 +264,8 @@ public Set createInterfaces(JavaClass owner) { @Override public List createTypeParameters(JavaClass owner) { - ImmutableList.Builder result = ImmutableList.builder(); - for (DomainBuilders.JavaTypeVariableBuilder builder : importRecord.getTypeParameterBuildersFor(owner.getName())) { - result.add(builder.build(classes.byTypeName())); - } - return result.build(); + TypeParametersBuilder typeParametersBuilder = importRecord.getTypeParameterBuildersFor(owner.getName()); + return typeParametersBuilder.build(classes.byTypeName()); } @Override diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java index b5648cff31..9c6348094b 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java @@ -16,6 +16,7 @@ package com.tngtech.archunit.core.importer; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; @@ -63,6 +64,7 @@ import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder.ValueBuilder; import static com.google.common.base.Preconditions.checkNotNull; +import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.completeTypeVariable; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createJavaClassList; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createSource; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createThrowsClause; @@ -510,72 +512,93 @@ JavaStaticInitializer construct(JavaStaticInitializerBuilder builder, ClassesByT } } - private interface JavaTypeBuilder { - JavaType build(ClassesByTypeName classes); + interface JavaTypeCreationProcess { + JavaType finish(Iterable allTypeParametersInContext, ClassesByTypeName classes); } @Internal - public static final class JavaTypeVariableBuilder implements JavaTypeBuilder { + public static final class JavaTypeParameterBuilder { private final String name; - private final List upperBounds = new ArrayList<>(); + private final List upperBounds = new ArrayList<>(); private ClassesByTypeName importedClasses; - JavaTypeVariableBuilder(String name) { + JavaTypeParameterBuilder(String name) { this.name = checkNotNull(name); } - void addBound(JavaParameterizedTypeBuilder builder) { - upperBounds.add(builder); + void addBound(JavaTypeCreationProcess bound) { + upperBounds.add(bound); } - @Override public JavaTypeVariable build(ClassesByTypeName importedClasses) { this.importedClasses = importedClasses; - return createTypeVariable(this); + return createTypeVariable(name, this.importedClasses.get(Object.class.getName())); } - public String getName() { - return name; + public List getUpperBounds(Iterable allGenericParametersOfClass) { + return buildJavaTypes(upperBounds, allGenericParametersOfClass, importedClasses); } + } - public List getUpperBounds() { - return buildJavaTypes(upperBounds, importedClasses); + static class TypeParametersBuilder { + private final Collection typeParameterBuilders; + + TypeParametersBuilder(Collection typeParameterBuilders) { + this.typeParameterBuilders = typeParameterBuilders; } - public JavaClass getUnboundErasureType(List upperBounds) { - return DomainBuilders.getUnboundErasureType(upperBounds, importedClasses); + public List build(ClassesByTypeName classesByTypeName) { + if (typeParameterBuilders.isEmpty()) { + return Collections.emptyList(); + } + + Map typeArgumentsToBuilders = new LinkedHashMap<>(); + for (JavaTypeParameterBuilder builder : typeParameterBuilders) { + typeArgumentsToBuilders.put(builder.build(classesByTypeName), builder); + } + for (Map.Entry typeParameterToBuilder : typeArgumentsToBuilders.entrySet()) { + List upperBounds = typeParameterToBuilder.getValue().getUpperBounds(typeArgumentsToBuilders.keySet()); + completeTypeVariable(typeParameterToBuilder.getKey(), upperBounds); + } + return ImmutableList.copyOf(typeArgumentsToBuilders.keySet()); } } + interface JavaTypeBuilder { + JavaType build(Iterable allTypeParametersInContext, ClassesByTypeName importedClasses); + } + @Internal public static final class JavaWildcardTypeBuilder implements JavaTypeBuilder { - private final List lowerBounds = new ArrayList<>(); - private final List upperBounds = new ArrayList<>(); + private final List lowerBoundCreationProcesses = new ArrayList<>(); + private final List upperBoundCreationProcesses = new ArrayList<>(); + private Iterable allTypeParametersInContext; private ClassesByTypeName importedClasses; JavaWildcardTypeBuilder() { } - public void addLowerBound(JavaTypeBuilder boundBuilder) { - lowerBounds.add(boundBuilder); + public void addLowerBound(JavaTypeCreationProcess boundCreationProcess) { + lowerBoundCreationProcesses.add(boundCreationProcess); } - public void addUpperBound(JavaTypeBuilder boundBuilder) { - upperBounds.add(boundBuilder); + public void addUpperBound(JavaTypeCreationProcess boundCreationProcess) { + upperBoundCreationProcesses.add(boundCreationProcess); } @Override - public JavaWildcardType build(ClassesByTypeName importedClasses) { + public JavaWildcardType build(Iterable allTypeParametersInContext, ClassesByTypeName importedClasses) { + this.allTypeParametersInContext = allTypeParametersInContext; this.importedClasses = importedClasses; return createWildcardType(this); } public List getUpperBounds() { - return buildJavaTypes(upperBounds, importedClasses); + return buildJavaTypes(upperBoundCreationProcesses, allTypeParametersInContext, importedClasses); } public List getLowerBounds() { - return buildJavaTypes(lowerBounds, importedClasses); + return buildJavaTypes(lowerBoundCreationProcesses, allTypeParametersInContext, importedClasses); } public JavaClass getUnboundErasureType(List upperBounds) { @@ -585,36 +608,27 @@ public JavaClass getUnboundErasureType(List upperBounds) { static class JavaParameterizedTypeBuilder implements JavaTypeBuilder { private final JavaClassDescriptor type; - private final List typeArgumentBuilders = new ArrayList<>(); + private final List typeArgumentCreationProcesses = new ArrayList<>(); JavaParameterizedTypeBuilder(JavaClassDescriptor type) { this.type = type; } - void addTypeArgument(JavaClassDescriptor type) { - typeArgumentBuilders.add(new JavaParameterizedTypeBuilder(type)); - } - - public JavaWildcardTypeBuilder addWildcardTypeParameter() { - JavaWildcardTypeBuilder wildcardTypeBuilder = new JavaWildcardTypeBuilder(); - typeArgumentBuilders.add(wildcardTypeBuilder); - return wildcardTypeBuilder; + void addTypeArgument(JavaTypeCreationProcess typeCreationProcess) { + typeArgumentCreationProcesses.add(typeCreationProcess); } @Override - public JavaParameterizedType build(ClassesByTypeName classes) { - ImmutableList.Builder typeArguments = ImmutableList.builder(); - for (JavaTypeBuilder typeArgumentBuilder : typeArgumentBuilders) { - typeArguments.add(typeArgumentBuilder.build(classes)); - } - return new ImportedParameterizedType(classes.get(type.getFullyQualifiedClassName()), typeArguments.build()); + public JavaParameterizedType build(Iterable allTypeParametersInContext, ClassesByTypeName classes) { + List typeArguments = buildJavaTypes(typeArgumentCreationProcesses, allTypeParametersInContext, classes); + return new ImportedParameterizedType(classes.get(type.getFullyQualifiedClassName()), typeArguments); } } - private static List buildJavaTypes(List javaTypeBuilders, ClassesByTypeName importedClasses) { + private static List buildJavaTypes(List typeCreationProcesses, Iterable allGenericParametersInContext, ClassesByTypeName classes) { ImmutableList.Builder result = ImmutableList.builder(); - for (JavaTypeBuilder builder : javaTypeBuilders) { - result.add(builder.build(importedClasses)); + for (JavaTypeCreationProcess typeCreationProcess : typeCreationProcesses) { + result.add(typeCreationProcess.finish(allGenericParametersInContext, classes)); } return result.build(); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java index ea22695a1b..933931660d 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java @@ -412,7 +412,7 @@ interface DeclarationHandler { void onNewClass(String className, Optional superClassName, Set interfaceNames); - void onDeclaredTypeParameters(List typeVariableBuilders); + void onDeclaredTypeParameters(DomainBuilders.TypeParametersBuilder typeParametersBuilder); void onDeclaredField(DomainBuilders.JavaFieldBuilder fieldBuilder); diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java index 5bd4dd9020..abed75b01e 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java @@ -18,9 +18,13 @@ import java.util.ArrayList; import java.util.List; +import com.tngtech.archunit.core.domain.JavaType; +import com.tngtech.archunit.core.domain.JavaTypeVariable; import com.tngtech.archunit.core.importer.DomainBuilders.JavaParameterizedTypeBuilder; -import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeVariableBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeParameterBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaWildcardTypeBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.TypeParametersBuilder; import com.tngtech.archunit.core.importer.JavaClassProcessor.DeclarationHandler; import org.objectweb.asm.signature.SignatureReader; import org.objectweb.asm.signature.SignatureVisitor; @@ -36,12 +40,12 @@ static void parseAsmTypeSignature(String signature, DeclarationHandler declarati JavaTypeVariableProcessor typeVariableProcessor = new JavaTypeVariableProcessor(); new SignatureReader(signature).accept(typeVariableProcessor); - declarationHandler.onDeclaredTypeParameters(typeVariableProcessor.typeVariableBuilders); + declarationHandler.onDeclaredTypeParameters(new TypeParametersBuilder(typeVariableProcessor.typeParameterBuilders)); } private static class JavaTypeVariableProcessor extends SignatureVisitor { - private final List typeVariableBuilders = new ArrayList<>(); - private JavaTypeVariableBuilder currentlyVisiting; + private final List typeParameterBuilders = new ArrayList<>(); + private JavaTypeParameterBuilder currentlyVisiting; JavaTypeVariableProcessor() { super(ASM_API_VERSION); @@ -49,8 +53,8 @@ private static class JavaTypeVariableProcessor extends SignatureVisitor { @Override public void visitFormalTypeParameter(String name) { - currentlyVisiting = new JavaTypeVariableBuilder(name); - typeVariableBuilders.add(currentlyVisiting); + currentlyVisiting = new JavaTypeParameterBuilder(name); + typeParameterBuilders.add(currentlyVisiting); } @Override @@ -69,43 +73,91 @@ private SignatureVisitor visitBound() { @Override public void visitClassType(String internalObjectName) { - bound = new JavaParameterizedTypeBuilder(JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName)); - currentlyVisiting.addBound(bound); + JavaParameterizedTypeBuilder builder = new JavaParameterizedTypeBuilder(JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName)); + this.bound = builder; + currentlyVisiting.addBound(new NewJavaTypeCreationProcess(builder)); } @Override public void visitTypeArgument() { - bound.addWildcardTypeParameter(); + addWildcardTypeArgument(); + } + + @Override + public void visitTypeVariable(String name) { + currentlyVisiting.addBound(new ReferenceCreationProcess(name)); } @Override public SignatureVisitor visitTypeArgument(char wildcard) { if (wildcard == '+') { - final JavaWildcardTypeBuilder javaWildcardTypeBuilder = bound.addWildcardTypeParameter(); + final JavaWildcardTypeBuilder javaWildcardTypeBuilder = addWildcardTypeArgument(); return new SignatureVisitor(ASM_API_VERSION) { @Override public void visitClassType(String internalObjectName) { - javaWildcardTypeBuilder.addUpperBound(new JavaParameterizedTypeBuilder(JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName))); + javaWildcardTypeBuilder.addUpperBound(newParameterizedTypeCreationProcess(internalObjectName)); } }; } if (wildcard == '-') { - final JavaWildcardTypeBuilder javaWildcardTypeBuilder = bound.addWildcardTypeParameter(); + final JavaWildcardTypeBuilder javaWildcardTypeBuilder = addWildcardTypeArgument(); return new SignatureVisitor(ASM_API_VERSION) { @Override public void visitClassType(String internalObjectName) { - javaWildcardTypeBuilder.addLowerBound(new JavaParameterizedTypeBuilder(JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName))); + javaWildcardTypeBuilder.addLowerBound(newParameterizedTypeCreationProcess(internalObjectName)); } }; } return new SignatureVisitor(ASM_API_VERSION) { @Override public void visitClassType(String internalObjectName) { - bound.addTypeArgument(JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName)); + bound.addTypeArgument(newParameterizedTypeCreationProcess(internalObjectName)); } }; } + + private NewJavaTypeCreationProcess newParameterizedTypeCreationProcess(String internalObjectName) { + return new NewJavaTypeCreationProcess(new JavaParameterizedTypeBuilder(JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName))); + } + + private JavaWildcardTypeBuilder addWildcardTypeArgument() { + JavaWildcardTypeBuilder wildcardTypeBuilder = new JavaWildcardTypeBuilder(); + bound.addTypeArgument(new NewJavaTypeCreationProcess(wildcardTypeBuilder)); + return wildcardTypeBuilder; + } }; } } + + private static class NewJavaTypeCreationProcess implements DomainBuilders.JavaTypeCreationProcess { + private final JavaTypeBuilder builder; + + NewJavaTypeCreationProcess(JavaTypeBuilder builder) { + this.builder = builder; + } + + @Override + public JavaType finish(Iterable allTypeParametersInContext, ClassesByTypeName classes) { + return builder.build(allTypeParametersInContext, classes); + } + } + + private static class ReferenceCreationProcess implements DomainBuilders.JavaTypeCreationProcess { + private final String typeVariableName; + + ReferenceCreationProcess(String typeVariableName) { + this.typeVariableName = typeVariableName; + } + + @Override + public JavaType finish(Iterable allTypeParametersInContext, ClassesByTypeName classes) { + for (JavaTypeVariable existingTypeVariable : allTypeParametersInContext) { + if (existingTypeVariable.getName().equals(typeVariableName)) { + return existingTypeVariable; + } + } + // type variables can be missing from the import context -> create a simple unbound type variable since we have no more information + return new JavaTypeParameterBuilder(typeVariableName).build(classes); + } + } } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java index d744020d2d..181175e166 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java @@ -20,6 +20,7 @@ import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.ExpectedConcreteTypeVariable.typeVariable; import static com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.ExpectedConcreteWildcardType.wildcardType; import static com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.parameterizedType; import static com.tngtech.java.junit.dataprovider.DataProviders.$; @@ -298,6 +299,35 @@ class ClassWithMultipleTypeParametersBoundByTypesWithDifferentBounds
{ + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithTypeParameterWithTypeVariableBound.class); + + JavaClass javaClass = classes.get(ClassWithTypeParameterWithTypeVariableBound.class); + + assertThatType(javaClass) + .hasTypeParameter("U").withBoundsMatching(typeVariable("T")); + } + + @Test + public void references_type_variable_bound() { + @SuppressWarnings("unused") + class ClassWithTypeParameterWithTypeVariableBound { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithTypeParameterWithTypeVariableBound.class, String.class); + + JavaClass javaClass = classes.get(ClassWithTypeParameterWithTypeVariableBound.class); + + assertThatType(javaClass) + .hasTypeParameter("U").withBoundsMatching(typeVariable("T").withUpperBounds(String.class)) + .hasTypeParameter("V").withBoundsMatching(typeVariable("T").withUpperBounds(String.class)); + } + @SuppressWarnings("unused") public static class ClassParameterWithSingleTypeParameter { } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java index 3ed9b90f84..e63f955d22 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java @@ -188,6 +188,36 @@ public static ExpectedConcreteWildcardType wildcardType() { } } + public static class ExpectedConcreteTypeVariable implements ExpectedConcreteType { + private final String name; + private List upperBounds; + + private ExpectedConcreteTypeVariable(String name) { + this.name = name; + } + + public ExpectedConcreteTypeVariable withUpperBounds(Class... bounds) { + upperBounds = ImmutableList.copyOf(ExpectedConcreteParameterizedType.wrap(bounds)); + return this; + } + + @Override + public void assertMatchWith(JavaType actual, DescriptionContext context) { + assertThat(actual).as(context.step("JavaType").toString()).isInstanceOf(JavaTypeVariable.class); + JavaTypeVariable actualTypeVariable = (JavaTypeVariable) actual; + assertThat(actualTypeVariable.getName()).as(context.step("type variable name").toString()).isEqualTo(name); + + if (upperBounds != null) { + DescriptionContext newContext = context.describe(actual.getName()).step("bounds").metaInfo().describeUpperBounds(); + assertConcreteTypesMatch(newContext, actualTypeVariable.getUpperBounds(), upperBounds); + } + } + + public static ExpectedConcreteTypeVariable typeVariable(String name) { + return new ExpectedConcreteTypeVariable(name); + } + } + private static class DescriptionContext { private static final String MARKER = "##MARKER##"; private static final String PLACEHOLDER = "_"; @@ -198,7 +228,7 @@ private static class DescriptionContext { private final String joinString; DescriptionContext(String context) { - this(context + MARKER, "assertion", "object", ", "); + this(context + MARKER, "assertion", "", ", "); } private DescriptionContext(String context, String description, String currentElement, String joinString) { @@ -223,7 +253,8 @@ public DescriptionContext describeLowerBounds() { } public DescriptionContext describeElements(int number) { - return new DescriptionContext(context.replace(MARKER, joinedPlaceHolders(number)), description, currentElement, joinString); + String elementsPlaceHolder = number > 0 ? joinedPlaceHolders(number) : "[]"; + return new DescriptionContext(context.replace(MARKER, elementsPlaceHolder), description, currentElement, joinString); } public DescriptionContext describeElement(int index, int totalSize) { @@ -243,6 +274,11 @@ public DescriptionContext step(String description) { return new DescriptionContext(context, description, currentElement, joinString); } + public DescriptionContext metaInfo() { + String newContext = context.replace(MARKER, "{" + MARKER + "}"); + return new DescriptionContext(newContext, description, currentElement, joinString); + } + public DescriptionContext describeTypeParameters() { String newContext = context.replace(MARKER, "<" + MARKER + ">"); String newJoinString = ", "; @@ -251,7 +287,8 @@ public DescriptionContext describeTypeParameters() { @Override public String toString() { - return "\"" + description + "\"[" + currentElement + "] -> " + context.replace(MARKER, ""); + String currentElementInfix = currentElement.isEmpty() ? "" : "[" + currentElement + "]"; + return "\"" + description + "\"" + currentElementInfix + " -> " + context.replace(MARKER, ""); } } } From 3b3e2663bc35e2631c95fc714580011504d93d30 Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Sat, 2 May 2020 18:03:12 +0200 Subject: [PATCH 036/115] support type variables referencing type variables of enclosing classes So far we support signatures like `Foo`, where one type parameter is bound by another type parameter. However it is possible, that type parameters of inner classes refer to type parameters of outer classes, e.g. `class Foo { class Inner {} }`. This means that we not only need all constructed type parameters of the class under construction to finish the type arguments, but also all type parameters of all enclosing classes. Unfortunately enclosing class creation has been too late in the construction process for this, thus I have moved it to an earlier place, namely when type hierarchies are assembled. Also to ensure that the construction of the type parameters occurs in the correct order (after all we need the type parameters of the enclosing class constructed before we can process the type parameters of the inner class) I have added sorting by type name to the processing of imported classes. I realize that this is somewhat implicit to ensure the correct order this way (i.e. rely on the naming, that inner class names start with the outer classes name and thus sorting the class names lexicographically will result in the outer classes being processed first), but I still opted for this solution, because I think the complexity to integrate it into the current code base is low and the solution should not impact performance in a noticeable way. In the central place I added a comment and made the internal API clear by replacing the return type with `Sorted...` (at least to point out that the order is of relevance). Should this be broken in the future, tests will likely notice the problem, even though the symptom might unfortunately just be flaky tests. Signed-off-by: Peter Gafert --- .../domain/DomainObjectCreationContext.java | 6 +- .../archunit/core/domain/JavaClass.java | 5 +- .../archunit/core/domain/JavaClasses.java | 8 +-- .../core/importer/ClassFileImportRecord.java | 8 +-- .../core/importer/ClassGraphCreator.java | 20 ++++--- .../core/importer/DomainBuilders.java | 19 ++++-- .../core/importer/ImportedClasses.java | 31 ++++------ .../ClassFileImporterGenericClassesTest.java | 59 ++++++++++++++++++- .../assertion/JavaTypeVariableAssertion.java | 12 +++- 9 files changed, 125 insertions(+), 43 deletions(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java index 2d6c9b2db6..91c3531215 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java @@ -63,7 +63,7 @@ @Internal public class DomainObjectCreationContext { public static JavaClasses createJavaClasses( - Map selectedClasses, Map allClasses, ImportContext importContext) { + Map selectedClasses, Collection allClasses, ImportContext importContext) { return JavaClasses.of(selectedClasses, allClasses, importContext); } @@ -76,6 +76,10 @@ public static void completeClassHierarchy(JavaClass javaClass, ImportContext imp javaClass.completeClassHierarchyFrom(importContext); } + public static void completeEnclosingClass(JavaClass javaClass, ImportContext importContext) { + javaClass.completeEnclosingClassFrom(importContext); + } + public static void completeTypeParameters(JavaClass javaClass, ImportContext importContext) { javaClass.completeTypeParametersFrom(importContext); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java index 1f917105e5..584ec87d3d 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java @@ -1192,6 +1192,10 @@ private void completeInterfacesFrom(ImportContext context) { } } + void completeEnclosingClassFrom(ImportContext context) { + enclosingClass = context.createEnclosingClass(this); + } + void completeTypeParametersFrom(ImportContext context) { typeParameters = context.createTypeParameters(this); } @@ -1217,7 +1221,6 @@ void completeAnnotations(final ImportContext context) { CompletionProcess completeFrom(ImportContext context) { completeComponentType(context); - enclosingClass = context.createEnclosingClass(this); javaClassDependencies = new JavaClassDependencies(this, context); return new CompletionProcess(); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClasses.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClasses.java index 02fd2029f1..4d72540819 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClasses.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClasses.java @@ -194,11 +194,11 @@ private static JavaPackage getRoot(JavaPackage javaPackage) { } static JavaClasses of( - Map selectedClasses, Map allClasses, ImportContext importContext) { + Map selectedClasses, Collection allClasses, ImportContext importContext) { - CompletionProcess completionProcess = new CompletionProcess(allClasses.values(), importContext); - JavaPackage defaultPackage = JavaPackage.from(allClasses.values()); - for (JavaClass clazz : allClasses.values()) { + CompletionProcess completionProcess = new CompletionProcess(allClasses, importContext); + JavaPackage defaultPackage = JavaPackage.from(allClasses); + for (JavaClass clazz : allClasses) { setPackage(clazz, defaultPackage); completionProcess.completeClass(clazz); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java index 44d1c3d1d8..8cedb78ea5 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java @@ -179,12 +179,12 @@ Set getAccessRecords() { .build(); } - Map getSuperClassNamesBySubClass() { - return superClassNamesByOwner; + Set getAllSuperClassNames() { + return ImmutableSet.copyOf(superClassNamesByOwner.values()); } - SetMultimap getInterfaceNamesBySubInterface() { - return interfaceNamesByOwner; + Set getAllSuperInterfaceNames() { + return ImmutableSet.copyOf(interfaceNamesByOwner.values()); } // NOTE: ASM calls visitInnerClass and visitOuterClass several times, sometimes when the outer class is imported diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java index 186edbc91f..ede2aa5c90 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java @@ -56,6 +56,7 @@ import static com.google.common.collect.Iterables.concat; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.completeClassHierarchy; +import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.completeEnclosingClass; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createJavaClasses; import static com.tngtech.archunit.core.importer.DomainBuilders.BuilderWithBuildParameter.BuildFinisher.build; import static com.tngtech.archunit.core.importer.DomainBuilders.buildAnnotations; @@ -99,7 +100,7 @@ public Set apply(JavaClass input) { JavaClasses complete() { ensureCallTargetsArePresent(); - ensureClassHierarchies(); + ensureClassHierarchiesAndEnclosingClasses(); completeTypeParametersAndMembers(); completeAnnotations(); for (RawAccessRecord.ForField fieldAccessRecord : importRecord.getRawFieldAccessRecords()) { @@ -111,7 +112,7 @@ JavaClasses complete() { for (RawAccessRecord constructorCallRecord : importRecord.getRawConstructorCallRecords()) { tryProcess(constructorCallRecord, AccessRecord.Factory.forConstructorCallRecord(), processedConstructorCallRecords); } - return createJavaClasses(classes.getDirectlyImported(), classes.getAll(), this); + return createJavaClasses(classes.getDirectlyImported(), classes.getAllWithOuterClassesSortedBeforeInnerClasses(), this); } private void ensureCallTargetsArePresent() { @@ -120,19 +121,20 @@ private void ensureCallTargetsArePresent() { } } - private void ensureClassHierarchies() { + private void ensureClassHierarchiesAndEnclosingClasses() { ensureClassesOfHierarchyInContext(); - for (JavaClass javaClass : classes.getAll().values()) { + for (JavaClass javaClass : classes.getAllWithOuterClassesSortedBeforeInnerClasses()) { completeClassHierarchy(javaClass, this); + completeEnclosingClass(javaClass, this); } } private void ensureClassesOfHierarchyInContext() { - for (String superClassName : ImmutableSet.copyOf(importRecord.getSuperClassNamesBySubClass().values())) { + for (String superClassName : importRecord.getAllSuperClassNames()) { resolveInheritance(superClassName, superClassStrategy); } - for (String superInterfaceName : ImmutableSet.copyOf(importRecord.getInterfaceNamesBySubInterface().values())) { + for (String superInterfaceName : importRecord.getAllSuperInterfaceNames()) { resolveInheritance(superInterfaceName, interfaceStrategy); } } @@ -144,14 +146,14 @@ private void resolveInheritance(String currentTypeName, Function createInterfaces(JavaClass owner) { @Override public List createTypeParameters(JavaClass owner) { TypeParametersBuilder typeParametersBuilder = importRecord.getTypeParameterBuildersFor(owner.getName()); - return typeParametersBuilder.build(classes.byTypeName()); + return typeParametersBuilder.build(owner, classes.byTypeName()); } @Override diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java index 9c6348094b..ca8ff9fcd6 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java @@ -64,6 +64,7 @@ import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder.ValueBuilder; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Sets.union; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.completeTypeVariable; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createJavaClassList; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createSource; @@ -535,8 +536,8 @@ public JavaTypeVariable build(ClassesByTypeName importedClasses) { return createTypeVariable(name, this.importedClasses.get(Object.class.getName())); } - public List getUpperBounds(Iterable allGenericParametersOfClass) { - return buildJavaTypes(upperBounds, allGenericParametersOfClass, importedClasses); + public List getUpperBounds(Iterable allGenericParametersInContext) { + return buildJavaTypes(upperBounds, allGenericParametersInContext, importedClasses); } } @@ -547,7 +548,7 @@ static class TypeParametersBuilder { this.typeParameterBuilders = typeParameterBuilders; } - public List build(ClassesByTypeName classesByTypeName) { + public List build(JavaClass owner, ClassesByTypeName classesByTypeName) { if (typeParameterBuilders.isEmpty()) { return Collections.emptyList(); } @@ -556,12 +557,22 @@ public List build(ClassesByTypeName classesByTypeName) { for (JavaTypeParameterBuilder builder : typeParameterBuilders) { typeArgumentsToBuilders.put(builder.build(classesByTypeName), builder); } + Set allGenericParametersInContext = union(allTypeParametersInEnclosingClassesOf(owner), typeArgumentsToBuilders.keySet()); for (Map.Entry typeParameterToBuilder : typeArgumentsToBuilders.entrySet()) { - List upperBounds = typeParameterToBuilder.getValue().getUpperBounds(typeArgumentsToBuilders.keySet()); + List upperBounds = typeParameterToBuilder.getValue().getUpperBounds(allGenericParametersInContext); completeTypeVariable(typeParameterToBuilder.getKey(), upperBounds); } return ImmutableList.copyOf(typeArgumentsToBuilders.keySet()); } + + private Set allTypeParametersInEnclosingClassesOf(JavaClass javaClass) { + Set result = new HashSet<>(); + while (javaClass.getEnclosingClass().isPresent()) { + result.addAll(javaClass.getEnclosingClass().get().getTypeParameters()); + javaClass = javaClass.getEnclosingClass().get(); + } + return result; + } } interface JavaTypeBuilder { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ImportedClasses.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ImportedClasses.java index bf164bc0e4..c87ea0c1b7 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ImportedClasses.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ImportedClasses.java @@ -15,11 +15,13 @@ */ package com.tngtech.archunit.core.importer; +import java.util.Collection; import java.util.HashMap; import java.util.Map; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Sets; import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.domain.JavaClass; @@ -36,11 +38,12 @@ class ImportedClasses { Sets.immutableEnumSet(PUBLIC, ABSTRACT, FINAL); private final ImmutableMap directlyImported; - private final Map additionalClasses = new HashMap<>(); + private final Map allClasses = new HashMap<>(); private final ClassResolver resolver; ImportedClasses(Map directlyImported, ClassResolver resolver) { this.directlyImported = ImmutableMap.copyOf(directlyImported); + allClasses.putAll(directlyImported); this.resolver = resolver; } @@ -49,29 +52,21 @@ Map getDirectlyImported() { } JavaClass getOrResolve(String typeName) { - ensurePresent(typeName); - return directlyImported.containsKey(typeName) ? - directlyImported.get(typeName) : - additionalClasses.get(typeName); - } - - void ensurePresent(String typeName) { - if (!contain(typeName)) { + JavaClass javaClass = allClasses.get(typeName); + if (javaClass == null) { Optional resolved = resolver.tryResolve(typeName); - JavaClass newClass = resolved.isPresent() ? resolved.get() : simpleClassOf(typeName); - additionalClasses.put(typeName, newClass); + javaClass = resolved.isPresent() ? resolved.get() : simpleClassOf(typeName); + allClasses.put(typeName, javaClass); } + return javaClass; } - private boolean contain(String name) { - return directlyImported.containsKey(name) || additionalClasses.containsKey(name); + void ensurePresent(String typeName) { + getOrResolve(typeName); } - Map getAll() { - return ImmutableMap.builder() - .putAll(directlyImported) - .putAll(additionalClasses) - .build(); + Collection getAllWithOuterClassesSortedBeforeInnerClasses() { + return ImmutableSortedMap.copyOf(allClasses).values(); } ClassesByTypeName byTypeName() { diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java index 181175e166..0ec5a06ba9 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java @@ -323,11 +323,68 @@ class ClassWithTypeParameterWithTypeVariableBound { + @SuppressWarnings("InnerClassMayBeStatic") + class SomeInner { + class EvenMoreInnerDeclaringOwn { + class AndEvenMoreInner { + } + } + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithTypeParameterWithInnerClassesWithTypeVariableBound.class, + ClassWithTypeParameterWithInnerClassesWithTypeVariableBound.SomeInner.class, + ClassWithTypeParameterWithInnerClassesWithTypeVariableBound.SomeInner.EvenMoreInnerDeclaringOwn.class, + ClassWithTypeParameterWithInnerClassesWithTypeVariableBound.SomeInner.EvenMoreInnerDeclaringOwn.AndEvenMoreInner.class, + String.class); + + JavaClass javaClass = classes.get(ClassWithTypeParameterWithInnerClassesWithTypeVariableBound.SomeInner.EvenMoreInnerDeclaringOwn.AndEvenMoreInner.class); + + assertThatType(javaClass).hasTypeParameters("MOST_INNER1", "MOST_INNER2") + .hasTypeParameter("MOST_INNER1") + .withBoundsMatching( + typeVariable("T").withUpperBounds(String.class)) + .hasTypeParameter("MOST_INNER2") + .withBoundsMatching( + typeVariable("MORE_INNER2").withUpperBounds( + typeVariable("U").withUpperBounds( + typeVariable("T").withUpperBounds(String.class)))); + } + + @Test + public void creates_new_stub_type_variables_for_type_variables_of_enclosing_classes_that_are_out_of_context() { + @SuppressWarnings("unused") + class ClassWithTypeParameterWithInnerClassesWithTypeVariableBound { + @SuppressWarnings("InnerClassMayBeStatic") + class SomeInner { + class EvenMoreInnerDeclaringOwn { + class AndEvenMoreInner { + } + } + } + } + + JavaClasses classes = new ClassFileImporter().importClasses( + ClassWithTypeParameterWithInnerClassesWithTypeVariableBound.SomeInner.EvenMoreInnerDeclaringOwn.AndEvenMoreInner.class); + + JavaClass javaClass = classes.get(ClassWithTypeParameterWithInnerClassesWithTypeVariableBound.SomeInner.EvenMoreInnerDeclaringOwn.AndEvenMoreInner.class); + + assertThatType(javaClass).hasTypeParameters("MOST_INNER1", "MOST_INNER2") + .hasTypeParameter("MOST_INNER1") + .withBoundsMatching(typeVariable("T").withoutUpperBounds()) + .hasTypeParameter("MOST_INNER2") + .withBoundsMatching(typeVariable("MORE_INNER2").withoutUpperBounds()); + } + @SuppressWarnings("unused") public static class ClassParameterWithSingleTypeParameter { } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java index e63f955d22..0251f80864 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java @@ -20,6 +20,7 @@ import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.archunit.testutil.Assertions.assertThatType; import static com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.ExpectedConcreteWildcardType.wildcardType; +import static java.util.Collections.emptyList; public class JavaTypeVariableAssertion extends AbstractObjectAssert { public JavaTypeVariableAssertion(JavaTypeVariable actual) { @@ -197,7 +198,16 @@ private ExpectedConcreteTypeVariable(String name) { } public ExpectedConcreteTypeVariable withUpperBounds(Class... bounds) { - upperBounds = ImmutableList.copyOf(ExpectedConcreteParameterizedType.wrap(bounds)); + return withUpperBounds(ExpectedConcreteParameterizedType.wrap(bounds)); + } + + public ExpectedConcreteTypeVariable withUpperBounds(ExpectedConcreteType... bounds) { + upperBounds = ImmutableList.copyOf(bounds); + return this; + } + + public ExpectedConcreteTypeVariable withoutUpperBounds() { + upperBounds = emptyList(); return this; } From e3c3ea6df60c62de272e48bc1e534839b7147ee3 Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Sun, 19 Apr 2020 17:22:43 +0200 Subject: [PATCH 037/115] support type variables as bounds of wildcards We can now also import upper and lower bounds of wildcards that are type variables, i.e. within `Foo, V extends List` we now support detecting `? extends T` and `? super T`. Signed-off-by: Peter Gafert --- .../importer/JavaGenericTypeImporter.java | 10 +++ .../ClassFileImporterGenericClassesTest.java | 82 +++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java index abed75b01e..5b3aab690a 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java @@ -97,6 +97,11 @@ public SignatureVisitor visitTypeArgument(char wildcard) { public void visitClassType(String internalObjectName) { javaWildcardTypeBuilder.addUpperBound(newParameterizedTypeCreationProcess(internalObjectName)); } + + @Override + public void visitTypeVariable(String name) { + javaWildcardTypeBuilder.addUpperBound(new ReferenceCreationProcess(name)); + } }; } if (wildcard == '-') { @@ -106,6 +111,11 @@ public void visitClassType(String internalObjectName) { public void visitClassType(String internalObjectName) { javaWildcardTypeBuilder.addLowerBound(newParameterizedTypeCreationProcess(internalObjectName)); } + + @Override + public void visitTypeVariable(String name) { + javaWildcardTypeBuilder.addLowerBound(new ReferenceCreationProcess(name)); + } }; } return new SignatureVisitor(ASM_API_VERSION) { diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java index 0ec5a06ba9..1d1315985d 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java @@ -385,6 +385,88 @@ class AndEvenMoreInner { .withBoundsMatching(typeVariable("MORE_INNER2").withoutUpperBounds()); } + @Test + public void imports_wild_cards_bound_by_type_variables() { + @SuppressWarnings("unused") + class ClassWithWildcardWithTypeVariableBounds, V extends List> { + class Inner> { + class MoreInner, MOST_INNER2 extends List> { + } + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithWildcardWithTypeVariableBounds.class, List.class, String.class); + + JavaClass javaClass = classes.get(ClassWithWildcardWithTypeVariableBounds.class); + + assertThatType(javaClass).hasTypeParameters("T", "U", "V") + .hasTypeParameter("U") + .withBoundsMatching( + parameterizedType(List.class).withWildcardTypeParameterWithUpperBound( + typeVariable("T").withUpperBounds(String.class))) + .hasTypeParameter("V") + .withBoundsMatching( + parameterizedType(List.class).withWildcardTypeParameterWithLowerBound( + typeVariable("T").withUpperBounds(String.class))); + } + + @Test + public void imports_wild_cards_bound_by_type_variables_of_enclosing_classes() { + @SuppressWarnings("unused") + class ClassWithWildcardWithTypeVariableBounds, V extends List> { + class Inner> { + class MoreInner, MOST_INNER2 extends List> { + } + } + } + + JavaClasses classes = new ClassFileImporter().importClasses( + ClassWithWildcardWithTypeVariableBounds.class, + ClassWithWildcardWithTypeVariableBounds.Inner.class, + ClassWithWildcardWithTypeVariableBounds.Inner.MoreInner.class, + List.class, String.class); + + JavaClass javaClass = classes.get(ClassWithWildcardWithTypeVariableBounds.Inner.MoreInner.class); + + assertThatType(javaClass).hasTypeParameters("MOST_INNER1", "MOST_INNER2") + .hasTypeParameter("MOST_INNER1") + .withBoundsMatching( + parameterizedType(List.class).withWildcardTypeParameterWithUpperBound( + typeVariable("T").withUpperBounds(String.class))) + .hasTypeParameter("MOST_INNER2") + .withBoundsMatching( + parameterizedType(List.class).withWildcardTypeParameterWithLowerBound( + typeVariable("V").withUpperBounds( + parameterizedType(List.class).withWildcardTypeParameterWithLowerBound( + typeVariable("T").withUpperBounds(String.class))))); + } + + @Test + public void creates_new_stub_type_variables_for_wildcards_bound_by_type_variables_of_enclosing_classes_that_are_out_of_context() { + @SuppressWarnings("unused") + class ClassWithWildcardWithTypeVariableBounds, V extends List> { + class Inner> { + class MoreInner, MOST_INNER2 extends List> { + } + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithWildcardWithTypeVariableBounds.Inner.MoreInner.class, + List.class, String.class); + + JavaClass javaClass = classes.get(ClassWithWildcardWithTypeVariableBounds.Inner.MoreInner.class); + + assertThatType(javaClass).hasTypeParameters("MOST_INNER1", "MOST_INNER2") + .hasTypeParameter("MOST_INNER1") + .withBoundsMatching( + parameterizedType(List.class).withWildcardTypeParameterWithUpperBound( + typeVariable("T").withoutUpperBounds())) + .hasTypeParameter("MOST_INNER2") + .withBoundsMatching( + parameterizedType(List.class).withWildcardTypeParameterWithLowerBound( + typeVariable("V").withoutUpperBounds())); + } + @SuppressWarnings("unused") public static class ClassParameterWithSingleTypeParameter { } From e0a41e876dd761f7c94994b360119cbd018cb669 Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Sat, 25 Apr 2020 21:18:47 +0200 Subject: [PATCH 038/115] generify type argument processing Reduced the number of visitor objects needed and generalize the design. We will need to apply more recursion to be able to follow the tree of arbitrarily nested types, e.g. `Foo, ?>, ?>, T>`. It will be possible to recursively reuse the type argument processors in the next step. Signed-off-by: Peter Gafert --- .../core/importer/DomainBuilders.java | 14 +- .../importer/JavaGenericTypeImporter.java | 185 +++++++++++------- 2 files changed, 127 insertions(+), 72 deletions(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java index ca8ff9fcd6..fbcf05e208 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java @@ -536,6 +536,10 @@ public JavaTypeVariable build(ClassesByTypeName importedClasses) { return createTypeVariable(name, this.importedClasses.get(Object.class.getName())); } + String getName() { + return name; + } + public List getUpperBounds(Iterable allGenericParametersInContext) { return buildJavaTypes(upperBounds, allGenericParametersInContext, importedClasses); } @@ -589,12 +593,14 @@ public static final class JavaWildcardTypeBuilder implements JavaTypeBuilder { JavaWildcardTypeBuilder() { } - public void addLowerBound(JavaTypeCreationProcess boundCreationProcess) { + public JavaWildcardTypeBuilder addLowerBound(JavaTypeCreationProcess boundCreationProcess) { lowerBoundCreationProcesses.add(boundCreationProcess); + return this; } - public void addUpperBound(JavaTypeCreationProcess boundCreationProcess) { + public JavaWildcardTypeBuilder addUpperBound(JavaTypeCreationProcess boundCreationProcess) { upperBoundCreationProcesses.add(boundCreationProcess); + return this; } @Override @@ -634,6 +640,10 @@ public JavaParameterizedType build(Iterable allTypeParametersI List typeArguments = buildJavaTypes(typeArgumentCreationProcesses, allTypeParametersInContext, classes); return new ImportedParameterizedType(classes.get(type.getFullyQualifiedClassName()), typeArguments); } + + String getTypeName() { + return type.getFullyQualifiedClassName(); + } } private static List buildJavaTypes(List typeCreationProcesses, Iterable allGenericParametersInContext, ClassesByTypeName classes) { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java index 5b3aab690a..a7642a38ab 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java @@ -18,34 +18,42 @@ import java.util.ArrayList; import java.util.List; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.JavaTypeVariable; import com.tngtech.archunit.core.importer.DomainBuilders.JavaParameterizedTypeBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeCreationProcess; import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeParameterBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaWildcardTypeBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.TypeParametersBuilder; import com.tngtech.archunit.core.importer.JavaClassProcessor.DeclarationHandler; import org.objectweb.asm.signature.SignatureReader; import org.objectweb.asm.signature.SignatureVisitor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static com.tngtech.archunit.core.importer.ClassFileProcessor.ASM_API_VERSION; class JavaGenericTypeImporter { + private static final Logger log = LoggerFactory.getLogger(JavaGenericTypeImporter.class); static void parseAsmTypeSignature(String signature, DeclarationHandler declarationHandler) { if (signature == null) { return; } + log.trace("Analyzing signature: {}", signature); + JavaTypeVariableProcessor typeVariableProcessor = new JavaTypeVariableProcessor(); new SignatureReader(signature).accept(typeVariableProcessor); declarationHandler.onDeclaredTypeParameters(new TypeParametersBuilder(typeVariableProcessor.typeParameterBuilders)); } private static class JavaTypeVariableProcessor extends SignatureVisitor { + private static final BoundProcessor boundProcessor = new BoundProcessor(); + private final List typeParameterBuilders = new ArrayList<>(); - private JavaTypeParameterBuilder currentlyVisiting; JavaTypeVariableProcessor() { super(ASM_API_VERSION); @@ -53,93 +61,62 @@ private static class JavaTypeVariableProcessor extends SignatureVisitor { @Override public void visitFormalTypeParameter(String name) { - currentlyVisiting = new JavaTypeParameterBuilder(name); - typeParameterBuilders.add(currentlyVisiting); + log.trace("Encountered type parameter {}", name); + JavaTypeParameterBuilder type = new JavaTypeParameterBuilder(name); + boundProcessor.setCurrentType(type); + typeParameterBuilders.add(type); } @Override public SignatureVisitor visitClassBound() { - return visitBound(); + return boundProcessor; } @Override public SignatureVisitor visitInterfaceBound() { - return visitBound(); + return boundProcessor; } - private SignatureVisitor visitBound() { - return new SignatureVisitor(ASM_API_VERSION) { - private JavaParameterizedTypeBuilder bound; + private static class BoundProcessor extends SignatureVisitor { + private JavaTypeParameterBuilder currentType; + private JavaParameterizedTypeBuilder currentBound; - @Override - public void visitClassType(String internalObjectName) { - JavaParameterizedTypeBuilder builder = new JavaParameterizedTypeBuilder(JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName)); - this.bound = builder; - currentlyVisiting.addBound(new NewJavaTypeCreationProcess(builder)); - } + BoundProcessor() { + super(ASM_API_VERSION); + } - @Override - public void visitTypeArgument() { - addWildcardTypeArgument(); - } + void setCurrentType(JavaTypeParameterBuilder type) { + this.currentType = type; + } - @Override - public void visitTypeVariable(String name) { - currentlyVisiting.addBound(new ReferenceCreationProcess(name)); - } + @Override + public void visitClassType(String internalObjectName) { + JavaClassDescriptor type = JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName); + log.trace("Encountered upper bound for {}: Class type {}", currentType.getName(), type.getFullyQualifiedClassName()); + this.currentBound = new JavaParameterizedTypeBuilder(type); + currentType.addBound(new NewJavaTypeCreationProcess(this.currentBound)); + } - @Override - public SignatureVisitor visitTypeArgument(char wildcard) { - if (wildcard == '+') { - final JavaWildcardTypeBuilder javaWildcardTypeBuilder = addWildcardTypeArgument(); - return new SignatureVisitor(ASM_API_VERSION) { - @Override - public void visitClassType(String internalObjectName) { - javaWildcardTypeBuilder.addUpperBound(newParameterizedTypeCreationProcess(internalObjectName)); - } - - @Override - public void visitTypeVariable(String name) { - javaWildcardTypeBuilder.addUpperBound(new ReferenceCreationProcess(name)); - } - }; - } - if (wildcard == '-') { - final JavaWildcardTypeBuilder javaWildcardTypeBuilder = addWildcardTypeArgument(); - return new SignatureVisitor(ASM_API_VERSION) { - @Override - public void visitClassType(String internalObjectName) { - javaWildcardTypeBuilder.addLowerBound(newParameterizedTypeCreationProcess(internalObjectName)); - } - - @Override - public void visitTypeVariable(String name) { - javaWildcardTypeBuilder.addLowerBound(new ReferenceCreationProcess(name)); - } - }; - } - return new SignatureVisitor(ASM_API_VERSION) { - @Override - public void visitClassType(String internalObjectName) { - bound.addTypeArgument(newParameterizedTypeCreationProcess(internalObjectName)); - } - }; - } + @Override + public void visitTypeArgument() { + log.trace("Encountered wildcard for {}", currentBound.getTypeName()); + currentBound.addTypeArgument(new NewJavaTypeCreationProcess(new JavaWildcardTypeBuilder())); + } - private NewJavaTypeCreationProcess newParameterizedTypeCreationProcess(String internalObjectName) { - return new NewJavaTypeCreationProcess(new JavaParameterizedTypeBuilder(JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName))); - } + @Override + public void visitTypeVariable(String name) { + log.trace("Encountered upper bound for {}: Type variable {}", currentType.getName(), name); + currentType.addBound(new ReferenceCreationProcess(name)); + } - private JavaWildcardTypeBuilder addWildcardTypeArgument() { - JavaWildcardTypeBuilder wildcardTypeBuilder = new JavaWildcardTypeBuilder(); - bound.addTypeArgument(new NewJavaTypeCreationProcess(wildcardTypeBuilder)); - return wildcardTypeBuilder; - } - }; + @Override + public SignatureVisitor visitTypeArgument(char wildcard) { + return TypeArgumentProcessor.create(wildcard, currentBound); + } } } - private static class NewJavaTypeCreationProcess implements DomainBuilders.JavaTypeCreationProcess { + private static class NewJavaTypeCreationProcess implements JavaTypeCreationProcess { private final JavaTypeBuilder builder; NewJavaTypeCreationProcess(JavaTypeBuilder builder) { @@ -152,7 +129,7 @@ public JavaType finish(Iterable allTypeParametersInContext, Cl } } - private static class ReferenceCreationProcess implements DomainBuilders.JavaTypeCreationProcess { + private static class ReferenceCreationProcess implements JavaTypeCreationProcess { private final String typeVariableName; ReferenceCreationProcess(String typeVariableName) { @@ -170,4 +147,72 @@ public JavaType finish(Iterable allTypeParametersInContext, Cl return new JavaTypeParameterBuilder(typeVariableName).build(classes); } } + + private static class TypeArgumentProcessor extends SignatureVisitor { + private final TypeArgumentType typeArgumentType; + private final JavaParameterizedTypeBuilder parameterizedType; + + TypeArgumentProcessor(TypeArgumentType typeArgumentType, JavaParameterizedTypeBuilder parameterizedType) { + super(ASM_API_VERSION); + this.typeArgumentType = typeArgumentType; + this.parameterizedType = parameterizedType; + } + + @Override + public void visitClassType(String internalObjectName) { + JavaClassDescriptor type = JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName); + log.trace("Encountered {} for {}: Class type {}", typeArgumentType.description, parameterizedType.getTypeName(), type.getFullyQualifiedClassName()); + typeArgumentType.addTypeArgumentToBuilder(parameterizedType, new NewJavaTypeCreationProcess(new JavaParameterizedTypeBuilder(type))); + } + + @Override + public void visitTypeVariable(String name) { + log.trace("Encountered {} for {}: Type variable {}", typeArgumentType.description, parameterizedType.getTypeName(), name); + typeArgumentType.addTypeArgumentToBuilder(parameterizedType, new ReferenceCreationProcess(name)); + } + + static TypeArgumentProcessor create(char identifier, JavaParameterizedTypeBuilder parameterizedType) { + switch (identifier) { + case '=': + return new TypeArgumentProcessor(PARAMETERIZED_TYPE, parameterizedType); + case '+': + return new TypeArgumentProcessor(WILDCARD_WITH_UPPER_BOUND, parameterizedType); + case '-': + return new TypeArgumentProcessor(WILDCARD_WITH_LOWER_BOUND, parameterizedType); + default: + throw new IllegalStateException(String.format("Cannot handle asm type argument identifier '%s'", identifier)); + } + } + } + + private abstract static class TypeArgumentType { + private final String description; + + TypeArgumentType(String description) { + this.description = description; + } + + abstract void addTypeArgumentToBuilder(JavaParameterizedTypeBuilder parameterizedType, JavaTypeCreationProcess creationProcess); + } + + private static final TypeArgumentType PARAMETERIZED_TYPE = new TypeArgumentType("type argument") { + @Override + void addTypeArgumentToBuilder(JavaParameterizedTypeBuilder parameterizedType, JavaTypeCreationProcess typeCreationProcess) { + parameterizedType.addTypeArgument(typeCreationProcess); + } + }; + + private static final TypeArgumentType WILDCARD_WITH_UPPER_BOUND = new TypeArgumentType("wildcard with upper bound") { + @Override + void addTypeArgumentToBuilder(JavaParameterizedTypeBuilder parameterizedType, JavaTypeCreationProcess typeCreationProcess) { + parameterizedType.addTypeArgument(new NewJavaTypeCreationProcess(new JavaWildcardTypeBuilder().addUpperBound(typeCreationProcess))); + } + }; + + private static final TypeArgumentType WILDCARD_WITH_LOWER_BOUND = new TypeArgumentType("wildcard with lower bound") { + @Override + void addTypeArgumentToBuilder(JavaParameterizedTypeBuilder parameterizedType, JavaTypeCreationProcess typeCreationProcess) { + parameterizedType.addTypeArgument(new NewJavaTypeCreationProcess(new JavaWildcardTypeBuilder().addLowerBound(typeCreationProcess))); + } + }; } From 60db8b655774ca8db0d1d95adb857e2895dfc44e Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Sun, 26 Apr 2020 20:37:32 +0200 Subject: [PATCH 039/115] support arbitrarily nested type parameters We now traverse the tree of type parameters recursively, thus supporting an arbitrarily nested tree of type parameters. I have thought about reusing the same visitor(s) to traverse the tree to reduce the number of created objects, but decided it is not worth the complexity at the moment. If we create new visitors for each level we have an unmodified clean state once we come back from a nested level without the need to clean up / implement `visitEnd()`. Also in general such nested type signatures are a real corner case, most type signatures have at max one level of nexted bounds, so together with only a small subset of used types even being generic I decided to go for simplicity and safety instead of premature optimization (before we would introduce extra complexity here, we should measure this on some real life code bases if this really provides any measurable performance boost). Signed-off-by: Peter Gafert --- .../importer/JavaGenericTypeImporter.java | 16 ++++- .../ClassFileImporterGenericClassesTest.java | 60 +++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java index a7642a38ab..adc67c323c 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java @@ -152,6 +152,8 @@ private static class TypeArgumentProcessor extends SignatureVisitor { private final TypeArgumentType typeArgumentType; private final JavaParameterizedTypeBuilder parameterizedType; + private JavaParameterizedTypeBuilder currentTypeArgument; + TypeArgumentProcessor(TypeArgumentType typeArgumentType, JavaParameterizedTypeBuilder parameterizedType) { super(ASM_API_VERSION); this.typeArgumentType = typeArgumentType; @@ -162,7 +164,14 @@ private static class TypeArgumentProcessor extends SignatureVisitor { public void visitClassType(String internalObjectName) { JavaClassDescriptor type = JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName); log.trace("Encountered {} for {}: Class type {}", typeArgumentType.description, parameterizedType.getTypeName(), type.getFullyQualifiedClassName()); - typeArgumentType.addTypeArgumentToBuilder(parameterizedType, new NewJavaTypeCreationProcess(new JavaParameterizedTypeBuilder(type))); + currentTypeArgument = new JavaParameterizedTypeBuilder(type); + typeArgumentType.addTypeArgumentToBuilder(parameterizedType, new NewJavaTypeCreationProcess(this.currentTypeArgument)); + } + + @Override + public void visitTypeArgument() { + log.trace("Encountered wildcard for {}", currentTypeArgument.getTypeName()); + currentTypeArgument.addTypeArgument(new NewJavaTypeCreationProcess(new JavaWildcardTypeBuilder())); } @Override @@ -171,6 +180,11 @@ public void visitTypeVariable(String name) { typeArgumentType.addTypeArgumentToBuilder(parameterizedType, new ReferenceCreationProcess(name)); } + @Override + public SignatureVisitor visitTypeArgument(char wildcard) { + return TypeArgumentProcessor.create(wildcard, currentTypeArgument); + } + static TypeArgumentProcessor create(char identifier, JavaParameterizedTypeBuilder parameterizedType) { switch (identifier) { case '=': diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java index 1d1315985d..bc22c962e6 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java @@ -6,6 +6,7 @@ import java.lang.ref.Reference; import java.util.List; import java.util.Map; +import java.util.Set; import com.tngtech.archunit.base.Function; import com.tngtech.archunit.core.domain.JavaClass; @@ -467,6 +468,65 @@ class MoreInner, MOST_INNER2 extends List< typeVariable("V").withoutUpperBounds())); } + @Test + public void imports_complex_type_with_multiple_nested_parameters_with_various_bounds_and_recursive_type_definitions() { + @SuppressWarnings("unused") + class ClassWithComplexTypeParameters< + A extends List & Serializable & Comparable, + B extends A, + C extends Map< + Map.Entry>, + Map>>>>>>>, + SELF extends ClassWithComplexTypeParameters, + D> { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithComplexTypeParameters.class, + List.class, Serializable.class, Comparable.class, Map.class, Map.Entry.class, String.class, Set.class, Iterable.class, Object.class); + + JavaClass javaClass = classes.get(ClassWithComplexTypeParameters.class); + + assertThatType(javaClass) + .hasTypeParameter("A") + .withBoundsMatching( + parameterizedType(List.class).withWildcardTypeParameter(), + parameterizedType(Serializable.class), + parameterizedType(Comparable.class).withTypeArguments(typeVariable("A"))) + .hasTypeParameter("B") + .withBoundsMatching(typeVariable("A")) + .hasTypeParameter("C") + .withBoundsMatching( + parameterizedType(Map.class).withTypeArguments( + parameterizedType(Map.Entry.class).withTypeArguments( + typeVariable("A"), + parameterizedType(Map.Entry.class).withTypeArguments( + parameterizedType(String.class), typeVariable("B"))), + parameterizedType(Map.class).withTypeArguments( + wildcardType().withUpperBound(String.class), + parameterizedType(Map.class).withTypeArguments( + wildcardType().withUpperBound(Serializable.class), + parameterizedType(List.class).withTypeArguments( + parameterizedType(List.class).withWildcardTypeParameterWithUpperBound( + parameterizedType(Set.class).withWildcardTypeParameterWithLowerBound( + parameterizedType(Iterable.class).withWildcardTypeParameterWithLowerBound( + parameterizedType(Map.class).withTypeArguments( + typeVariable("B"), wildcardType()))))) + ) + ) + )) + .hasTypeParameter("SELF") + .withBoundsMatching( + parameterizedType(ClassWithComplexTypeParameters.class).withTypeArguments( + typeVariable("A"), + typeVariable("B"), + typeVariable("C"), + typeVariable("SELF"), + typeVariable("D") + )) + .hasTypeParameter("D").withBoundsMatching(Object.class); + } + @SuppressWarnings("unused") public static class ClassParameterWithSingleTypeParameter { } From 22ff054f791a37df27d565292ae11017f1e52c6c Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Mon, 13 Jul 2020 16:38:30 +0200 Subject: [PATCH 040/115] support concrete array types in generic type signature So far types like `Foo>` were not correctly handled. We now support any arbitrarily nested array types as well, as long as they are concrete array types and not generic array types. Note that by the JLS direct bounds cannot be array types (i.e. `Foo` is not a valid signature). Signed-off-by: Peter Gafert --- .../core/domain/JavaClassDescriptor.java | 25 +++++++++++-- .../importer/JavaGenericTypeImporter.java | 33 +++++++++++++---- .../core/domain/JavaClassDescriptorTest.java | 37 +++++++++++++++++++ .../core/domain/JavaTypeVariableTest.java | 18 +++++++++ .../core/domain/JavaWildcardTypeTest.java | 22 ++++++++++- .../ClassFileImporterGenericClassesTest.java | 34 +++++++++++++++++ 6 files changed, 156 insertions(+), 13 deletions(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDescriptor.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDescriptor.java index a19be58634..080122c4e6 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDescriptor.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDescriptor.java @@ -61,6 +61,8 @@ public interface JavaClassDescriptor { JavaClassDescriptor withSimpleClassName(String simpleName); + JavaClassDescriptor toArrayDescriptor(); + @Internal final class From { private static final LoadingCache descriptorCache = @@ -232,11 +234,11 @@ private ObjectClassDescriptor(String fullName, String simpleName, String package public JavaClassDescriptor withSimpleClassName(String simpleName) { return new ObjectClassDescriptor(getFullyQualifiedClassName(), simpleName, getPackageName()); } - } - private static String createPackage(String fullName) { - int packageEnd = fullName.lastIndexOf('.'); - return packageEnd >= 0 ? fullName.substring(0, packageEnd) : ""; + @Override + public JavaClassDescriptor toArrayDescriptor() { + return From.name(getFullyQualifiedClassName() + "[]"); + } } private static class PrimitiveClassDescriptor extends AbstractClassDescriptor { @@ -259,6 +261,11 @@ public boolean isPrimitive() { public JavaClassDescriptor withSimpleClassName(String simpleName) { throw new UnsupportedOperationException("It should never make sense to override the simple type of a primitive"); } + + @Override + public JavaClassDescriptor toArrayDescriptor() { + return From.name(getFullyQualifiedClassName() + "[]"); + } } private static class ArrayClassDescriptor extends AbstractClassDescriptor { @@ -299,6 +306,16 @@ public Optional tryGetComponentType() { String componentTypeName = canonicalName.substring(0, canonicalName.lastIndexOf("[")); return Optional.of(JavaClassDescriptor.From.name(componentTypeName)); } + + @Override + public JavaClassDescriptor toArrayDescriptor() { + return new ArrayClassDescriptor("[" + getFullyQualifiedClassName()); + } + } + + private static String createPackage(String fullName) { + int packageEnd = fullName.lastIndexOf('.'); + return packageEnd >= 0 ? fullName.substring(0, packageEnd) : ""; } } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java index adc67c323c..5b11cc301f 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java @@ -18,6 +18,8 @@ import java.util.ArrayList; import java.util.List; +import com.google.common.base.Function; +import com.google.common.base.Functions; import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.JavaTypeVariable; @@ -33,6 +35,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static com.google.common.base.Functions.compose; import static com.tngtech.archunit.core.importer.ClassFileProcessor.ASM_API_VERSION; class JavaGenericTypeImporter { @@ -111,7 +114,7 @@ public void visitTypeVariable(String name) { @Override public SignatureVisitor visitTypeArgument(char wildcard) { - return TypeArgumentProcessor.create(wildcard, currentBound); + return TypeArgumentProcessor.create(wildcard, currentBound, Functions.identity()); } } } @@ -149,20 +152,29 @@ public JavaType finish(Iterable allTypeParametersInContext, Cl } private static class TypeArgumentProcessor extends SignatureVisitor { + private static final Function TO_ARRAY_TYPE = new Function() { + @Override + public JavaClassDescriptor apply(JavaClassDescriptor input) { + return input.toArrayDescriptor(); + } + }; + private final TypeArgumentType typeArgumentType; private final JavaParameterizedTypeBuilder parameterizedType; + private final Function typeMapping; private JavaParameterizedTypeBuilder currentTypeArgument; - TypeArgumentProcessor(TypeArgumentType typeArgumentType, JavaParameterizedTypeBuilder parameterizedType) { + TypeArgumentProcessor(TypeArgumentType typeArgumentType, JavaParameterizedTypeBuilder parameterizedType, Function typeMapping) { super(ASM_API_VERSION); this.typeArgumentType = typeArgumentType; this.parameterizedType = parameterizedType; + this.typeMapping = typeMapping; } @Override public void visitClassType(String internalObjectName) { - JavaClassDescriptor type = JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName); + JavaClassDescriptor type = typeMapping.apply(JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName)); log.trace("Encountered {} for {}: Class type {}", typeArgumentType.description, parameterizedType.getTypeName(), type.getFullyQualifiedClassName()); currentTypeArgument = new JavaParameterizedTypeBuilder(type); typeArgumentType.addTypeArgumentToBuilder(parameterizedType, new NewJavaTypeCreationProcess(this.currentTypeArgument)); @@ -182,17 +194,22 @@ public void visitTypeVariable(String name) { @Override public SignatureVisitor visitTypeArgument(char wildcard) { - return TypeArgumentProcessor.create(wildcard, currentTypeArgument); + return TypeArgumentProcessor.create(wildcard, currentTypeArgument, typeMapping); + } + + @Override + public SignatureVisitor visitArrayType() { + return new TypeArgumentProcessor(typeArgumentType, parameterizedType, compose(typeMapping, TO_ARRAY_TYPE)); } - static TypeArgumentProcessor create(char identifier, JavaParameterizedTypeBuilder parameterizedType) { + static TypeArgumentProcessor create(char identifier, JavaParameterizedTypeBuilder parameterizedType, Function typeMapping) { switch (identifier) { case '=': - return new TypeArgumentProcessor(PARAMETERIZED_TYPE, parameterizedType); + return new TypeArgumentProcessor(PARAMETERIZED_TYPE, parameterizedType, typeMapping); case '+': - return new TypeArgumentProcessor(WILDCARD_WITH_UPPER_BOUND, parameterizedType); + return new TypeArgumentProcessor(WILDCARD_WITH_UPPER_BOUND, parameterizedType, typeMapping); case '-': - return new TypeArgumentProcessor(WILDCARD_WITH_LOWER_BOUND, parameterizedType); + return new TypeArgumentProcessor(WILDCARD_WITH_LOWER_BOUND, parameterizedType, typeMapping); default: throw new IllegalStateException(String.format("Cannot handle asm type argument identifier '%s'", identifier)); } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassDescriptorTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassDescriptorTest.java index 99dfdfc574..a1d28bec90 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassDescriptorTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassDescriptorTest.java @@ -121,6 +121,43 @@ public static List> primitives() { .build(); } + @Test + public void convert_object_descriptor_to_array_descriptor() { + JavaClassDescriptor arrayDescriptor = JavaClassDescriptor.From.name(Object.class.getName()).toArrayDescriptor(); + + assertThat(arrayDescriptor.getFullyQualifiedClassName()).isEqualTo(Object[].class.getName()); + assertThat(arrayDescriptor.getSimpleClassName()).isEqualTo(Object[].class.getSimpleName()); + assertThat(arrayDescriptor.getPackageName()).isEqualTo(Object.class.getPackage().getName()); + } + + @Test + public void convert_primitive_descriptor_to_array_descriptor() { + JavaClassDescriptor arrayDescriptor = JavaClassDescriptor.From.name(int.class.getName()).toArrayDescriptor(); + + assertThat(arrayDescriptor.getFullyQualifiedClassName()).isEqualTo(int[].class.getName()); + assertThat(arrayDescriptor.getSimpleClassName()).isEqualTo(int[].class.getSimpleName()); + assertThat(arrayDescriptor.getPackageName()).isEmpty(); + } + + @Test + public void convert_array_descriptor_to_2_dim_array_descriptor() { + JavaClassDescriptor arrayDescriptor = JavaClassDescriptor.From.name(Object[].class.getName()).toArrayDescriptor(); + + assertThat(arrayDescriptor.getFullyQualifiedClassName()).isEqualTo(Object[][].class.getName()); + assertThat(arrayDescriptor.getSimpleClassName()).isEqualTo(Object[][].class.getSimpleName()); + assertThat(arrayDescriptor.getPackageName()).isEqualTo(Object.class.getPackage().getName()); + } + + @Test + public void converts_descriptor_repeatedly_multi_dim_array_descriptor() { + JavaClassDescriptor arrayDescriptor = JavaClassDescriptor.From.name(Object.class.getName()) + .toArrayDescriptor().toArrayDescriptor().toArrayDescriptor(); + + assertThat(arrayDescriptor.getFullyQualifiedClassName()).isEqualTo(Object[][][].class.getName()); + assertThat(arrayDescriptor.getSimpleClassName()).isEqualTo(Object[][][].class.getSimpleName()); + assertThat(arrayDescriptor.getPackageName()).isEqualTo(Object.class.getPackage().getName()); + } + private static List> namesToPrimitive(Class primitiveType) { return ImmutableList.>of( ImmutableList.of(primitiveType.getName(), primitiveType), diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java index 008f64cb93..1a7a762de6 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java @@ -80,6 +80,19 @@ class ClassWithBoundTypeParameterWithMultipleGenericClassAndInterfaceBounds, U extends List, V extends List[][][]>> { + } + + List typeParameters = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithSingleGenericArrayBound.class).getTypeParameters(); + + assertThatType(getTypeArgumentOfFirstBound(typeParameters.get(0)).toErasure()).matches(Object[].class); + assertThatType(getTypeArgumentOfFirstBound(typeParameters.get(1)).toErasure()).matches(String[][].class); + assertThatType(getTypeArgumentOfFirstBound(typeParameters.get(2)).toErasure()).matches(List[][][].class); + } + @Test public void toString_unbounded() { @SuppressWarnings("unused") @@ -119,4 +132,9 @@ class BoundedByMultipleBounds { .contains(JavaTypeVariable.class.getSimpleName()) .contains("NAME extends java.lang.String & java.io.Serializable"); } + + private static JavaType getTypeArgumentOfFirstBound(JavaTypeVariable typeParameter) { + JavaParameterizedType firstBound = (JavaParameterizedType) typeParameter.getBounds().get(0); + return firstBound.getActualTypeArguments().get(0); + } } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java index 731185c169..133cb26926 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java @@ -78,6 +78,22 @@ class ClassWithBoundTypeParameterWithSingleGenericClassBound, U extends List, V extends List[][][]>> { + } + + JavaWildcardType wildcard = importWildcardTypeOf(ClassWithBoundTypeParameterWithSingleGenericArrayBound.class, 0); + assertThatType(wildcard.toErasure()).matches(Object[].class); + + wildcard = importWildcardTypeOf(ClassWithBoundTypeParameterWithSingleGenericArrayBound.class, 1); + assertThatType(wildcard.toErasure()).matches(String[][].class); + + wildcard = importWildcardTypeOf(ClassWithBoundTypeParameterWithSingleGenericArrayBound.class, 2); + assertThatType(wildcard.toErasure()).matches(List[][][].class); + } + @Test public void toString_unbounded() { @SuppressWarnings("unused") @@ -122,7 +138,11 @@ class LowerBounded> { } private JavaWildcardType importWildcardTypeOf(Class clazz) { - JavaType listType = new ClassFileImporter().importClass(clazz).getTypeParameters().get(0) + return importWildcardTypeOf(clazz, 0); + } + + private JavaWildcardType importWildcardTypeOf(Class clazz, int typeParameterIndex) { + JavaType listType = new ClassFileImporter().importClass(clazz).getTypeParameters().get(typeParameterIndex) .getUpperBounds().get(0); JavaType wildcardType = ((JavaParameterizedType) listType).getActualTypeArguments().get(0); return (JavaWildcardType) wildcardType; diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java index bc22c962e6..51d2813e91 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java @@ -527,6 +527,40 @@ class ClassWithComplexTypeParameters< .hasTypeParameter("D").withBoundsMatching(Object.class); } + @Test + public void imports_complex_type_with_multiple_nested_parameters_with_concrete_array_bounds() { + @SuppressWarnings("unused") + class ClassWithComplexTypeParametersWithConcreteArrayBounds< + A extends List, + B extends List, + C extends Map, Serializable[][]>> + > { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithComplexTypeParametersWithConcreteArrayBounds.class, + List.class, Serializable.class, Map.class, String.class); + + JavaClass javaClass = classes.get(ClassWithComplexTypeParametersWithConcreteArrayBounds.class); + + assertThatType(javaClass) + .hasTypeParameter("A") + .withBoundsMatching( + parameterizedType(List.class).withTypeArguments(Serializable[].class)) + .hasTypeParameter("B") + .withBoundsMatching( + parameterizedType(List.class).withWildcardTypeParameterWithUpperBound(Serializable[][].class)) + .hasTypeParameter("C") + .withBoundsMatching( + parameterizedType(Map.class).withTypeArguments( + wildcardType().withLowerBound(String[].class), + parameterizedType(Map.class).withTypeArguments( + parameterizedType(Map.class).withTypeArguments( + wildcardType().withLowerBound(String[][][].class), + wildcardType()), + parameterizedType(Serializable[][].class))) + ); + } + @SuppressWarnings("unused") public static class ClassParameterWithSingleTypeParameter { } From 5ef589513b93569468022ff88b30f761be476f0b Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Tue, 14 Jul 2020 21:11:40 +0200 Subject: [PATCH 041/115] support generic array types in generic type signature We can now handle generic array types like `T[][]` in `Foo>`. Note that a generic array type cannot be a toplevel bound of a generic type, but can only appear as part of the nested type signature. Signed-off-by: Peter Gafert --- .../domain/DomainObjectCreationContext.java | 8 ++ .../core/domain/JavaClassDescriptor.java | 2 +- .../core/domain/JavaGenericArrayType.java | 74 +++++++++++++++ .../archunit/core/domain/JavaType.java | 3 + .../importer/JavaGenericTypeImporter.java | 90 ++++++++++++++++--- .../core/domain/JavaTypeVariableTest.java | 13 +++ .../core/domain/JavaWildcardTypeTest.java | 16 ++++ .../ClassFileImporterGenericClassesTest.java | 49 ++++++++++ .../assertion/JavaTypeVariableAssertion.java | 31 +++++++ 9 files changed, 273 insertions(+), 13 deletions(-) create mode 100644 archunit/src/main/java/com/tngtech/archunit/core/domain/JavaGenericArrayType.java diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java index 91c3531215..49408dd863 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java @@ -48,6 +48,8 @@ import com.tngtech.archunit.core.importer.DomainBuilders.JavaWildcardTypeBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.MethodCallTargetBuilder; +import static com.google.common.base.Preconditions.checkArgument; + /** * Together with {@link DomainBuilders}, this class is the link to create domain objects from the import * context. To make the API clear, we try to keep only those methods public, which are really meant to be used. @@ -160,6 +162,12 @@ public static void completeTypeVariable(JavaTypeVariable variable, List + * E.g. for {@code MyClass>} the upper bound {@code List} + * would have one {@link JavaGenericArrayType} {@code A[]} as its type parameter.
+ * Like its concrete counterpart a {@link JavaGenericArrayType} can be queried for its + * {@link #getComponentType() component type}, which will by definition be a + * {@link JavaTypeVariable} or a {@link JavaGenericArrayType} corresponding to a lower dimensional array. + */ +@PublicAPI(usage = ACCESS) +public final class JavaGenericArrayType implements JavaType { + private final String name; + private final JavaType componentType; + private final JavaClass erasure; + + JavaGenericArrayType(String name, JavaType componentType, JavaClass erasure) { + this.name = checkNotNull(name); + this.componentType = checkNotNull(componentType); + this.erasure = checkNotNull(erasure); + } + + /** + * @return The name of this {@link JavaGenericArrayType}, e.g. for {@code A[]} within + * signature {@code MyClass>} the name would be "A[]" + */ + @Override + @PublicAPI(usage = ACCESS) + public String getName() { + return name; + } + + /** + * @return The component type of this {@link JavaGenericArrayType}, e.g. for {@code A[]} within + * signature {@code MyClass>} the component type would be {@code A}, + * while for {@code A[][]} within {@code MyClass>} the component + * type would be {@code A[]}. + */ + @PublicAPI(usage = ACCESS) + public JavaType getComponentType() { + return componentType; + } + + @Override + @PublicAPI(usage = ACCESS) + public JavaClass toErasure() { + return erasure; + } + + @Override + public String toString() { + return getClass().getSimpleName() + '{' + getName() + '}'; + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java index 2dec6444f6..06b9bc1a0a 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaType.java @@ -30,6 +30,9 @@ public interface JavaType extends HasName { *
  • the class itself, if this type is a {@link JavaClass}
  • *
  • the {@link JavaClass} equivalent to {@link Object}, if this type is an unbound {@link JavaTypeVariable}
  • *
  • the {@link JavaClass} equivalent to the erasure of the left most bound, if this type is a bound {@link JavaTypeVariable}
  • + *
  • if this type is a {@link JavaGenericArrayType}, the erasure will be the {@link JavaClass} + * equivalent to the array type that has the erasure of the generic component type of this type as its component type; + * e.g. take the generic array type {@code T[][]} where {@code T} is unbound, then the erasure will be the array type {@code Object[][]}
  • * */ @PublicAPI(usage = ACCESS) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java index 5b11cc301f..1b0ad6ea62 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java @@ -20,6 +20,7 @@ import com.google.common.base.Function; import com.google.common.base.Functions; +import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.JavaTypeVariable; @@ -36,6 +37,7 @@ import org.slf4j.LoggerFactory; import static com.google.common.base.Functions.compose; +import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createGenericArrayType; import static com.tngtech.archunit.core.importer.ClassFileProcessor.ASM_API_VERSION; class JavaGenericTypeImporter { @@ -109,12 +111,12 @@ public void visitTypeArgument() { @Override public void visitTypeVariable(String name) { log.trace("Encountered upper bound for {}: Type variable {}", currentType.getName(), name); - currentType.addBound(new ReferenceCreationProcess(name)); + currentType.addBound(new ReferenceCreationProcess(name, ReferenceCreationProcess.JavaTypeVariableFinisher.IDENTITY)); } @Override public SignatureVisitor visitTypeArgument(char wildcard) { - return TypeArgumentProcessor.create(wildcard, currentBound, Functions.identity()); + return TypeArgumentProcessor.create(wildcard, currentBound, Functions.identity(), ReferenceCreationProcess.JavaTypeVariableFinisher.IDENTITY); } } } @@ -134,13 +136,19 @@ public JavaType finish(Iterable allTypeParametersInContext, Cl private static class ReferenceCreationProcess implements JavaTypeCreationProcess { private final String typeVariableName; + private final JavaTypeVariableFinisher finisher; - ReferenceCreationProcess(String typeVariableName) { + ReferenceCreationProcess(String typeVariableName, JavaTypeVariableFinisher finisher) { this.typeVariableName = typeVariableName; + this.finisher = finisher; } @Override public JavaType finish(Iterable allTypeParametersInContext, ClassesByTypeName classes) { + return finisher.finish(createTypeVariable(allTypeParametersInContext, classes), classes); + } + + private JavaType createTypeVariable(Iterable allTypeParametersInContext, ClassesByTypeName classes) { for (JavaTypeVariable existingTypeVariable : allTypeParametersInContext) { if (existingTypeVariable.getName().equals(typeVariableName)) { return existingTypeVariable; @@ -149,6 +157,38 @@ public JavaType finish(Iterable allTypeParametersInContext, Cl // type variables can be missing from the import context -> create a simple unbound type variable since we have no more information return new JavaTypeParameterBuilder(typeVariableName).build(classes); } + + abstract static class JavaTypeVariableFinisher { + abstract JavaType finish(JavaType input, ClassesByTypeName classes); + + abstract String getFinishedName(String name); + + JavaTypeVariableFinisher after(final JavaTypeVariableFinisher other) { + return new JavaTypeVariableFinisher() { + @Override + JavaType finish(JavaType input, ClassesByTypeName classes) { + return JavaTypeVariableFinisher.this.finish(other.finish(input, classes), classes); + } + + @Override + String getFinishedName(String name) { + return JavaTypeVariableFinisher.this.getFinishedName(other.getFinishedName(name)); + } + }; + } + + static JavaTypeVariableFinisher IDENTITY = new JavaTypeVariableFinisher() { + @Override + JavaType finish(JavaType input, ClassesByTypeName classes) { + return input; + } + + @Override + String getFinishedName(String name) { + return name; + } + }; + } } private static class TypeArgumentProcessor extends SignatureVisitor { @@ -158,18 +198,37 @@ public JavaClassDescriptor apply(JavaClassDescriptor input) { return input.toArrayDescriptor(); } }; + private static final ReferenceCreationProcess.JavaTypeVariableFinisher GENERIC_ARRAY_CREATOR = new ReferenceCreationProcess.JavaTypeVariableFinisher() { + @Override + public JavaType finish(JavaType componentType, ClassesByTypeName classes) { + JavaClassDescriptor erasureType = JavaClassDescriptor.From.javaClass(componentType.toErasure()).toArrayDescriptor(); + JavaClass erasure = classes.get(erasureType.getFullyQualifiedClassName()); + return createGenericArrayType(componentType, erasure); + } + + @Override + String getFinishedName(String name) { + return name + "[]"; + } + }; private final TypeArgumentType typeArgumentType; private final JavaParameterizedTypeBuilder parameterizedType; private final Function typeMapping; + private final ReferenceCreationProcess.JavaTypeVariableFinisher typeVariableFinisher; private JavaParameterizedTypeBuilder currentTypeArgument; - TypeArgumentProcessor(TypeArgumentType typeArgumentType, JavaParameterizedTypeBuilder parameterizedType, Function typeMapping) { + TypeArgumentProcessor( + TypeArgumentType typeArgumentType, + JavaParameterizedTypeBuilder parameterizedType, + Function typeMapping, + ReferenceCreationProcess.JavaTypeVariableFinisher typeVariableFinisher) { super(ASM_API_VERSION); this.typeArgumentType = typeArgumentType; this.parameterizedType = parameterizedType; this.typeMapping = typeMapping; + this.typeVariableFinisher = typeVariableFinisher; } @Override @@ -188,28 +247,35 @@ public void visitTypeArgument() { @Override public void visitTypeVariable(String name) { - log.trace("Encountered {} for {}: Type variable {}", typeArgumentType.description, parameterizedType.getTypeName(), name); - typeArgumentType.addTypeArgumentToBuilder(parameterizedType, new ReferenceCreationProcess(name)); + if (log.isTraceEnabled()) { + log.trace("Encountered {} for {}: Type variable {}", typeArgumentType.description, parameterizedType.getTypeName(), typeVariableFinisher.getFinishedName(name)); + } + typeArgumentType.addTypeArgumentToBuilder(parameterizedType, new ReferenceCreationProcess(name, typeVariableFinisher)); } @Override public SignatureVisitor visitTypeArgument(char wildcard) { - return TypeArgumentProcessor.create(wildcard, currentTypeArgument, typeMapping); + return TypeArgumentProcessor.create(wildcard, currentTypeArgument, typeMapping, typeVariableFinisher); } @Override public SignatureVisitor visitArrayType() { - return new TypeArgumentProcessor(typeArgumentType, parameterizedType, compose(typeMapping, TO_ARRAY_TYPE)); + return new TypeArgumentProcessor(typeArgumentType, parameterizedType, compose(typeMapping, TO_ARRAY_TYPE), typeVariableFinisher.after(GENERIC_ARRAY_CREATOR)); } - static TypeArgumentProcessor create(char identifier, JavaParameterizedTypeBuilder parameterizedType, Function typeMapping) { + static TypeArgumentProcessor create( + char identifier, + JavaParameterizedTypeBuilder parameterizedType, + Function typeMapping, + ReferenceCreationProcess.JavaTypeVariableFinisher typeVariableFinisher) { + switch (identifier) { case '=': - return new TypeArgumentProcessor(PARAMETERIZED_TYPE, parameterizedType, typeMapping); + return new TypeArgumentProcessor(PARAMETERIZED_TYPE, parameterizedType, typeMapping, typeVariableFinisher); case '+': - return new TypeArgumentProcessor(WILDCARD_WITH_UPPER_BOUND, parameterizedType, typeMapping); + return new TypeArgumentProcessor(WILDCARD_WITH_UPPER_BOUND, parameterizedType, typeMapping, typeVariableFinisher); case '-': - return new TypeArgumentProcessor(WILDCARD_WITH_LOWER_BOUND, parameterizedType, typeMapping); + return new TypeArgumentProcessor(WILDCARD_WITH_LOWER_BOUND, parameterizedType, typeMapping, typeVariableFinisher); default: throw new IllegalStateException(String.format("Cannot handle asm type argument identifier '%s'", identifier)); } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java index 1a7a762de6..4ef9d2eaef 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java @@ -93,6 +93,19 @@ class ClassWithBoundTypeParameterWithSingleGenericArrayBound, T extends List, U extends List, V extends List> { + } + + List typeParameters = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithGenericArrayBounds.class).getTypeParameters(); + + assertThatType(getTypeArgumentOfFirstBound(typeParameters.get(3)).toErasure()).matches(Object[].class); + assertThatType(getTypeArgumentOfFirstBound(typeParameters.get(4)).toErasure()).matches(String[][].class); + assertThatType(getTypeArgumentOfFirstBound(typeParameters.get(5)).toErasure()).matches(List[][][].class); + } + @Test public void toString_unbounded() { @SuppressWarnings("unused") diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java index 133cb26926..5203af7692 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaWildcardTypeTest.java @@ -94,6 +94,22 @@ class ClassWithBoundTypeParameterWithSingleGenericArrayBound, T extends List, U extends List, V extends List> { + } + + JavaWildcardType wildcard = importWildcardTypeOf(ClassWithBoundTypeParameterWithGenericArrayBounds.class, 3); + assertThatType(wildcard.toErasure()).matches(Object[].class); + + wildcard = importWildcardTypeOf(ClassWithBoundTypeParameterWithGenericArrayBounds.class, 4); + assertThatType(wildcard.toErasure()).matches(String[][].class); + + wildcard = importWildcardTypeOf(ClassWithBoundTypeParameterWithGenericArrayBounds.class, 5); + assertThatType(wildcard.toErasure()).matches(List[][][].class); + } + @Test public void toString_unbounded() { @SuppressWarnings("unused") diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java index 51d2813e91..7fd6dc6935 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterGenericClassesTest.java @@ -22,6 +22,7 @@ import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.archunit.testutil.Assertions.assertThatType; import static com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.ExpectedConcreteTypeVariable.typeVariable; +import static com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.ExpectedConcreteTypeVariableArray.typeVariableArray; import static com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.ExpectedConcreteWildcardType.wildcardType; import static com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.parameterizedType; import static com.tngtech.java.junit.dataprovider.DataProviders.$; @@ -561,6 +562,54 @@ class ClassWithComplexTypeParametersWithConcreteArrayBounds< ); } + @Test + public void imports_complex_type_with_multiple_nested_parameters_with_generic_array_bounds() { + @SuppressWarnings("unused") + class ClassWithComplexTypeParametersWithGenericArrayBounds< + X extends Serializable, + Y extends String, + A extends List, + B extends List, + C extends Map, X[][]>> + > { + } + + JavaClasses classes = new ClassFileImporter().importClasses(ClassWithComplexTypeParametersWithGenericArrayBounds.class, + List.class, Serializable.class, Map.class, String.class); + + JavaClass javaClass = classes.get(ClassWithComplexTypeParametersWithGenericArrayBounds.class); + + assertThatType(javaClass) + .hasTypeParameter("A") + .withBoundsMatching( + parameterizedType(List.class).withTypeArguments( + typeVariableArray("X[]").withComponentType(typeVariable("X").withUpperBounds(Serializable.class)))) + .hasTypeParameter("B") + .withBoundsMatching( + parameterizedType(List.class).withWildcardTypeParameterWithUpperBound( + typeVariableArray("X[][]").withComponentType( + typeVariableArray("X[]").withComponentType( + typeVariable("X").withUpperBounds(Serializable.class))))) + .hasTypeParameter("C") + .withBoundsMatching( + parameterizedType(Map.class).withTypeArguments( + wildcardType().withLowerBound( + typeVariableArray("Y[]").withComponentType( + typeVariable("Y").withUpperBounds(String.class))), + parameterizedType(Map.class).withTypeArguments( + parameterizedType(Map.class).withTypeArguments( + wildcardType().withLowerBound( + typeVariableArray("Y[][][]").withComponentType( + typeVariableArray("Y[][]").withComponentType( + typeVariableArray("Y[]").withComponentType( + typeVariable("Y").withUpperBounds(String.class))))), + wildcardType()), + typeVariableArray("X[][]").withComponentType( + typeVariableArray("X[]").withComponentType( + typeVariable("X").withUpperBounds(Serializable.class))))) + ); + } + @SuppressWarnings("unused") public static class ClassParameterWithSingleTypeParameter { } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java index 0251f80864..9d0d146717 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java @@ -7,6 +7,7 @@ import com.google.common.base.Joiner; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; +import com.tngtech.archunit.core.domain.JavaGenericArrayType; import com.tngtech.archunit.core.domain.JavaParameterizedType; import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.JavaTypeVariable; @@ -228,6 +229,36 @@ public static ExpectedConcreteTypeVariable typeVariable(String name) { } } + public static class ExpectedConcreteTypeVariableArray implements ExpectedConcreteType { + private final String name; + private ExpectedConcreteType componentType; + + private ExpectedConcreteTypeVariableArray(String name) { + this.name = name; + } + + public ExpectedConcreteTypeVariableArray withComponentType(ExpectedConcreteType componentType) { + this.componentType = componentType; + return this; + } + + @Override + public void assertMatchWith(JavaType actual, DescriptionContext context) { + assertThat(actual).as(context.step("JavaType").toString()).isInstanceOf(JavaGenericArrayType.class); + JavaGenericArrayType actualArrayType = (JavaGenericArrayType) actual; + assertThat(actualArrayType.getName()).as(context.step("type variable name").toString()).isEqualTo(name); + + if (componentType != null) { + DescriptionContext newContext = context.describe(actual.getName()).step("component type").metaInfo(); + componentType.assertMatchWith(actualArrayType.getComponentType(), newContext); + } + } + + public static ExpectedConcreteTypeVariableArray typeVariableArray(String typeVariableArrayString) { + return new ExpectedConcreteTypeVariableArray(typeVariableArrayString); + } + } + private static class DescriptionContext { private static final String MARKER = "##MARKER##"; private static final String PLACEHOLDER = "_"; From e53525fc8c3a2f7f10161bb64b504dc661197526 Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Sat, 5 Sep 2020 15:32:50 +0200 Subject: [PATCH 042/115] optimize performance of `SourceCodeLocation` Once again surprising, but for sufficiently many classes it has a noticeable performance impact to use `String.format(..)` instead of `StringBuilder` (which constant string concatenation will be compiled to in the byte code for all non-ancient JDK versions I know). Since we create the formatted source code location for every class in the import this scales linear with the number of classes. Signed-off-by: Peter Gafert --- .../com/tngtech/archunit/core/domain/SourceCodeLocation.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/SourceCodeLocation.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/SourceCodeLocation.java index 95252079c7..95d99e76f3 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/SourceCodeLocation.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/SourceCodeLocation.java @@ -40,7 +40,6 @@ */ @PublicAPI(usage = ACCESS) public final class SourceCodeLocation { - private static final String LOCATION_TEMPLATE = "(%s:%d)"; @PublicAPI(usage = ACCESS) public static SourceCodeLocation of(JavaClass sourceClass) { @@ -57,7 +56,7 @@ private static String formatLocation(JavaClass sourceClass, int lineNumber) { ? sourceClass.getSource().get().getFileName() : Optional.absent(); String sourceFileName = recordedSourceFileName.isPresent() ? recordedSourceFileName.get() : guessSourceFileName(sourceClass); - return String.format(LOCATION_TEMPLATE, sourceFileName, lineNumber); + return "(" + sourceFileName + ":" + lineNumber + ")"; } private static String guessSourceFileName(JavaClass location) { From bbfd4d5f29a813b8ec7d9999d7049e1365a6c0b5 Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Sat, 5 Sep 2020 17:13:23 +0200 Subject: [PATCH 043/115] consistently link to latest LTS JLS version where possible To keep the required adjustments of JLS links in Javadoc low, we decided to always link to the latest LTS JLS version where possible, instead of the latest available JLS version. If we need a concept only present in a newer JLS it is okay to simply link to the current JLS. When a new JLS is then released we will update all the links in one shot, including the ones that temporarily had to link to a newer version. Signed-off-by: Peter Gafert --- .../core/domain/AnnotationPropertiesFormatter.java | 2 +- .../com/tngtech/archunit/core/domain/JavaClass.java | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatter.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatter.java index 3ab49b7b1b..2594901541 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatter.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/AnnotationPropertiesFormatter.java @@ -134,7 +134,7 @@ public String apply(String input) { /** * Configures that the identifier is omitted if the annotation is a - * single-element annotation + * single-element annotation * and the identifier of the only element is "value". * *