From 1aa40284a9a390afff0bb56840f52d0cbf7ec805 Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Sun, 29 May 2022 14:18:47 +0700 Subject: [PATCH 1/3] make `Formatters.formatNamesOf(..)` compatible with bound types At the moment if there is some sort of `Collection>` instead of `Collection>` then `Formatters.formatNamesOf(collection)` does not compile, because `Class` counts as subtype of `Class`. Thus, we change the signature to allow `? extends Class` to make this work out of the box with bounded types. Signed-off-by: Peter Gafert --- .../java/com/tngtech/archunit/core/domain/Formatters.java | 2 +- .../com/tngtech/archunit/core/domain/FormattersTest.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/Formatters.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/Formatters.java index e418507f3f..9949b3aeb6 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/Formatters.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/Formatters.java @@ -89,7 +89,7 @@ public static List formatNamesOf(Class... paramTypes) { * @return A {@link List} of fully qualified class names of the passed {@link Class} objects */ @PublicAPI(usage = ACCESS) - public static List formatNamesOf(Iterable> paramTypes) { + public static List formatNamesOf(Iterable> paramTypes) { ImmutableList.Builder result = ImmutableList.builder(); for (Class paramType : paramTypes) { result.add(paramType.getName()); diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/FormattersTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/FormattersTest.java index 87e4c03ec1..f83c8b512a 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/FormattersTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/FormattersTest.java @@ -1,5 +1,6 @@ package com.tngtech.archunit.core.domain; +import java.io.Serializable; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collections; @@ -41,6 +42,10 @@ public void formatNamesOf() { assertThat(Formatters.formatNamesOf(of(List.class, Iterable.class, String.class))) .containsExactly(List.class.getName(), Iterable.class.getName(), String.class.getName()); + + // special case because the inferred type of the list is List> + assertThat(Formatters.formatNamesOf(of(String.class, Serializable.class))) + .containsExactly(String.class.getName(), Serializable.class.getName()); } @Test From 88e7ec501d0577a3c66d37f3d7838366740a7d70 Mon Sep 17 00:00:00 2001 From: Peter Gafert Date: Sun, 13 Mar 2022 17:11:50 +0700 Subject: [PATCH 2/3] move inner assertion classes out of `..testutil.Assertions` It is more consistent if all concrete assertion classes reside in `testutil.assertion`, instead of just some of them. Signed-off-by: Peter Gafert --- .../tngtech/archunit/testutil/Assertions.java | 284 +----------------- .../assertion/AccessToFieldAssertion.java | 38 +++ .../testutil/assertion/AccessesAssertion.java | 38 +++ .../assertion/BaseAccessAssertion.java | 53 ++++ .../assertion/CodeUnitAccessAssertion.java | 52 ++++ .../assertion/ExpectedAccessCreation.java | 67 +++++ .../assertion/JavaEnumConstantAssertion.java | 28 ++ .../assertion/JavaEnumConstantsAssertion.java | 19 ++ .../assertion/ThrowsClauseAssertion.java | 24 ++ .../assertion/ThrowsDeclarationAssertion.java | 16 + 10 files changed, 343 insertions(+), 276 deletions(-) create mode 100644 archunit/src/test/java/com/tngtech/archunit/testutil/assertion/AccessToFieldAssertion.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/testutil/assertion/AccessesAssertion.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/testutil/assertion/BaseAccessAssertion.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/testutil/assertion/CodeUnitAccessAssertion.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ExpectedAccessCreation.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaEnumConstantAssertion.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaEnumConstantsAssertion.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ThrowsClauseAssertion.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ThrowsDeclarationAssertion.java 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 f33d93588f..88144d9da5 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java @@ -1,18 +1,10 @@ package com.tngtech.archunit.testutil; import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; import java.util.Set; -import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; import com.tngtech.archunit.base.DescribedPredicate; -import com.tngtech.archunit.core.domain.AccessTarget; -import com.tngtech.archunit.core.domain.AccessTarget.CodeUnitAccessTarget; -import com.tngtech.archunit.core.domain.AccessTarget.ConstructorCallTarget; import com.tngtech.archunit.core.domain.AccessTarget.FieldAccessTarget; import com.tngtech.archunit.core.domain.Dependency; import com.tngtech.archunit.core.domain.JavaAccess; @@ -35,22 +27,27 @@ import com.tngtech.archunit.core.domain.ReferencedClassObject; import com.tngtech.archunit.core.domain.ThrowsClause; import com.tngtech.archunit.core.domain.ThrowsDeclaration; -import com.tngtech.archunit.core.domain.properties.HasName; import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.testutil.assertion.AccessToFieldAssertion; +import com.tngtech.archunit.testutil.assertion.AccessesAssertion; import com.tngtech.archunit.testutil.assertion.ArchConditionAssertion; import com.tngtech.archunit.testutil.assertion.ArchRuleAssertion; +import com.tngtech.archunit.testutil.assertion.CodeUnitAccessAssertion; import com.tngtech.archunit.testutil.assertion.ConditionEventsAssertion; 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.ExpectedAccessCreation; import com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion; import com.tngtech.archunit.testutil.assertion.JavaAnnotationsAssertion; import com.tngtech.archunit.testutil.assertion.JavaClassAssertion; 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.JavaEnumConstantAssertion; +import com.tngtech.archunit.testutil.assertion.JavaEnumConstantsAssertion; import com.tngtech.archunit.testutil.assertion.JavaFieldAssertion; import com.tngtech.archunit.testutil.assertion.JavaFieldsAssertion; import com.tngtech.archunit.testutil.assertion.JavaMemberAssertion; @@ -62,14 +59,8 @@ import com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion; import com.tngtech.archunit.testutil.assertion.JavaTypesAssertion; import com.tngtech.archunit.testutil.assertion.ReferencedClassObjectsAssertion; -import org.assertj.core.api.AbstractObjectAssert; -import org.assertj.core.api.Condition; - -import static com.google.common.base.Strings.emptyToNull; -import static com.tngtech.archunit.core.domain.Formatters.formatMethodSimple; -import static com.tngtech.archunit.core.domain.Formatters.formatNamesOf; -import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; -import static com.tngtech.archunit.core.domain.TestUtils.targetFrom; +import com.tngtech.archunit.testutil.assertion.ThrowsClauseAssertion; +import com.tngtech.archunit.testutil.assertion.ThrowsDeclarationAssertion; public class Assertions extends org.assertj.core.api.Assertions { public static ArchConditionAssertion assertThat(ArchCondition archCondition) { @@ -209,61 +200,6 @@ public static ExpectedAccessCreation expectedAccess() { return new ExpectedAccessCreation(); } - public static class ExpectedAccessCreation { - private ExpectedAccessCreation() { - } - - public Step2 from(Class originClass, String codeUnitName) { - return new Step2(originClass, codeUnitName); - } - - public static class Step2 { - private final Class originClass; - private final String originCodeUnitName; - - private Step2(Class originClass, String originCodeUnitName) { - this.originClass = originClass; - this.originCodeUnitName = originCodeUnitName; - } - - public Condition> to(final Class targetClass, final String targetName) { - return new Condition>( - String.format("%s from %s.%s to %s.%s", - JavaAccess.class.getSimpleName(), - originClass.getName(), originCodeUnitName, - targetClass.getSimpleName(), targetName)) { - @Override - public boolean matches(JavaAccess access) { - return access.getOriginOwner().isEquivalentTo(originClass) && - access.getOrigin().getName().equals(originCodeUnitName) && - access.getTargetOwner().isEquivalentTo(targetClass) && - access.getTarget().getName().equals(targetName); - } - }; - } - - public Condition> toConstructor(final Class targetClass, final Class... paramTypes) { - final List paramTypeNames = formatNamesOf(paramTypes); - return new Condition>( - String.format("%s from %s.%s to %s", - JavaAccess.class.getSimpleName(), - originClass.getName(), originCodeUnitName, - formatMethodSimple(targetClass.getSimpleName(), CONSTRUCTOR_NAME, paramTypeNames))) { - @Override - public boolean matches(JavaAccess access) { - return to(targetClass, CONSTRUCTOR_NAME).matches(access) && - rawParameterTypeNamesOf(access).equals(paramTypeNames); - } - - private List rawParameterTypeNamesOf(JavaAccess access) { - ConstructorCallTarget target = (ConstructorCallTarget) access.getTarget(); - return HasName.Utils.namesOf(target.getRawParameterTypes()); - } - }; - } - } - } - public static AccessToFieldAssertion assertThatAccess(JavaFieldAccess access) { return new AccessToFieldAssertion(access); } @@ -279,208 +215,4 @@ public static CodeUnitAccessAssertion assertThatCall(JavaMethodCall call) { public static CodeUnitAccessAssertion assertThatCall(JavaConstructorCall call) { return assertThatAccess(call); } - - public static class JavaEnumConstantAssertion extends AbstractObjectAssert { - private JavaEnumConstantAssertion(JavaEnumConstant enumConstant) { - super(enumConstant, JavaEnumConstantAssertion.class); - } - - public void isEquivalentTo(Enum enumConstant) { - assertThat(actual).as(describePartialAssertion()).isNotNull(); - assertThat(actual.getDeclaringClass().getName()).as(describePartialAssertion("type")).isEqualTo(enumConstant.getDeclaringClass().getName()); - assertThat(actual.name()).as(describePartialAssertion("name")).isEqualTo(enumConstant.name()); - } - - private String describePartialAssertion() { - return describePartialAssertion(""); - } - - private String describePartialAssertion(String partialAssertionDescription) { - return Joiner.on(": ").skipNulls().join(emptyToNull(descriptionText()), emptyToNull(partialAssertionDescription)); - } - } - - public static class JavaEnumConstantsAssertion extends AbstractObjectAssert { - private JavaEnumConstantsAssertion(JavaEnumConstant[] enumConstants) { - super(enumConstants, JavaEnumConstantsAssertion.class); - } - - public void matches(Enum... enumConstants) { - assertThat((Object[]) actual).as("Enum constants").hasSize(enumConstants.length); - for (int i = 0; i < actual.length; i++) { - assertThat(actual[i]).as("Element %d", i).isEquivalentTo(enumConstants[i]); - } - } - } - - public static class ThrowsDeclarationAssertion extends AbstractObjectAssert> { - private ThrowsDeclarationAssertion(ThrowsDeclaration throwsDeclaration) { - super(throwsDeclaration, ThrowsDeclarationAssertion.class); - } - - public void matches(Class clazz) { - assertThatType(actual.getRawType()).as("Type of " + actual) - .matches(clazz); - } - } - - public static class ThrowsClauseAssertion extends AbstractObjectAssert> { - private ThrowsClauseAssertion(ThrowsClause throwsClause) { - super(throwsClause, ThrowsClauseAssertion.class); - } - - public void matches(Class... classes) { - assertThat(actual).as("ThrowsClause").hasSize(classes.length); - for (int i = 0; i < actual.size(); i++) { - assertThat(Iterables.get(actual, i)).as("Element %d", i).matches(classes[i]); - } - } - - public void isEmpty() { - assertThat(actual).as("ThrowsClause").isEmpty(); - } - } - - public static class AccessesAssertion { - private final Set> actualRemaining; - - AccessesAssertion(Collection> accesses) { - this.actualRemaining = new HashSet<>(accesses); - } - - public AccessesAssertion contain(Condition> condition) { - for (Iterator> iterator = actualRemaining.iterator(); iterator.hasNext(); ) { - if (condition.matches(iterator.next())) { - iterator.remove(); - return this; - } - } - throw new AssertionError("No access matches " + condition); - } - - @SafeVarargs - public final AccessesAssertion containOnly(Condition>... conditions) { - for (Condition> condition : conditions) { - contain(condition); - } - assertThat(actualRemaining).as("Unexpected " + JavaAccess.class.getSimpleName()).isEmpty(); - return this; - } - } - - protected abstract static class BaseAccessAssertion< - SELF extends BaseAccessAssertion, - ACCESS extends JavaAccess, - TARGET extends AccessTarget> { - - ACCESS access; - - BaseAccessAssertion(ACCESS access) { - this.access = access; - } - - public SELF isFrom(String name, Class... parameterTypes) { - return isFrom(access.getOrigin().getOwner().getCodeUnitWithParameterTypes(name, parameterTypes)); - } - - public SELF isFrom(Class originClass, String name, Class... parameterTypes) { - assertThatType(access.getOriginOwner()).matches(originClass); - return isFrom(name, parameterTypes); - } - - public SELF isFrom(JavaCodeUnit codeUnit) { - assertThat(access.getOrigin()).as("Origin of access").isEqualTo(codeUnit); - return newAssertion(access); - } - - public SELF isTo(TARGET target) { - assertThat(access.getTarget()).as("Target of " + access.getName()).isEqualTo(target); - return newAssertion(access); - } - - public SELF isTo(Condition target) { - assertThat(access.getTarget()).as("Target of " + access.getName()).is(target); - return newAssertion(access); - } - - public void inLineNumber(int number) { - assertThat(access.getLineNumber()) - .as("Line number of access to " + access.getName()) - .isEqualTo(number); - } - - protected abstract SELF newAssertion(ACCESS access); - } - - public static class AccessToFieldAssertion extends BaseAccessAssertion { - private AccessToFieldAssertion(JavaFieldAccess access) { - super(access); - } - - @Override - protected AccessToFieldAssertion newAssertion(JavaFieldAccess access) { - return new AccessToFieldAssertion(access); - } - - public AccessToFieldAssertion isTo(final String name) { - return isTo(new Condition("field with name '" + name + "'") { - @Override - public boolean matches(FieldAccessTarget fieldAccessTarget) { - return fieldAccessTarget.getName().equals(name); - } - }); - } - - public AccessToFieldAssertion isTo(JavaField field) { - return isTo(targetFrom(field)); - } - - public AccessToFieldAssertion isOfType(JavaFieldAccess.AccessType type) { - assertThat(access.getAccessType()).isEqualTo(type); - return newAssertion(access); - } - } - - public static class CodeUnitAccessAssertion - extends BaseAccessAssertion, CodeUnitAccessTarget> { - @SuppressWarnings({"rawtypes", "unchecked"}) - private CodeUnitAccessAssertion(JavaCodeUnitAccess call) { - super((JavaCodeUnitAccess) call); - } - - public CodeUnitAccessAssertion isTo(final JavaCodeUnit target) { - return isTo(new Condition("method " + target.getFullName()) { - @Override - public boolean matches(CodeUnitAccessTarget codeUnitAccessTarget) { - return codeUnitAccessTarget.getOwner().equals(target.getOwner()) - && codeUnitAccessTarget.getName().equals(target.getName()) - && codeUnitAccessTarget.getRawParameterTypes().equals(target.getRawParameterTypes()); - } - }); - } - - public CodeUnitAccessAssertion isTo(final Class codeUnitOwner) { - return isTo(new Condition() { - @Override - public boolean matches(CodeUnitAccessTarget target) { - return target.getOwner().isEquivalentTo(codeUnitOwner); - } - }); - } - - public CodeUnitAccessAssertion isTo(final String codeUnitName, final Class... parameterTypes) { - return isTo(new Condition("code unit " + codeUnitName + "(" + formatNamesOf(parameterTypes) + ")") { - @Override - public boolean matches(CodeUnitAccessTarget target) { - return target.getName().equals(codeUnitName) - && HasName.Utils.namesOf(target.getRawParameterTypes()).equals(formatNamesOf(parameterTypes)); - } - }); - } - - @Override - protected CodeUnitAccessAssertion newAssertion(JavaCodeUnitAccess reference) { - return new CodeUnitAccessAssertion(reference); - } - } } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/AccessToFieldAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/AccessToFieldAssertion.java new file mode 100644 index 0000000000..1912003180 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/AccessToFieldAssertion.java @@ -0,0 +1,38 @@ +package com.tngtech.archunit.testutil.assertion; + +import com.tngtech.archunit.core.domain.AccessTarget; +import com.tngtech.archunit.core.domain.JavaField; +import com.tngtech.archunit.core.domain.JavaFieldAccess; +import org.assertj.core.api.Condition; + +import static com.tngtech.archunit.core.domain.TestUtils.targetFrom; +import static org.assertj.core.api.Assertions.assertThat; + +public class AccessToFieldAssertion extends BaseAccessAssertion { + public AccessToFieldAssertion(JavaFieldAccess access) { + super(access); + } + + @Override + protected AccessToFieldAssertion newAssertion(JavaFieldAccess access) { + return new AccessToFieldAssertion(access); + } + + public AccessToFieldAssertion isTo(final String name) { + return isTo(new Condition("field with name '" + name + "'") { + @Override + public boolean matches(AccessTarget.FieldAccessTarget fieldAccessTarget) { + return fieldAccessTarget.getName().equals(name); + } + }); + } + + public AccessToFieldAssertion isTo(JavaField field) { + return isTo(targetFrom(field)); + } + + public AccessToFieldAssertion isOfType(JavaFieldAccess.AccessType type) { + assertThat(access.getAccessType()).isEqualTo(type); + return newAssertion(access); + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/AccessesAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/AccessesAssertion.java new file mode 100644 index 0000000000..08f02ff4d9 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/AccessesAssertion.java @@ -0,0 +1,38 @@ +package com.tngtech.archunit.testutil.assertion; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import com.tngtech.archunit.core.domain.JavaAccess; +import org.assertj.core.api.Condition; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AccessesAssertion { + private final Set> actualRemaining; + + public AccessesAssertion(Collection> accesses) { + this.actualRemaining = new HashSet<>(accesses); + } + + public AccessesAssertion contain(Condition> condition) { + for (Iterator> iterator = actualRemaining.iterator(); iterator.hasNext(); ) { + if (condition.matches(iterator.next())) { + iterator.remove(); + return this; + } + } + throw new AssertionError("No access matches " + condition); + } + + @SafeVarargs + public final AccessesAssertion containOnly(Condition>... conditions) { + for (Condition> condition : conditions) { + contain(condition); + } + assertThat(actualRemaining).as("Unexpected " + JavaAccess.class.getSimpleName()).isEmpty(); + return this; + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/BaseAccessAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/BaseAccessAssertion.java new file mode 100644 index 0000000000..b54874e7b0 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/BaseAccessAssertion.java @@ -0,0 +1,53 @@ +package com.tngtech.archunit.testutil.assertion; + +import com.tngtech.archunit.core.domain.AccessTarget; +import com.tngtech.archunit.core.domain.JavaAccess; +import com.tngtech.archunit.core.domain.JavaCodeUnit; +import org.assertj.core.api.Condition; + +import static com.tngtech.archunit.testutil.Assertions.assertThatType; +import static org.assertj.core.api.Assertions.assertThat; + +abstract class BaseAccessAssertion< + SELF extends BaseAccessAssertion, + ACCESS extends JavaAccess, + TARGET extends AccessTarget> { + + ACCESS access; + + BaseAccessAssertion(ACCESS access) { + this.access = access; + } + + public SELF isFrom(String name, Class... parameterTypes) { + return isFrom(access.getOrigin().getOwner().getCodeUnitWithParameterTypes(name, parameterTypes)); + } + + public SELF isFrom(Class originClass, String name, Class... parameterTypes) { + assertThatType(access.getOriginOwner()).matches(originClass); + return isFrom(name, parameterTypes); + } + + public SELF isFrom(JavaCodeUnit codeUnit) { + assertThat(access.getOrigin()).as("Origin of access").isEqualTo(codeUnit); + return newAssertion(access); + } + + public SELF isTo(TARGET target) { + assertThat(access.getTarget()).as("Target of " + access.getName()).isEqualTo(target); + return newAssertion(access); + } + + public SELF isTo(Condition target) { + assertThat(access.getTarget()).as("Target of " + access.getName()).is(target); + return newAssertion(access); + } + + public void inLineNumber(int number) { + assertThat(access.getLineNumber()) + .as("Line number of access to " + access.getName()) + .isEqualTo(number); + } + + protected abstract SELF newAssertion(ACCESS access); +} diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/CodeUnitAccessAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/CodeUnitAccessAssertion.java new file mode 100644 index 0000000000..31b6eda2ef --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/CodeUnitAccessAssertion.java @@ -0,0 +1,52 @@ +package com.tngtech.archunit.testutil.assertion; + +import com.tngtech.archunit.core.domain.AccessTarget; +import com.tngtech.archunit.core.domain.JavaCodeUnit; +import com.tngtech.archunit.core.domain.JavaCodeUnitAccess; +import com.tngtech.archunit.core.domain.properties.HasName; +import org.assertj.core.api.Condition; + +import static com.tngtech.archunit.core.domain.Formatters.formatNamesOf; + +public class CodeUnitAccessAssertion + extends BaseAccessAssertion, AccessTarget.CodeUnitAccessTarget> { + @SuppressWarnings({"rawtypes", "unchecked"}) + public CodeUnitAccessAssertion(JavaCodeUnitAccess call) { + super((JavaCodeUnitAccess) call); + } + + public CodeUnitAccessAssertion isTo(final JavaCodeUnit target) { + return isTo(new Condition("method " + target.getFullName()) { + @Override + public boolean matches(AccessTarget.CodeUnitAccessTarget codeUnitAccessTarget) { + return codeUnitAccessTarget.getOwner().equals(target.getOwner()) + && codeUnitAccessTarget.getName().equals(target.getName()) + && codeUnitAccessTarget.getRawParameterTypes().equals(target.getRawParameterTypes()); + } + }); + } + + public CodeUnitAccessAssertion isTo(final Class codeUnitOwner) { + return isTo(new Condition() { + @Override + public boolean matches(AccessTarget.CodeUnitAccessTarget target) { + return target.getOwner().isEquivalentTo(codeUnitOwner); + } + }); + } + + public CodeUnitAccessAssertion isTo(final String codeUnitName, final Class... parameterTypes) { + return isTo(new Condition("code unit " + codeUnitName + "(" + formatNamesOf(parameterTypes) + ")") { + @Override + public boolean matches(AccessTarget.CodeUnitAccessTarget target) { + return target.getName().equals(codeUnitName) + && HasName.Utils.namesOf(target.getRawParameterTypes()).equals(formatNamesOf(parameterTypes)); + } + }); + } + + @Override + protected CodeUnitAccessAssertion newAssertion(JavaCodeUnitAccess reference) { + return new CodeUnitAccessAssertion(reference); + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ExpectedAccessCreation.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ExpectedAccessCreation.java new file mode 100644 index 0000000000..83cc948cf1 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ExpectedAccessCreation.java @@ -0,0 +1,67 @@ +package com.tngtech.archunit.testutil.assertion; + +import java.util.List; + +import com.tngtech.archunit.core.domain.AccessTarget; +import com.tngtech.archunit.core.domain.JavaAccess; +import com.tngtech.archunit.core.domain.properties.HasName; +import org.assertj.core.api.Condition; + +import static com.tngtech.archunit.core.domain.Formatters.formatMethodSimple; +import static com.tngtech.archunit.core.domain.Formatters.formatNamesOf; +import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; + +public class ExpectedAccessCreation { + public ExpectedAccessCreation() { + } + + public Step2 from(Class originClass, String codeUnitName) { + return new Step2(originClass, codeUnitName); + } + + public static class Step2 { + private final Class originClass; + private final String originCodeUnitName; + + private Step2(Class originClass, String originCodeUnitName) { + this.originClass = originClass; + this.originCodeUnitName = originCodeUnitName; + } + + public Condition> to(final Class targetClass, final String targetName) { + return new Condition>( + String.format("%s from %s.%s to %s.%s", + JavaAccess.class.getSimpleName(), + originClass.getName(), originCodeUnitName, + targetClass.getSimpleName(), targetName)) { + @Override + public boolean matches(JavaAccess access) { + return access.getOriginOwner().isEquivalentTo(originClass) && + access.getOrigin().getName().equals(originCodeUnitName) && + access.getTargetOwner().isEquivalentTo(targetClass) && + access.getTarget().getName().equals(targetName); + } + }; + } + + public Condition> toConstructor(final Class targetClass, final Class... paramTypes) { + final List paramTypeNames = formatNamesOf(paramTypes); + return new Condition>( + String.format("%s from %s.%s to %s", + JavaAccess.class.getSimpleName(), + originClass.getName(), originCodeUnitName, + formatMethodSimple(targetClass.getSimpleName(), CONSTRUCTOR_NAME, paramTypeNames))) { + @Override + public boolean matches(JavaAccess access) { + return to(targetClass, CONSTRUCTOR_NAME).matches(access) && + rawParameterTypeNamesOf(access).equals(paramTypeNames); + } + + private List rawParameterTypeNamesOf(JavaAccess access) { + AccessTarget.ConstructorCallTarget target = (AccessTarget.ConstructorCallTarget) access.getTarget(); + return HasName.Utils.namesOf(target.getRawParameterTypes()); + } + }; + } + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaEnumConstantAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaEnumConstantAssertion.java new file mode 100644 index 0000000000..fdbb7e4d89 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaEnumConstantAssertion.java @@ -0,0 +1,28 @@ +package com.tngtech.archunit.testutil.assertion; + +import com.google.common.base.Joiner; +import com.tngtech.archunit.core.domain.JavaEnumConstant; +import org.assertj.core.api.AbstractObjectAssert; + +import static com.google.common.base.Strings.emptyToNull; +import static org.assertj.core.api.Assertions.assertThat; + +public class JavaEnumConstantAssertion extends AbstractObjectAssert { + public JavaEnumConstantAssertion(JavaEnumConstant enumConstant) { + super(enumConstant, JavaEnumConstantAssertion.class); + } + + public void isEquivalentTo(Enum enumConstant) { + assertThat(actual).as(describePartialAssertion()).isNotNull(); + assertThat(actual.getDeclaringClass().getName()).as(describePartialAssertion("type")).isEqualTo(enumConstant.getDeclaringClass().getName()); + assertThat(actual.name()).as(describePartialAssertion("name")).isEqualTo(enumConstant.name()); + } + + private String describePartialAssertion() { + return describePartialAssertion(""); + } + + private String describePartialAssertion(String partialAssertionDescription) { + return Joiner.on(": ").skipNulls().join(emptyToNull(descriptionText()), emptyToNull(partialAssertionDescription)); + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaEnumConstantsAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaEnumConstantsAssertion.java new file mode 100644 index 0000000000..947e4fe1e3 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaEnumConstantsAssertion.java @@ -0,0 +1,19 @@ +package com.tngtech.archunit.testutil.assertion; + +import com.tngtech.archunit.core.domain.JavaEnumConstant; +import org.assertj.core.api.AbstractObjectAssert; + +import static com.tngtech.archunit.testutil.Assertions.assertThat; + +public class JavaEnumConstantsAssertion extends AbstractObjectAssert { + public JavaEnumConstantsAssertion(JavaEnumConstant[] enumConstants) { + super(enumConstants, JavaEnumConstantsAssertion.class); + } + + public void matches(Enum... enumConstants) { + assertThat((Object[]) actual).as("Enum constants").hasSize(enumConstants.length); + for (int i = 0; i < actual.length; i++) { + assertThat(actual[i]).as("Element %d", i).isEquivalentTo(enumConstants[i]); + } + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ThrowsClauseAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ThrowsClauseAssertion.java new file mode 100644 index 0000000000..24fa33a547 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ThrowsClauseAssertion.java @@ -0,0 +1,24 @@ +package com.tngtech.archunit.testutil.assertion; + +import com.google.common.collect.Iterables; +import com.tngtech.archunit.core.domain.ThrowsClause; +import org.assertj.core.api.AbstractObjectAssert; + +import static com.tngtech.archunit.testutil.Assertions.assertThat; + +public class ThrowsClauseAssertion extends AbstractObjectAssert> { + public ThrowsClauseAssertion(ThrowsClause throwsClause) { + super(throwsClause, ThrowsClauseAssertion.class); + } + + public void matches(Class... classes) { + assertThat(actual).as("ThrowsClause").hasSize(classes.length); + for (int i = 0; i < actual.size(); i++) { + assertThat(Iterables.get(actual, i)).as("Element %d", i).matches(classes[i]); + } + } + + public void isEmpty() { + assertThat(actual).as("ThrowsClause").isEmpty(); + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ThrowsDeclarationAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ThrowsDeclarationAssertion.java new file mode 100644 index 0000000000..4d73c6a3ed --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ThrowsDeclarationAssertion.java @@ -0,0 +1,16 @@ +package com.tngtech.archunit.testutil.assertion; + +import com.tngtech.archunit.core.domain.ThrowsDeclaration; +import org.assertj.core.api.AbstractObjectAssert; + +import static com.tngtech.archunit.testutil.Assertions.assertThatType; + +public class ThrowsDeclarationAssertion extends AbstractObjectAssert> { + public ThrowsDeclarationAssertion(ThrowsDeclaration throwsDeclaration) { + super(throwsDeclaration, ThrowsDeclarationAssertion.class); + } + + public void matches(Class clazz) { + assertThatType(actual.getRawType()).as("Type of " + actual).matches(clazz); + } +} From e0e0c65ef4d60ad10005feb1bee1d2e5bfb11a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Siersze=C5=84?= Date: Fri, 25 Feb 2022 13:04:16 +0100 Subject: [PATCH 3/3] Add support to test caught exceptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This will add `TryCatchBlocks` to the ArchUnit core and introduce `JavaCodeUnit.getTryCatchBlocks()` to examine try-catch blocks that have been parsed from the bytecode of a method or constructor. We also add an extension `JavaAccess.getContainingTryBlocks()` to make it easy to verify that certain accesses in code are wrapped into certain try-catch blocks (e.g. "whenever method x is called there should be a try-catch block to handle exception case y"). Signed-off-by: Krzysztof Sierszeń Signed-off-by: Peter Gafert --- .../domain/DomainObjectCreationContext.java | 5 + .../archunit/core/domain/ImportContext.java | 13 +- .../archunit/core/domain/JavaAccess.java | 12 + .../archunit/core/domain/JavaCodeUnit.java | 22 +- .../archunit/core/domain/TryCatchBlock.java | 75 +++++ .../archunit/core/importer/AccessRecord.java | 7 + .../core/importer/ClassFileImportRecord.java | 11 + .../core/importer/ClassFileProcessor.java | 55 +++- .../core/importer/ClassGraphCreator.java | 43 ++- .../core/importer/DomainBuilders.java | 64 +++++ .../core/importer/JavaClassProcessor.java | 50 +++- .../core/importer/RawAccessRecord.java | 22 +- .../core/importer/TryCatchRecorder.java | 179 ++++++++++++ .../archunit/core/domain/TestUtils.java | 6 +- .../ClassFileImporterAccessesTest.java | 260 ++++++++++++++++++ .../core/importer/ImportTestUtils.java | 16 +- .../trycatch/ClassHoldingMethods.java | 21 ++ .../ClassWithComplexTryCatchBlocks.java | 30 ++ .../ClassWithSimpleTryCatchBlocks.java | 23 ++ ...assWithTryCatchBlockWithoutThrowables.java | 12 + .../trycatch/ClassWithTryWithResources.java | 19 ++ .../tngtech/archunit/testutil/Assertions.java | 11 + .../archunit/testutil/JavaCallQuery.java | 46 ++++ .../assertion/CodeUnitAccessAssertion.java | 26 ++ .../assertion/TryCatchBlockAssertion.java | 24 ++ .../assertion/TryCatchBlocksAssertion.java | 76 +++++ 26 files changed, 1078 insertions(+), 50 deletions(-) create mode 100644 archunit/src/main/java/com/tngtech/archunit/core/domain/TryCatchBlock.java create mode 100644 archunit/src/main/java/com/tngtech/archunit/core/importer/TryCatchRecorder.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/ClassHoldingMethods.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/ClassWithComplexTryCatchBlocks.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/ClassWithSimpleTryCatchBlocks.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/ClassWithTryCatchBlockWithoutThrowables.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/ClassWithTryWithResources.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/testutil/JavaCallQuery.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/testutil/assertion/TryCatchBlockAssertion.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/testutil/assertion/TryCatchBlocksAssertion.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 6ef359216b..11cf906c86 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 @@ -44,6 +44,7 @@ import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodReferenceBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaStaticInitializerBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaWildcardTypeBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.TryCatchBlockBuilder; import static com.google.common.base.Preconditions.checkArgument; @@ -107,6 +108,10 @@ public static JavaField createJavaField(JavaFieldBuilder builder) { return new JavaField(builder); } + public static TryCatchBlock createTryCatchBlock(TryCatchBlockBuilder builder) { + return new TryCatchBlock(builder); + } + public static JavaFieldAccess createJavaFieldAccess(JavaFieldAccessBuilder builder) { return new JavaFieldAccess(builder); } 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 5973f162ec..5b4aa1c14b 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 @@ -21,6 +21,7 @@ import java.util.Set; import com.tngtech.archunit.Internal; +import com.tngtech.archunit.core.importer.DomainBuilders.TryCatchBlockBuilder; @Internal public interface ImportContext { @@ -50,15 +51,17 @@ public interface ImportContext { Optional createEnclosingCodeUnit(JavaClass owner); - Set createFieldAccessesFor(JavaCodeUnit codeUnit); + Set createFieldAccessesFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders); - Set createMethodCallsFor(JavaCodeUnit codeUnit); + Set createMethodCallsFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders); - Set createConstructorCallsFor(JavaCodeUnit codeUnit); + Set createConstructorCallsFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders); - Set createMethodReferencesFor(JavaCodeUnit codeUnit); + Set createMethodReferencesFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders); - Set createConstructorReferencesFor(JavaCodeUnit codeUnit); + Set createConstructorReferencesFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders); + + Set createTryCatchBlockBuilders(JavaCodeUnit codeUnit); JavaClass resolveClass(String fullyQualifiedClassName); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAccess.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAccess.java index 08e3313168..6bc7c0e3c2 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAccess.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAccess.java @@ -16,6 +16,7 @@ package com.tngtech.archunit.core.domain; import java.util.Objects; +import java.util.Set; import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.base.ChainableFunction; @@ -28,6 +29,7 @@ import com.tngtech.archunit.core.importer.DomainBuilders; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; public abstract class JavaAccess @@ -128,6 +130,16 @@ public String getDescription() { protected abstract String descriptionVerb(); + /** + * @return All try-catch-blocks where this {@link JavaAccess} is contained within the try-part the try-catch-block + */ + @PublicAPI(usage = ACCESS) + public Set getContainingTryBlocks() { + return getOrigin().getTryCatchBlocks().stream() + .filter(block -> block.getAccessesContainedInTryBlock().contains(this)) + .collect(toImmutableSet()); + } + public static final class Predicates { private Predicates() { } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java index 5fbde5d45b..4c3276db18 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java @@ -34,7 +34,9 @@ import com.tngtech.archunit.core.domain.properties.HasThrowsClause; import com.tngtech.archunit.core.domain.properties.HasTypeParameters; import com.tngtech.archunit.core.importer.DomainBuilders.JavaCodeUnitBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.TryCatchBlockBuilder; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.Sets.union; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; import static com.tngtech.archunit.core.domain.Formatters.formatMethod; @@ -66,6 +68,7 @@ public abstract class JavaCodeUnit private Set constructorCalls = Collections.emptySet(); private Set methodReferences = Collections.emptySet(); private Set constructorReferences = Collections.emptySet(); + private Set tryCatchBlocks = Collections.emptySet(); JavaCodeUnit(JavaCodeUnitBuilder builder) { super(builder); @@ -203,6 +206,11 @@ public Set getInstanceofChecks() { return instanceofChecks; } + @PublicAPI(usage = ACCESS) + public Set getTryCatchBlocks() { + return tryCatchBlocks; + } + @PublicAPI(usage = ACCESS) public Set> getCallsFromSelf() { return union(getMethodCallsFromSelf(), getConstructorCallsFromSelf()); @@ -261,11 +269,15 @@ public List>> getParameterAnnotations() { } void completeAccessesFrom(ImportContext context) { - fieldAccesses = context.createFieldAccessesFor(this); - methodCalls = context.createMethodCallsFor(this); - constructorCalls = context.createConstructorCallsFor(this); - methodReferences = context.createMethodReferencesFor(this); - constructorReferences = context.createConstructorReferencesFor(this); + Set tryCatchBlockBuilders = context.createTryCatchBlockBuilders(this); + fieldAccesses = context.createFieldAccessesFor(this, tryCatchBlockBuilders); + methodCalls = context.createMethodCallsFor(this, tryCatchBlockBuilders); + constructorCalls = context.createConstructorCallsFor(this, tryCatchBlockBuilders); + methodReferences = context.createMethodReferencesFor(this, tryCatchBlockBuilders); + constructorReferences = context.createConstructorReferencesFor(this, tryCatchBlockBuilders); + tryCatchBlocks = tryCatchBlockBuilders.stream() + .map(builder -> builder.build(this, context)) + .collect(toImmutableSet()); } @ResolvesTypesViaReflection diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/TryCatchBlock.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/TryCatchBlock.java new file mode 100644 index 0000000000..39c833e7ef --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/TryCatchBlock.java @@ -0,0 +1,75 @@ +/* + * Copyright 2014-2022 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.Set; + +import com.google.common.collect.ImmutableSet; +import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.core.domain.properties.HasOwner; +import com.tngtech.archunit.core.domain.properties.HasSourceCodeLocation; +import com.tngtech.archunit.core.importer.DomainBuilders.TryCatchBlockBuilder; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; +import static com.tngtech.archunit.core.domain.properties.HasName.Utils.namesOf; + +@PublicAPI(usage = ACCESS) +public final class TryCatchBlock implements HasOwner, HasSourceCodeLocation { + private final JavaCodeUnit owner; + private final Set caughtThrowables; + private final SourceCodeLocation sourceCodeLocation; + private final Set> accessesContainedInTryBlock; + + TryCatchBlock(TryCatchBlockBuilder builder) { + this.owner = checkNotNull(builder.getOwner()); + this.caughtThrowables = ImmutableSet.copyOf(builder.getCaughtThrowables()); + this.sourceCodeLocation = checkNotNull(builder.getSourceCodeLocation()); + this.accessesContainedInTryBlock = ImmutableSet.copyOf(builder.getAccessesContainedInTryBlock()); + } + + @Override + @PublicAPI(usage = ACCESS) + public JavaCodeUnit getOwner() { + return owner; + } + + @PublicAPI(usage = ACCESS) + public Set getCaughtThrowables() { + return caughtThrowables; + } + + @Override + @PublicAPI(usage = ACCESS) + public SourceCodeLocation getSourceCodeLocation() { + return sourceCodeLocation; + } + + @PublicAPI(usage = ACCESS) + public Set> getAccessesContainedInTryBlock() { + return accessesContainedInTryBlock; + } + + @Override + public String toString() { + return toStringHelper(this) + .add("owner", owner.getFullName()) + .add("caughtThrowables", namesOf(caughtThrowables)) + .add("location", sourceCodeLocation) + .toString(); + } +} 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 ecd9c0fc19..385c815eb3 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 @@ -54,6 +54,8 @@ interface AccessRecord { int getLineNumber(); + RawAccessRecord getRaw(); + @Internal interface FieldAccessRecord extends AccessRecord { AccessType getAccessType(); @@ -260,6 +262,11 @@ public TARGET getTarget() { public int getLineNumber() { return record.lineNumber; } + + @Override + public RawAccessRecord getRaw() { + return record; + } } private static class RawFieldAccessRecordProcessed extends RawAccessRecordProcessed implements FieldAccessRecord { 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 1ae9465a12..385ed40076 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 @@ -31,6 +31,7 @@ import com.google.common.collect.ListMultimap; import com.google.common.collect.SetMultimap; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaCodeUnit; import com.tngtech.archunit.core.domain.JavaMember; import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder; @@ -43,6 +44,7 @@ import com.tngtech.archunit.core.importer.DomainBuilders.JavaParameterizedTypeBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaStaticInitializerBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeParameterBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.TryCatchBlockBuilder; import com.tngtech.archunit.core.importer.RawAccessRecord.CodeUnit; import static com.google.common.base.Preconditions.checkArgument; @@ -66,6 +68,7 @@ class ClassFileImportRecord { private final SetMultimap annotationsByOwner = HashMultimap.create(); private final Map annotationDefaultValuesByOwner = new HashMap<>(); private final EnclosingDeclarationsByInnerClasses enclosingDeclarationsByOwner = new EnclosingDeclarationsByInnerClasses(); + private final SetMultimap tryCatchBlocksByOwner = HashMultimap.create(); private final Set rawFieldAccessRecords = new HashSet<>(); private final Set rawMethodCallRecords = new HashSet<>(); @@ -135,6 +138,10 @@ void setEnclosingCodeUnit(String ownerName, CodeUnit enclosingCodeUnit) { enclosingDeclarationsByOwner.registerEnclosingCodeUnit(ownerName, enclosingCodeUnit); } + void addTryCatchBlocks(String declaringClassName, String methodName, String descriptor, Set tryCatchBlocks) { + tryCatchBlocksByOwner.putAll(getMemberKey(declaringClassName, methodName, descriptor), tryCatchBlocks); + } + Optional getSuperclassFor(String name) { return Optional.ofNullable(superclassNamesByOwner.get(name)); } @@ -238,6 +245,10 @@ Optional getEnclosingCodeUnitFor(String ownerName) { return enclosingDeclarationsByOwner.getEnclosingCodeUnit(ownerName); } + Set getTryCatchBlockBuildersFor(JavaCodeUnit codeUnit) { + return tryCatchBlocksByOwner.get(getMemberKey(codeUnit)); + } + void registerFieldAccess(RawAccessRecord.ForField record) { rawFieldAccessRecords.add(record); } 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 4f24ec17eb..0c1a9d3d5b 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 @@ -24,6 +24,7 @@ import com.tngtech.archunit.ArchConfiguration; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.domain.JavaFieldAccess.AccessType; import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder; @@ -33,12 +34,15 @@ import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaParameterizedTypeBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaStaticInitializerBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.TryCatchBlockBuilder; import com.tngtech.archunit.core.importer.JavaClassProcessor.AccessHandler; import com.tngtech.archunit.core.importer.RawAccessRecord.CodeUnit; import com.tngtech.archunit.core.importer.RawAccessRecord.TargetInfo; +import com.tngtech.archunit.core.importer.TryCatchRecorder.TryCatchBlocksFinishedListener; import com.tngtech.archunit.core.importer.resolvers.ClassResolver; import com.tngtech.archunit.core.importer.resolvers.ClassResolver.ClassUriImporter; import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Label; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -196,13 +200,14 @@ public void onDeclaredGenericSignatureType(String typeName) { } } - private static class RecordAccessHandler implements AccessHandler { + private static class RecordAccessHandler implements AccessHandler, TryCatchBlocksFinishedListener { private static final Logger LOG = LoggerFactory.getLogger(RecordAccessHandler.class); private final ClassFileImportRecord importRecord; private final DependencyResolutionProcess dependencyResolutionProcess; private CodeUnit codeUnit; private int lineNumber; + private final TryCatchRecorder tryCatchRecorder = new TryCatchRecorder(this); private RecordAccessHandler(ClassFileImportRecord importRecord, DependencyResolutionProcess dependencyResolutionProcess) { this.importRecord = importRecord; @@ -215,8 +220,14 @@ public void setContext(CodeUnit codeUnit) { } @Override - public void setLineNumber(int lineNumber) { + public void onLineNumber(int lineNumber, Label label) { this.lineNumber = lineNumber; + tryCatchRecorder.onEncounteredLabel(label, lineNumber); + } + + @Override + public void onLabel(Label label) { + tryCatchRecorder.onEncounteredLabel(label); } @Override @@ -224,9 +235,11 @@ public void handleFieldInstruction(int opcode, String owner, String name, String AccessType accessType = AccessType.forOpCode(opcode); LOG.trace("Found {} access to field {}.{}:{} in line {}", accessType, owner, name, desc, lineNumber); TargetInfo target = new TargetInfo(owner, name, desc); - importRecord.registerFieldAccess(filled(new RawAccessRecord.ForField.Builder(), target) + RawAccessRecord.ForField accessRecord = filled(new RawAccessRecord.ForField.Builder(), target) .withAccessType(accessType) - .build()); + .build(); + importRecord.registerFieldAccess(accessRecord); + tryCatchRecorder.registerAccess(accessRecord); dependencyResolutionProcess.registerAccessToType(target.owner.getFullyQualifiedClassName()); } @@ -234,11 +247,13 @@ public void handleFieldInstruction(int opcode, String owner, String name, String public void handleMethodInstruction(String owner, String name, String desc) { LOG.trace("Found call of method {}.{}:{} in line {}", owner, name, desc, lineNumber); TargetInfo target = new TargetInfo(owner, name, desc); + RawAccessRecord accessRecord = filled(new RawAccessRecord.Builder(), target).build(); if (CONSTRUCTOR_NAME.equals(name)) { - importRecord.registerConstructorCall(filled(new RawAccessRecord.Builder(), target).build()); + importRecord.registerConstructorCall(accessRecord); } else { - importRecord.registerMethodCall(filled(new RawAccessRecord.Builder(), target).build()); + importRecord.registerMethodCall(accessRecord); } + tryCatchRecorder.registerAccess(accessRecord); dependencyResolutionProcess.registerAccessToType(target.owner.getFullyQualifiedClassName()); } @@ -246,14 +261,38 @@ public void handleMethodInstruction(String owner, String name, String desc) { public void handleMethodReferenceInstruction(String owner, String name, String desc) { LOG.trace("Found method reference {}.{}:{} in line {}", owner, name, desc, lineNumber); TargetInfo target = new TargetInfo(owner, name, desc); + RawAccessRecord accessRecord = filled(new RawAccessRecord.Builder(), target).build(); if (CONSTRUCTOR_NAME.equals(name)) { - importRecord.registerConstructorReference(filled(new RawAccessRecord.Builder(), target).build()); + importRecord.registerConstructorReference(accessRecord); } else { - importRecord.registerMethodReference(filled(new RawAccessRecord.Builder(), target).build()); + importRecord.registerMethodReference(accessRecord); } + tryCatchRecorder.registerAccess(accessRecord); dependencyResolutionProcess.registerAccessToType(target.owner.getFullyQualifiedClassName()); } + @Override + public void handleTryCatchBlock(Label start, Label end, Label handler, JavaClassDescriptor throwableType) { + LOG.trace("Found try/catch block between {} and {} for throwable {}", start, end, throwableType); + tryCatchRecorder.registerTryCatchBlock(start, end, handler, throwableType); + } + + @Override + public void handleTryFinallyBlock(Label start, Label end, Label handler) { + LOG.trace("Found try/finally block between {} and {}", start, end); + tryCatchRecorder.registerTryFinallyBlock(start, end, handler); + } + + @Override + public void onMethodEnd() { + tryCatchRecorder.onEncounteredMethodEnd(); + } + + @Override + public void onTryCatchBlocksFinished(Set tryCatchBlocks) { + importRecord.addTryCatchBlocks(codeUnit.getDeclaringClassName(), codeUnit.getName(), codeUnit.getDescriptor(), tryCatchBlocks); + } + private > BUILDER filled(BUILDER builder, TargetInfo target) { return builder .withCaller(codeUnit) 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 de69e68e3c..47f8e97db0 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 @@ -33,6 +33,7 @@ import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; import com.tngtech.archunit.core.domain.AccessTarget.MethodReferenceTarget; import com.tngtech.archunit.core.domain.ImportContext; +import com.tngtech.archunit.core.domain.JavaAccess; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; @@ -57,6 +58,7 @@ import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodCallBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodReferenceBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaParameterizedTypeBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.TryCatchBlockBuilder; import com.tngtech.archunit.core.importer.RawAccessRecord.CodeUnit; import com.tngtech.archunit.core.importer.resolvers.ClassResolver; @@ -131,52 +133,66 @@ private , B extends RawAccessRecord> void tryProcess( } @Override - public Set createFieldAccessesFor(JavaCodeUnit codeUnit) { + public Set createFieldAccessesFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders) { ImmutableSet.Builder result = ImmutableSet.builder(); for (FieldAccessRecord record : processedFieldAccessRecords.get(codeUnit)) { - result.add(accessBuilderFrom(new JavaFieldAccessBuilder(), record) + JavaFieldAccess access = accessBuilderFrom(new JavaFieldAccessBuilder(), record) .withAccessType(record.getAccessType()) - .build()); + .build(); + result.add(access); + handlePossibleTryBlockAccess(tryCatchBlockBuilders, record, access); } return result.build(); } @Override - public Set createMethodCallsFor(JavaCodeUnit codeUnit) { + public Set createMethodCallsFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders) { ImmutableSet.Builder result = ImmutableSet.builder(); for (AccessRecord record : processedMethodCallRecords.get(codeUnit)) { - result.add(accessBuilderFrom(new JavaMethodCallBuilder(), record).build()); + JavaMethodCall call = accessBuilderFrom(new JavaMethodCallBuilder(), record).build(); + result.add(call); + handlePossibleTryBlockAccess(tryCatchBlockBuilders, record, call); } return result.build(); } @Override - public Set createConstructorCallsFor(JavaCodeUnit codeUnit) { + public Set createConstructorCallsFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders) { ImmutableSet.Builder result = ImmutableSet.builder(); for (AccessRecord record : processedConstructorCallRecords.get(codeUnit)) { - result.add(accessBuilderFrom(new JavaConstructorCallBuilder(), record).build()); + JavaConstructorCall call = accessBuilderFrom(new JavaConstructorCallBuilder(), record).build(); + result.add(call); + handlePossibleTryBlockAccess(tryCatchBlockBuilders, record, call); } return result.build(); } @Override - public Set createMethodReferencesFor(JavaCodeUnit codeUnit) { + public Set createMethodReferencesFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders) { ImmutableSet.Builder result = ImmutableSet.builder(); for (AccessRecord record : processedMethodReferenceRecords.get(codeUnit)) { - result.add(accessBuilderFrom(new JavaMethodReferenceBuilder(), record).build()); + JavaMethodReference methodReference = accessBuilderFrom(new JavaMethodReferenceBuilder(), record).build(); + result.add(methodReference); + handlePossibleTryBlockAccess(tryCatchBlockBuilders, record, methodReference); } return result.build(); } @Override - public Set createConstructorReferencesFor(JavaCodeUnit codeUnit) { + public Set createConstructorReferencesFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders) { ImmutableSet.Builder result = ImmutableSet.builder(); for (AccessRecord record : processedConstructorReferenceRecords.get(codeUnit)) { - result.add(accessBuilderFrom(new JavaConstructorReferenceBuilder(), record).build()); + JavaConstructorReference constructorReference = accessBuilderFrom(new JavaConstructorReferenceBuilder(), record).build(); + result.add(constructorReference); + handlePossibleTryBlockAccess(tryCatchBlockBuilders, record, constructorReference); } return result.build(); } + private void handlePossibleTryBlockAccess(Set tryCatchBlockBuilders, AccessRecord record, JavaAccess access) { + tryCatchBlockBuilders.forEach(builder -> builder.addIfContainedInTryBlock(record.getRaw(), access)); + } + private > B accessBuilderFrom(B builder, AccessRecord record) { return builder @@ -308,6 +324,11 @@ public Optional createEnclosingCodeUnit(JavaClass owner) { return enclosingClass.tryGetCodeUnitWithParameterTypeNames(codeUnit.getName(), codeUnit.getRawParameterTypeNames()); } + @Override + public Set createTryCatchBlockBuilders(JavaCodeUnit codeUnit) { + return importRecord.getTryCatchBlockBuildersFor(codeUnit); + } + @Override public JavaClass resolveClass(String fullyQualifiedClassName) { return classes.getOrResolve(fullyQualifiedClassName); 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 ac5c5bdd54..184b7b0bb9 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 @@ -42,7 +42,9 @@ import com.tngtech.archunit.core.domain.AccessTarget.MethodReferenceTarget; import com.tngtech.archunit.core.domain.DomainObjectCreationContext; import com.tngtech.archunit.core.domain.Formatters; +import com.tngtech.archunit.core.domain.ImportContext; import com.tngtech.archunit.core.domain.InstanceofCheck; +import com.tngtech.archunit.core.domain.JavaAccess; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClassDescriptor; @@ -67,11 +69,14 @@ import com.tngtech.archunit.core.domain.JavaWildcardType; import com.tngtech.archunit.core.domain.ReferencedClassObject; import com.tngtech.archunit.core.domain.Source; +import com.tngtech.archunit.core.domain.SourceCodeLocation; import com.tngtech.archunit.core.domain.ThrowsClause; +import com.tngtech.archunit.core.domain.TryCatchBlock; import com.tngtech.archunit.core.domain.properties.HasTypeParameters; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableSet.toImmutableSet; 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.createGenericArrayType; @@ -79,6 +84,7 @@ import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createReferencedClassObject; 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.createTryCatchBlock; 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.Formatters.ensureCanonicalArrayTypeName; @@ -937,6 +943,64 @@ static Set build( } } + @Internal + public static class TryCatchBlockBuilder { + private Set caughtThrowables; + private int lineNumber; + private JavaCodeUnit owner; + private ImportContext context; + private final Set> accessesContainedInTryBlock = new HashSet<>(); + private Set rawAccessesContainedInTryBlock; + + TryCatchBlockBuilder() { + } + + TryCatchBlockBuilder withCaughtThrowables(Set caughtThrowables) { + this.caughtThrowables = caughtThrowables; + return this; + } + + TryCatchBlockBuilder withLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + return this; + } + + TryCatchBlockBuilder withRawAccessesInTryBlock(Set accessRecords) { + this.rawAccessesContainedInTryBlock = accessRecords; + return this; + } + + void addIfContainedInTryBlock(RawAccessRecord rawRecord, JavaAccess access) { + if (rawAccessesContainedInTryBlock.contains(rawRecord)) { + accessesContainedInTryBlock.add(access); + } + } + + public TryCatchBlock build(JavaCodeUnit owner, ImportContext context) { + this.owner = owner; + this.context = context; + return createTryCatchBlock(this); + } + + public JavaCodeUnit getOwner() { + return owner; + } + + public Set> getAccessesContainedInTryBlock() { + return accessesContainedInTryBlock; + } + + public Set getCaughtThrowables() { + return caughtThrowables.stream() + .map(throwable -> context.resolveClass(throwable.getFullyQualifiedClassName())) + .collect(toImmutableSet()); + } + + public SourceCodeLocation getSourceCodeLocation() { + return SourceCodeLocation.of(owner.getOwner(), lineNumber); + } + } + @Internal public abstract static class JavaAccessBuilder> { private JavaCodeUnit origin; 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 2fc7c34e9f..462d433c06 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 @@ -356,11 +356,17 @@ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, bo // NOTE: ASM does not reliably visit this method, so if this method is skipped, line number 0 is recorded @Override - public void visitLineNumber(int line, Label start) { - LOG.trace("Examining line number {}", line); + public void visitLineNumber(int line, Label label) { + LOG.trace("Examining line number {} at label {}", line, label); codeUnitBuilder.recordLineNumber(line); actualLineNumber = line; - accessHandler.setLineNumber(actualLineNumber); + accessHandler.onLineNumber(actualLineNumber, label); + } + + @Override + public void visitLabel(Label label) { + LOG.trace("Examining label {}", label); + accessHandler.onLabel(label); } @Override @@ -372,6 +378,15 @@ public void visitLdcInsn(Object value) { } } + @Override + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + if (type != null) { + accessHandler.handleTryCatchBlock(start, end, handler, JavaClassDescriptorImporter.createFromAsmObjectTypeName(type)); + } else { + accessHandler.handleTryFinallyBlock(start, end, handler); + } + } + @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { accessHandler.handleFieldInstruction(opcode, owner, name, desc); @@ -425,6 +440,7 @@ private void processLambdaMetafactoryMethodHandleArgument(Handle methodHandle) { @Override public void visitEnd() { declarationHandler.onDeclaredMemberAnnotations(codeUnitBuilder.getName(), codeUnitBuilder.getDescriptor(), annotations); + accessHandler.onMethodEnd(); } private static class AnnotationDefaultProcessor extends ClassAndPrimitiveDistinguishingAnnotationVisitor { @@ -512,12 +528,20 @@ interface AccessHandler { void setContext(CodeUnit codeUnit); - void setLineNumber(int lineNumber); + void onLineNumber(int lineNumber, Label label); + + void onLabel(Label label); void handleMethodInstruction(String owner, String name, String desc); void handleMethodReferenceInstruction(String owner, String name, String desc); + void handleTryCatchBlock(Label start, Label end, Label handler, JavaClassDescriptor throwableType); + + void handleTryFinallyBlock(Label start, Label end, Label handler); + + void onMethodEnd(); + @Internal class NoOp implements AccessHandler { @Override @@ -529,7 +553,11 @@ public void setContext(CodeUnit codeUnit) { } @Override - public void setLineNumber(int lineNumber) { + public void onLineNumber(int lineNumber, Label label) { + } + + @Override + public void onLabel(Label label) { } @Override @@ -539,6 +567,18 @@ public void handleMethodInstruction(String owner, String name, String desc) { @Override public void handleMethodReferenceInstruction(String owner, String name, String desc) { } + + @Override + public void handleTryCatchBlock(Label start, Label end, Label handler, JavaClassDescriptor throwableType) { + } + + @Override + public void handleTryFinallyBlock(Label start, Label end, Label handler) { + } + + @Override + public void onMethodEnd() { + } } } 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 4e1f6bbc6d..eb6ee6c7c0 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 @@ -91,15 +91,15 @@ private static List namesOf(Iterable descriptors) { return result.build(); } - public String getName() { + String getName() { return name; } - public List getRawParameterTypes() { + List getRawParameterTypes() { return rawParameterTypes; } - public List getRawParameterTypeNames() { + List getRawParameterTypeNames() { return rawParameterTypeNames; } @@ -107,6 +107,16 @@ String getDeclaringClassName() { return declaringClassName; } + String getDescriptor() { + return descriptor; + } + + boolean is(JavaCodeUnit method) { + return getName().equals(method.getName()) + && descriptor.equals(method.getDescriptor()) + && getDeclaringClassName().equals(method.getOwner().getName()); + } + @Override public int hashCode() { return hashCode; @@ -135,12 +145,6 @@ public String toString() { ", declaringClassName='" + declaringClassName + '\'' + '}'; } - - public boolean is(JavaCodeUnit method) { - return getName().equals(method.getName()) - && descriptor.equals(method.getDescriptor()) - && getDeclaringClassName().equals(method.getOwner().getName()); - } } static final class TargetInfo { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/TryCatchRecorder.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/TryCatchRecorder.java new file mode 100644 index 0000000000..3cf58ad470 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/TryCatchRecorder.java @@ -0,0 +1,179 @@ +/* + * Copyright 2014-2022 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.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; +import com.tngtech.archunit.core.importer.DomainBuilders.TryCatchBlockBuilder; +import org.objectweb.asm.Label; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.google.common.collect.ImmutableSet.toImmutableSet; + +class TryCatchRecorder { + private static final Logger log = LoggerFactory.getLogger(TryCatchRecorder.class); + + private final TryCatchBlocksFinishedListener tryCatchBlocksFinishedListener; + private final Map> blocksByEndByStart = new HashMap<>(); + private final Multimap activeBlocksByEnd = HashMultimap.create(); + private final Set