diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java index ca8f4d6839..cb2337d05a 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java @@ -19,6 +19,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import com.google.common.base.MoreObjects; import com.google.common.collect.ComparisonChain; @@ -51,6 +52,7 @@ *
  • a class (or method/constructor of this class) declares a type parameter referencing another class
  • *
  • a class (or method/constructor of this class) is annotated with an annotation of a certain type or referencing another class as annotation parameter
  • *
  • a method/constructor of a class references another class in a throws declaration
  • + *
  • a class references another class in a {@code catch} clause
  • *
  • a class references another class object (e.g. {@code Example.class})
  • *
  • a class references another class in an {@code instanceof} check
  • * @@ -125,6 +127,12 @@ static Set tryCreateFromThrowsDeclaration(ThrowsDeclaration tryCreateFromTryCatchBlock(TryCatchBlock tryCatchBlock) { + return tryCatchBlock.getCaughtThrowables().stream() + .flatMap(caughtThrowable -> tryCreateDependency(tryCatchBlock.getOwner(), "catches type", caughtThrowable, tryCatchBlock.getSourceCodeLocation()).stream()) + .collect(Collectors.toSet()); + } + static Set tryCreateFromInstanceofCheck(InstanceofCheck instanceofCheck) { return tryCreateDependency( instanceofCheck.getOwner(), "checks instanceof", @@ -269,6 +277,10 @@ public String getDescription() { return description; } + /** + * @implNote For dependencies created by {@code catch} clauses, the line number reported currently refers to + * the first access in the {@code try} block, not to the {@code catch} clause. + */ @Override @PublicAPI(usage = ACCESS) public SourceCodeLocation getSourceCodeLocation() { 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 e4480a4dae..02392e94f9 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 @@ -646,6 +646,11 @@ public Set getInstanceofChecks() { return members.getInstanceofChecks(); } + @PublicAPI(usage = ACCESS) + public Set getTryCatchBlocks() { + return members.getTryCatchBlocks(); + } + @PublicAPI(usage = ACCESS) public Set getReferencedClassObjects() { return members.getReferencedClassObjects(); @@ -1310,6 +1315,14 @@ public Set> getMethodThrowsDeclarationsWithTypeOfS return reverseDependencies.getMethodThrowsDeclarationsWithTypeOf(this); } + /** + * @return {@link TryCatchBlock TryCatchBlocks} of all imported classes that declare to catch this class. + */ + @PublicAPI(usage = ACCESS) + public Set getTryCatchBlocksThatCatchSelf() { + return reverseDependencies.getTryCatchBlocksThatCatch(this); + } + /** * @return Constructors of all imported classes that have a parameter type of this class. */ diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java index 35e29d4aa7..678383c36e 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java @@ -47,6 +47,7 @@ private Supplier> createDirectDependenciesFromClassSupplier() { returnTypeDependenciesFromSelf(), codeUnitParameterDependenciesFromSelf(), throwsDeclarationDependenciesFromSelf(), + tryCatchBlockDependenciesFromSelf(), annotationDependenciesFromSelf(), instanceofCheckDependenciesFromSelf(), referencedClassObjectDependenciesFromSelf(), @@ -166,6 +167,11 @@ private Stream throwsDeclarationDependenciesFromSelf() { .flatMap(throwsDeclaration -> Dependency.tryCreateFromThrowsDeclaration(throwsDeclaration).stream()); } + private Stream tryCatchBlockDependenciesFromSelf() { + return javaClass.getTryCatchBlocks().stream() + .flatMap(tryCatchBlock -> Dependency.tryCreateFromTryCatchBlock(tryCatchBlock).stream()); + } + private Stream annotationDependenciesFromSelf() { return Streams.concat( annotationDependencies(javaClass), diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassMembers.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassMembers.java index 1a5e1bcbbf..c797e60cb2 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassMembers.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassMembers.java @@ -191,6 +191,14 @@ Set getInstanceofChecks() { return result.build(); } + Set getTryCatchBlocks() { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (JavaCodeUnit codeUnit : codeUnits) { + result.addAll(codeUnit.getTryCatchBlocks()); + } + return result.build(); + } + Set getReferencedClassObjects() { ImmutableSet.Builder result = ImmutableSet.builder(); for (JavaCodeUnit codeUnit : codeUnits) { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/ReverseDependencies.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/ReverseDependencies.java index e6a0fd7748..0e68259a7d 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/ReverseDependencies.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/ReverseDependencies.java @@ -41,6 +41,7 @@ final class ReverseDependencies { private final SetMultimap methodParameterTypeDependencies; private final SetMultimap methodReturnTypeDependencies; private final SetMultimap> methodsThrowsDeclarationDependencies; + private final SetMultimap tryCatchBlockDependencies; private final SetMultimap constructorParameterTypeDependencies; private final SetMultimap> constructorThrowsDeclarationDependencies; private final SetMultimap> annotationTypeDependencies; @@ -58,6 +59,7 @@ private ReverseDependencies(ReverseDependencies.Creation creation) { this.methodParameterTypeDependencies = creation.methodParameterTypeDependencies.build(); this.methodReturnTypeDependencies = creation.methodReturnTypeDependencies.build(); this.methodsThrowsDeclarationDependencies = creation.methodsThrowsDeclarationDependencies.build(); + this.tryCatchBlockDependencies = creation.tryCatchBlockDependencies.build(); this.constructorParameterTypeDependencies = creation.constructorParameterTypeDependencies.build(); this.constructorThrowsDeclarationDependencies = creation.constructorThrowsDeclarationDependencies.build(); this.annotationTypeDependencies = creation.annotationTypeDependencies.build(); @@ -114,6 +116,10 @@ Set> getMethodThrowsDeclarationsWithTypeOf(JavaCla return methodsThrowsDeclarationDependencies.get(clazz); } + Set getTryCatchBlocksThatCatch(JavaClass clazz) { + return tryCatchBlockDependencies.get(clazz); + } + Set getConstructorsWithParameterTypeOf(JavaClass clazz) { return constructorParameterTypeDependencies.get(clazz); } @@ -150,6 +156,7 @@ static class Creation { private final ImmutableSetMultimap.Builder methodParameterTypeDependencies = ImmutableSetMultimap.builder(); private final ImmutableSetMultimap.Builder methodReturnTypeDependencies = ImmutableSetMultimap.builder(); private final ImmutableSetMultimap.Builder> methodsThrowsDeclarationDependencies = ImmutableSetMultimap.builder(); + private final ImmutableSetMultimap.Builder tryCatchBlockDependencies = ImmutableSetMultimap.builder(); private final ImmutableSetMultimap.Builder constructorParameterTypeDependencies = ImmutableSetMultimap.builder(); private final ImmutableSetMultimap.Builder> constructorThrowsDeclarationDependencies = ImmutableSetMultimap.builder(); private final ImmutableSetMultimap.Builder> annotationTypeDependencies = ImmutableSetMultimap.builder(); @@ -200,6 +207,11 @@ private void registerMethods(JavaClass clazz) { for (ThrowsDeclaration throwsDeclaration : method.getThrowsClause()) { methodsThrowsDeclarationDependencies.put(throwsDeclaration.getRawType(), throwsDeclaration); } + for (TryCatchBlock tryCatchBlock : method.getTryCatchBlocks()) { + for (JavaClass caughtThrowable : tryCatchBlock.getCaughtThrowables()) { + tryCatchBlockDependencies.put(caughtThrowable.toErasure(), tryCatchBlock); + } + } for (InstanceofCheck instanceofCheck : method.getInstanceofChecks()) { instanceofCheckDependencies.put(instanceofCheck.getRawType(), instanceofCheck); } @@ -214,6 +226,11 @@ private void registerConstructors(JavaClass clazz) { for (ThrowsDeclaration throwsDeclaration : constructor.getThrowsClause()) { constructorThrowsDeclarationDependencies.put(throwsDeclaration.getRawType(), throwsDeclaration); } + for (TryCatchBlock tryCatchBlock : constructor.getTryCatchBlocks()) { + for (JavaClass caughtThrowable : tryCatchBlock.getCaughtThrowables()) { + tryCatchBlockDependencies.put(caughtThrowable.toErasure(), tryCatchBlock); + } + } for (InstanceofCheck instanceofCheck : constructor.getInstanceofChecks()) { instanceofCheckDependencies.put(instanceofCheck.getRawType(), instanceofCheck); } @@ -252,11 +269,16 @@ private Set> findAnnotations(JavaClass clazz) { } private void registerStaticInitializer(JavaClass clazz) { - if (clazz.getStaticInitializer().isPresent()) { - for (InstanceofCheck instanceofCheck : clazz.getStaticInitializer().get().getInstanceofChecks()) { + clazz.getStaticInitializer().ifPresent(staticInitializer -> { + for (TryCatchBlock tryCatchBlock : staticInitializer.getTryCatchBlocks()) { + for (JavaClass caughtThrowable : tryCatchBlock.getCaughtThrowables()) { + tryCatchBlockDependencies.put(caughtThrowable.toErasure(), tryCatchBlock); + } + } + for (InstanceofCheck instanceofCheck : staticInitializer.getInstanceofChecks()) { instanceofCheckDependencies.put(instanceofCheck.getRawType(), instanceofCheck); } - } + }); } void finish(Iterable classes) { 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 f56e69dbd2..db06560861 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 @@ -13,6 +13,7 @@ import com.google.common.base.MoreObjects; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.testobjects.ClassWithArrayDependencies; +import com.tngtech.archunit.core.domain.testobjects.ClassWithDependencyOnCaughtException; import com.tngtech.archunit.core.domain.testobjects.ClassWithDependencyOnInstanceofCheck; import com.tngtech.archunit.core.domain.testobjects.ClassWithDependencyOnInstanceofCheck.InstanceOfCheckTarget; import com.tngtech.archunit.core.domain.testobjects.DependenciesOnClassObjects; @@ -200,6 +201,67 @@ public void Dependency_from_throws_declaration() { .contains("Method <" + origin.getFullName() + "> throws type <" + IOException.class.getName() + ">"); } + @DataProvider + public static Object[][] with_try_catch_block_members() { + JavaClass javaClass = importClassesWithContext(ClassWithDependencyOnCaughtException.class, IOException.class) + .get(ClassWithDependencyOnCaughtException.class); + + return $$( + $(javaClass.getStaticInitializer().get(), 9), + $(javaClass.getConstructor(), 16), + $(javaClass.getMethod("simpleCatchClauseMethod"), 23) + ); + } + + @Test + @UseDataProvider("with_try_catch_block_members") + public void Dependency_from_simple_catch_clause(JavaCodeUnit memberWithTryCatchBlock, int expectedLineNumber) { + TryCatchBlock tryCatchBlock = getOnlyElement(memberWithTryCatchBlock.getTryCatchBlocks()); + + Dependency dependency = getOnlyElement(Dependency.tryCreateFromTryCatchBlock(tryCatchBlock)); + + Assertions.assertThatDependency(dependency) + .satisfies(catchesType(IOException.class, memberWithTryCatchBlock, expectedLineNumber, ClassWithDependencyOnCaughtException.class)); + } + + private static Consumer catchesType(Class targetClass, JavaCodeUnit javaCodeUnit, int expectedLineNumber, Class originClass) { + return dependency -> Assertions.assertThatDependency(dependency) + .matches(originClass, targetClass) + .hasDescription(javaCodeUnit.getFullName(), "catches type", targetClass.getName()) + .inLocation(originClass, expectedLineNumber); + } + + @Test + public void Dependency_from_union_catch_clause() { + JavaMethod method = importClassesWithContext(ClassWithDependencyOnCaughtException.class, IllegalStateException.class, IOException.class) + .get(ClassWithDependencyOnCaughtException.class) + .getMethod("unionCatchClauseMethod"); + TryCatchBlock tryCatchBlock = getOnlyElement(method.getTryCatchBlocks()); + + Set dependencies = Dependency.tryCreateFromTryCatchBlock(tryCatchBlock); + + Assertions.assertThatDependencies(dependencies).satisfiesExactlyInAnyOrder( + catchesType(IllegalStateException.class, method, 30, ClassWithDependencyOnCaughtException.class), + catchesType(IOException.class, method, 30, ClassWithDependencyOnCaughtException.class) + ); + } + + @Test + public void Dependency_from_multiple_catch_clauses() { + JavaMethod method = importClassesWithContext(ClassWithDependencyOnCaughtException.class, IllegalStateException.class, IOException.class) + .get(ClassWithDependencyOnCaughtException.class) + .getMethod("multipleCatchClausesMethod"); + TryCatchBlock tryCatchBlock = getOnlyElement(method.getTryCatchBlocks()); + + Set dependencies = Dependency.tryCreateFromTryCatchBlock(tryCatchBlock); + + Assertions.assertThatDependencies(dependencies).satisfiesExactlyInAnyOrder( + catchesType(IllegalStateException.class, method, 37, ClassWithDependencyOnCaughtException.class), + catchesType(RuntimeException.class, method, 37, ClassWithDependencyOnCaughtException.class), + catchesType(IOException.class, method, 37, ClassWithDependencyOnCaughtException.class) + ); + } + @DataProvider public static Object[][] with_instanceof_check_members() { JavaClass javaClass = importClassesWithContext(ClassWithDependencyOnInstanceofCheck.class, InstanceOfCheckTarget.class) 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 c08fc3ac32..2b0ecdcfea 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 @@ -26,13 +26,19 @@ import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.base.HasDescription; import com.tngtech.archunit.core.domain.testobjects.AAccessingB; +import com.tngtech.archunit.core.domain.testobjects.ACatchingBException; import com.tngtech.archunit.core.domain.testobjects.AExtendingSuperAImplementingInterfaceForA; import com.tngtech.archunit.core.domain.testobjects.AReferencingB; +import com.tngtech.archunit.core.domain.testobjects.AThrowingBException; import com.tngtech.archunit.core.domain.testobjects.AhavingMembersOfTypeB; import com.tngtech.archunit.core.domain.testobjects.AllPrimitiveDependencies; import com.tngtech.archunit.core.domain.testobjects.ArrayComponentTypeDependencies; import com.tngtech.archunit.core.domain.testobjects.B; +import com.tngtech.archunit.core.domain.testobjects.BException1; +import com.tngtech.archunit.core.domain.testobjects.BException2; +import com.tngtech.archunit.core.domain.testobjects.BException3; import com.tngtech.archunit.core.domain.testobjects.BReferencedByA; +import com.tngtech.archunit.core.domain.testobjects.ClassWithDependencyOnInstanceofCheck; import com.tngtech.archunit.core.domain.testobjects.ComponentTypeDependency; import com.tngtech.archunit.core.domain.testobjects.DependenciesOnClassObjects; import com.tngtech.archunit.core.domain.testobjects.InterfaceForA; @@ -808,26 +814,159 @@ public void direct_dependencies_from_self_by_member_declarations() { JavaClass javaClass = importClasses(AhavingMembersOfTypeB.class, B.class).get(AhavingMembersOfTypeB.class); assertThat(javaClass.getDirectDependenciesFromSelf()) - .areAtLeastOne(methodReturnTypeDependency() + .areAtLeastOne(fieldTypeDependency() .from(AhavingMembersOfTypeB.class) .to(B.class) .inLineNumber(0)) - .areAtLeastOne(methodThrowsDeclarationDependency() + .areAtLeastOne(methodReturnTypeDependency() .from(AhavingMembersOfTypeB.class) - .to(B.BException.class) + .to(B.class) .inLineNumber(0)) .areAtLeast(2, parameterTypeDependency() + .from(AhavingMembersOfTypeB.class) + .to(B.class) + .inLineNumber(0)); + } + + @Test + public void direct_dependencies_to_self_by_member_declarations() { + JavaClass javaClass = importClassesWithContext(AhavingMembersOfTypeB.class, B.class).get(B.class); + + assertThat(javaClass.getDirectDependenciesToSelf()) + .areAtLeastOne(fieldTypeDependency() .from(AhavingMembersOfTypeB.class) .to(B.class) .inLineNumber(0)) - .areAtLeastOne(methodChecksInstanceOfDependency() + .areAtLeastOne(methodReturnTypeDependency() .from(AhavingMembersOfTypeB.class) .to(B.class) - .inLineNumber(7)) - .areAtLeastOne(methodChecksInstanceOfDependency() + .inLineNumber(0)) + .areAtLeast(2, parameterTypeDependency() .from(AhavingMembersOfTypeB.class) .to(B.class) - .inLineNumber(25)); + .inLineNumber(0)); + } + + @Test + public void direct_dependencies_from_self_by_instanceof_checks() { + JavaClass javaClass = importClasses(ClassWithDependencyOnInstanceofCheck.class, ClassWithDependencyOnInstanceofCheck.InstanceOfCheckTarget.class) + .get(ClassWithDependencyOnInstanceofCheck.class); + + assertThat(javaClass.getDirectDependenciesFromSelf()) + .areAtLeastOne(methodChecksInstanceOfDependency() + .from(ClassWithDependencyOnInstanceofCheck.class) + .to(ClassWithDependencyOnInstanceofCheck.InstanceOfCheckTarget.class) + .inLineNumber(6)) + .areAtLeastOne(methodChecksInstanceOfDependency() + .from(ClassWithDependencyOnInstanceofCheck.class) + .to(ClassWithDependencyOnInstanceofCheck.InstanceOfCheckTarget.class) + .inLineNumber(9)) + .areAtLeastOne(methodChecksInstanceOfDependency() + .from(ClassWithDependencyOnInstanceofCheck.class) + .to(ClassWithDependencyOnInstanceofCheck.InstanceOfCheckTarget.class) + .inLineNumber(13)); + } + + @Test + public void direct_dependencies_to_self_by_instanceof_checks() { + JavaClass javaClass = importClasses(ClassWithDependencyOnInstanceofCheck.class, ClassWithDependencyOnInstanceofCheck.InstanceOfCheckTarget.class) + .get(ClassWithDependencyOnInstanceofCheck.InstanceOfCheckTarget.class); + + assertThat(javaClass.getDirectDependenciesToSelf()) + .areAtLeastOne(methodChecksInstanceOfDependency() + .from(ClassWithDependencyOnInstanceofCheck.class) + .to(ClassWithDependencyOnInstanceofCheck.InstanceOfCheckTarget.class) + .inLineNumber(6)) + .areAtLeastOne(methodChecksInstanceOfDependency() + .from(ClassWithDependencyOnInstanceofCheck.class) + .to(ClassWithDependencyOnInstanceofCheck.InstanceOfCheckTarget.class) + .inLineNumber(9)) + .areAtLeastOne(methodChecksInstanceOfDependency() + .from(ClassWithDependencyOnInstanceofCheck.class) + .to(ClassWithDependencyOnInstanceofCheck.InstanceOfCheckTarget.class) + .inLineNumber(13)); + } + + @Test + public void direct_dependencies_from_self_by_catch_clauses() { + JavaClass javaClass = importClasses(ACatchingBException.class, BException1.class) + .get(ACatchingBException.class); + + assertThat(javaClass.getDirectDependenciesFromSelf()) + .areAtLeastOne(codeUnitTryCatchDependency() + .from(ACatchingBException.class) + .to(BException1.class) + .inLineNumber(7)) + .areAtLeastOne(codeUnitTryCatchDependency() + .from(ACatchingBException.class) + .to(BException1.class) + .inLineNumber(14)) + .areAtLeastOne(codeUnitTryCatchDependency() + .from(ACatchingBException.class) + .to(BException1.class) + .inLineNumber(21)); + } + + @Test + public void direct_dependencies_to_self_by_catch_clause() { + JavaClass javaClass = importClasses(ACatchingBException.class, BException1.class) + .get(BException1.class); + + assertThat(javaClass.getDirectDependenciesToSelf()) + .areAtLeastOne(codeUnitTryCatchDependency() + .from(ACatchingBException.class) + .to(BException1.class) + .inLineNumber(7)) + .areAtLeastOne(codeUnitTryCatchDependency() + .from(ACatchingBException.class) + .to(BException1.class) + .inLineNumber(14)) + .areAtLeastOne(codeUnitTryCatchDependency() + .from(ACatchingBException.class) + .to(BException1.class) + .inLineNumber(21)); + } + + @Test + public void direct_dependencies_from_self_by_throws_clause() { + JavaClass javaClass = importClasses(AThrowingBException.class, BException1.class, BException2.class, BException3.class) + .get(AThrowingBException.class); + + assertThat(javaClass.getDirectDependenciesFromSelf()) + .areAtLeastOne(methodThrowsDeclarationDependency() + .from(AThrowingBException.class) + .to(BException1.class) + .inLineNumber(0)) + .areAtLeastOne(methodThrowsDeclarationDependency() + .from(AThrowingBException.class) + .to(BException2.class) + .inLineNumber(0)) + .areAtLeastOne(methodThrowsDeclarationDependency() + .from(AThrowingBException.class) + .to(BException3.class) + .inLineNumber(0)); + } + + @DataProvider + public static Object[][] with_throws_dependencies() { + return $$( + $(BException1.class, AThrowingBException.class, 0), + $(BException2.class, AThrowingBException.class, 0), + $(BException3.class, AThrowingBException.class, 0) + ); + } + + @Test + @UseDataProvider("with_throws_dependencies") + public void direct_dependencies_to_self_by_throws_clause(Class selfClass, Class throwingClass, int expectedLineNumber) { + JavaClass javaClass = importClasses(selfClass, throwingClass) + .get(selfClass); + + assertThat(javaClass.getDirectDependenciesToSelf()) + .areAtLeastOne(methodThrowsDeclarationDependency() + .from(throwingClass) + .to(selfClass) + .inLineNumber(expectedLineNumber)); } @Test @@ -1282,42 +1421,6 @@ void method(List irrelevant, SecondGenericType> par ); } - @Test - public void direct_dependencies_to_self_by_member_declarations() { - JavaClass javaClass = importClassesWithContext(AhavingMembersOfTypeB.class, B.class).get(B.class); - - assertThat(javaClass.getDirectDependenciesToSelf()) - .areAtLeastOne(fieldTypeDependency() - .from(AhavingMembersOfTypeB.class) - .to(B.class) - .inLineNumber(0)) - .areAtLeastOne(methodReturnTypeDependency() - .from(AhavingMembersOfTypeB.class) - .to(B.class) - .inLineNumber(0)) - .areAtLeast(2, parameterTypeDependency() - .from(AhavingMembersOfTypeB.class) - .to(B.class) - .inLineNumber(0)) - .areAtLeastOne(methodChecksInstanceOfDependency() - .from(AhavingMembersOfTypeB.class) - .to(B.class) - .inLineNumber(7)) - .areAtLeastOne(methodChecksInstanceOfDependency() - .from(AhavingMembersOfTypeB.class) - .to(B.class) - .inLineNumber(25)); - - JavaClass exceptionClass = importClassesWithContext(AhavingMembersOfTypeB.class, B.BException.class) - .get(B.BException.class); - - assertThat(exceptionClass.getDirectDependenciesToSelf()) - .areAtLeastOne(methodThrowsDeclarationDependency() - .from(AhavingMembersOfTypeB.class) - .to(B.BException.class) - .inLineNumber(0)); - } - @Test public void direct_dependencies_from_self_by_references() { JavaClass javaClass = importClasses(AReferencingB.class, BReferencedByA.class).get(AReferencingB.class); @@ -2101,6 +2204,10 @@ private static DependencyConditionCreation methodThrowsDeclarationDependency() { return new DependencyConditionCreation("throws type"); } + private static DependencyConditionCreation codeUnitTryCatchDependency() { + return new DependencyConditionCreation("catches type"); + } + private static DependencyConditionCreation methodChecksInstanceOfDependency() { return new DependencyConditionCreation("checks instanceof"); } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ACatchingBException.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ACatchingBException.java new file mode 100644 index 0000000000..3ca7c5f86d --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ACatchingBException.java @@ -0,0 +1,25 @@ +package com.tngtech.archunit.core.domain.testobjects; + +@SuppressWarnings("unused") +public class ACatchingBException { + static { + try { + new AThrowingBException(); + } catch (BException1 e) { + } + } + + public ACatchingBException() { + try { + new AThrowingBException(); + } catch (BException1 e) { + } + } + + void method() { + try { + new AThrowingBException(); + } catch (BException1 e) { + } + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/AThrowingBException.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/AThrowingBException.java new file mode 100644 index 0000000000..6e50b8463f --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/AThrowingBException.java @@ -0,0 +1,16 @@ +package com.tngtech.archunit.core.domain.testobjects; + +@SuppressWarnings({"unused"}) +public class AThrowingBException { + public AThrowingBException() throws BException1 { + throw new BException1(); + } + + static void throwingBException2() throws BException2 { + throw new BException2(); + } + + public void throwingBException3() throws BException3 { + throw new BException3(); + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/AhavingMembersOfTypeB.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/AhavingMembersOfTypeB.java index 3afa6159b8..d5ee125be4 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/AhavingMembersOfTypeB.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/AhavingMembersOfTypeB.java @@ -1,12 +1,11 @@ package com.tngtech.archunit.core.domain.testobjects; @DomainAnnotation -@SuppressWarnings({"RedundantThrows", "unused"}) +@SuppressWarnings("unused") public class AhavingMembersOfTypeB { private B b; - private boolean staticInitializerInstanceofCheck = new Object() instanceof B; - public AhavingMembersOfTypeB(B b) throws B.BException { + public AhavingMembersOfTypeB(B b) { this.b = b; } @@ -16,12 +15,4 @@ B methodReturningB() { void methodWithParameterTypeB(String some, B b) { } - - void throwingBException() throws B.BException { - - } - - void checkingInstanceOfB() { - boolean check = new Object() instanceof B; - } } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/B.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/B.java index 02da744bbc..3c2cb3a41e 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/B.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/B.java @@ -6,8 +6,4 @@ public class B { void call() { } - - public static class BException extends Exception { - - } } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/BException1.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/BException1.java new file mode 100644 index 0000000000..72b4d937fe --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/BException1.java @@ -0,0 +1,4 @@ +package com.tngtech.archunit.core.domain.testobjects; + +public class BException1 extends Exception { +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/BException2.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/BException2.java new file mode 100644 index 0000000000..3c927e854a --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/BException2.java @@ -0,0 +1,4 @@ +package com.tngtech.archunit.core.domain.testobjects; + +public class BException2 extends Exception { +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/BException3.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/BException3.java new file mode 100644 index 0000000000..97c8f21770 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/BException3.java @@ -0,0 +1,4 @@ +package com.tngtech.archunit.core.domain.testobjects; + +public class BException3 extends Exception { +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ClassWithDependencyOnCaughtException.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ClassWithDependencyOnCaughtException.java new file mode 100644 index 0000000000..6ee27fabde --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/ClassWithDependencyOnCaughtException.java @@ -0,0 +1,43 @@ +package com.tngtech.archunit.core.domain.testobjects; + +import java.io.IOException; + +@SuppressWarnings("unused") +public class ClassWithDependencyOnCaughtException { + static { + try { + throw new IOException(); + } catch (IOException e) { + } + } + + ClassWithDependencyOnCaughtException() { + try { + throw new IOException(); + } catch (IOException e) { + } + } + + void simpleCatchClauseMethod() { + try { + throw new IOException(); + } catch (IOException e) { + } + } + + void unionCatchClauseMethod() { + try { + throw new IOException(); + } catch (IllegalStateException | IOException e) { + } + } + + void multipleCatchClausesMethod() { + try { + throw new IOException(); + } catch (IllegalStateException e) { + } catch (RuntimeException e) { + } catch (IOException e) { + } + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAccessesTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAccessesTest.java index bfd12c55ea..6f191e7662 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAccessesTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAccessesTest.java @@ -83,6 +83,7 @@ import com.tngtech.archunit.core.importer.testexamples.integration.ClassXDependingOnClassesABCD; import com.tngtech.archunit.core.importer.testexamples.integration.InterfaceOfClassX; import com.tngtech.archunit.core.importer.testexamples.specialtargets.ClassCallingSpecialTarget; +import com.tngtech.archunit.core.importer.testexamples.trycatch.CatchClauseTargetException; import com.tngtech.archunit.core.importer.testexamples.trycatch.ClassHoldingMethods; import com.tngtech.archunit.core.importer.testexamples.trycatch.ClassWithComplexTryCatchBlocks; import com.tngtech.archunit.core.importer.testexamples.trycatch.ClassWithSimpleTryCatchBlocks; @@ -890,7 +891,7 @@ public void imports_complex_nested_try_catch_blocks() { .atLocation(ClassWithComplexTryCatchBlocks.class, 14) ).areExactly(1, tryCatchBlock() .declaredIn(constructor) - .catching(IllegalArgumentException.class, UnsupportedOperationException.class) + .catching(IllegalArgumentException.class, UnsupportedOperationException.class, CatchClauseTargetException.class) .atLocation(ClassWithComplexTryCatchBlocks.class, 17) ).areExactly(1, tryCatchBlock() .declaredIn(constructor) diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterMembersTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterMembersTest.java index 9661deb159..36185d82bb 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterMembersTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterMembersTest.java @@ -43,6 +43,9 @@ import com.tngtech.archunit.core.importer.testexamples.methodimport.ClassWithStringStringMethod; import com.tngtech.archunit.core.importer.testexamples.methodimport.ClassWithThrowingMethod; import com.tngtech.archunit.core.importer.testexamples.referencedclassobjects.ReferencingClassObjects; +import com.tngtech.archunit.core.importer.testexamples.trycatch.CatchClauseTargetException; +import com.tngtech.archunit.core.importer.testexamples.trycatch.ClassWithComplexTryCatchBlocks; +import com.tngtech.archunit.core.importer.testexamples.trycatch.ClassWithSimpleTryCatchBlocks; import com.tngtech.archunit.testutil.assertion.ReferencedClassObjectsAssertion.ExpectedReferencedClassObject; import org.assertj.core.util.Objects; import org.junit.Test; @@ -359,6 +362,20 @@ public void classes_know_which_constructor_throws_clauses_contain_their_type() { assertThat(classes.get(FirstCheckedException.class).getMethodThrowsDeclarationsWithTypeOfSelf()).isEmpty(); } + @Test + public void non_throwable_classes_report_that_no_method_throws_clause_contains_their_type() { + JavaClass stringClass = new ClassFileImporter().importClass(String.class); + + assertThat(stringClass.getMethodThrowsDeclarationsWithTypeOfSelf()).isEmpty(); + } + + @Test + public void non_throwable_classes_report_that_no_constructor_throws_clause_contains_their_type() { + JavaClass stringClass = new ClassFileImporter().importClass(String.class); + + assertThat(stringClass.getConstructorsWithThrowsDeclarationTypeOfSelf()).isEmpty(); + } + @Test public void classes_know_which_instanceof_checks_check_their_type() { JavaClass clazz = new ClassFileImporter().importPackagesOf(InstanceofChecked.class).get(InstanceofChecked.class); @@ -373,4 +390,25 @@ public void classes_know_which_instanceof_checks_check_their_type() { ChecksInstanceofInStaticInitializer.class, ChecksMultipleInstanceofs.class); } + + @Test + public void classes_know_which_catch_clauses_contain_their_type() { + JavaClass clazz = new ClassFileImporter().importPackagesOf(CatchClauseTargetException.class).get(CatchClauseTargetException.class); + + Set origins = clazz.getTryCatchBlocksThatCatchSelf().stream() + .map(tryCatchBlock -> tryCatchBlock.getOwner().getOwner()) + .collect(toSet()); + + assertThatTypes(origins).matchInAnyOrder( + ClassWithComplexTryCatchBlocks.class, + ClassWithSimpleTryCatchBlocks.class); + } + + @Test + public void non_throwable_classes_report_that_no_catch_clause_contains_their_type() { + JavaClass stringClass = new ClassFileImporter().importClass(String.class); + + assertThat(stringClass.getTryCatchBlocksThatCatchSelf()).isEmpty(); + } + } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/CatchClauseTargetException.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/CatchClauseTargetException.java new file mode 100644 index 0000000000..4f5fdcb3c5 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/CatchClauseTargetException.java @@ -0,0 +1,4 @@ +package com.tngtech.archunit.core.importer.testexamples.trycatch; + +public class CatchClauseTargetException extends RuntimeException { +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/ClassWithComplexTryCatchBlocks.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/ClassWithComplexTryCatchBlocks.java index 109ba674e3..33a58c8818 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/ClassWithComplexTryCatchBlocks.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/ClassWithComplexTryCatchBlocks.java @@ -15,7 +15,7 @@ public class ClassWithComplexTryCatchBlocks { someOtherObject.toString(); try { instanceOne.doSomething(); - } catch (IllegalArgumentException | UnsupportedOperationException e) { + } catch (IllegalArgumentException | UnsupportedOperationException | CatchClauseTargetException e) { } } catch (Exception e) { } finally { diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/ClassWithSimpleTryCatchBlocks.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/ClassWithSimpleTryCatchBlocks.java index 9ce2581c6f..d903cb76a0 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/ClassWithSimpleTryCatchBlocks.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/ClassWithSimpleTryCatchBlocks.java @@ -15,6 +15,8 @@ Object method() { System.out.println("Error1"); } catch (IllegalArgumentException e) { System.out.println("Error2"); + } catch (CatchClauseTargetException e) { + System.out.println("Error3"); } finally { System.out.println("finally"); } diff --git a/archunit/src/test/java/com/tngtech/archunit/library/LayeredArchitectureTest.java b/archunit/src/test/java/com/tngtech/archunit/library/LayeredArchitectureTest.java index f60adc3014..aed169d487 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/LayeredArchitectureTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/LayeredArchitectureTest.java @@ -22,12 +22,14 @@ import com.tngtech.archunit.library.testclasses.dependencysettings.forbidden_forwards.DependencySettingsForbiddenByMayOnlyAccess; import com.tngtech.archunit.library.testclasses.dependencysettings.origin.DependencySettingsOriginClass; import com.tngtech.archunit.library.testclasses.dependencysettings_outside.DependencySettingsOutsideOfLayersBeingAccessedByLayers; +import com.tngtech.archunit.library.testclasses.first.any.pkg.ClassWithCatch; import com.tngtech.archunit.library.testclasses.first.any.pkg.FirstAnyPkgClass; import com.tngtech.archunit.library.testclasses.first.three.any.FirstThreeAnyClass; import com.tngtech.archunit.library.testclasses.mayonlyaccesslayers.forbidden.MayOnlyAccessLayersForbiddenClass; import com.tngtech.archunit.library.testclasses.mayonlyaccesslayers.origin.MayOnlyAccessLayersOriginClass; import com.tngtech.archunit.library.testclasses.second.three.any.SecondThreeAnyClass; import com.tngtech.archunit.library.testclasses.some.pkg.SomePkgClass; +import com.tngtech.archunit.library.testclasses.some.pkg.SomePkgException; import com.tngtech.archunit.library.testclasses.some.pkg.sub.SomePkgSubclass; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; @@ -213,7 +215,8 @@ public void layered_architecture_gathers_all_layer_violations(LayeredArchitectur expectedAccessViolationPattern(FirstThreeAnyClass.class, "call", FirstAnyPkgClass.class, "callMe"), expectedFieldTypePattern(FirstAnyPkgClass.class, "illegalTarget", SomePkgSubclass.class), expectedFieldTypePattern(FirstThreeAnyClass.class, "illegalTarget", FirstAnyPkgClass.class), - expectedFieldTypePattern(SecondThreeAnyClass.class, "illegalTarget", SomePkgClass.class))); + expectedFieldTypePattern(SecondThreeAnyClass.class, "illegalTarget", SomePkgClass.class), + expectedCatchPattern(ClassWithCatch.class, "method", SomePkgException.class))); } @DataProvider @@ -490,6 +493,10 @@ private static String expectedInheritancePattern(Class child, Class parent return String.format("Class .*%s.* extends class .*.%s.*", child.getSimpleName(), parent.getSimpleName()); } + static String expectedCatchPattern(Class from, String fromMethod, Class to) { + return String.format(".*%s.%s().*catches type.*%s.*", quote(from.getName()), fromMethod, quote(to.getName())); + } + static String expectedEmptyLayerPattern(String layerName) { return String.format("Layer '%s' is empty", layerName); } diff --git a/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SliceTest.java b/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SliceTest.java index 13b953b45d..045a21d5ec 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SliceTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/dependencies/SliceTest.java @@ -3,10 +3,12 @@ import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.library.testclasses.first.any.pkg.ClassOnlyDependentOnOwnPackageAndObject; +import com.tngtech.archunit.library.testclasses.first.any.pkg.ClassWithCatch; import com.tngtech.archunit.library.testclasses.first.any.pkg.FirstAnyPkgClass; import com.tngtech.archunit.library.testclasses.first.three.any.FirstThreeAnyClass; import com.tngtech.archunit.library.testclasses.second.any.pkg.SecondAnyClass; import com.tngtech.archunit.library.testclasses.second.three.any.SecondThreeAnyClass; +import com.tngtech.archunit.library.testclasses.some.pkg.SomePkgException; import com.tngtech.archunit.library.testclasses.some.pkg.sub.SomePkgSubclass; import org.junit.Test; @@ -26,6 +28,8 @@ public void dependencies_from_self() { .from(ClassOnlyDependentOnOwnPackageAndObject.class).to(Object.class) .from(FirstThreeAnyClass.class).to(Object.class) .from(FirstThreeAnyClass.class).to(SecondThreeAnyClass.class) + .from(ClassWithCatch.class).to(Object.class) + .from(ClassWithCatch.class).to(SomePkgException.class) ); } diff --git a/archunit/src/test/java/com/tngtech/archunit/library/testclasses/first/any/pkg/ClassWithCatch.java b/archunit/src/test/java/com/tngtech/archunit/library/testclasses/first/any/pkg/ClassWithCatch.java new file mode 100644 index 0000000000..dcd5196693 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/library/testclasses/first/any/pkg/ClassWithCatch.java @@ -0,0 +1,15 @@ +package com.tngtech.archunit.library.testclasses.first.any.pkg; + +import com.tngtech.archunit.library.testclasses.some.pkg.SomePkgException; + +public class ClassWithCatch { + void method() { + try { + callForTry(); + } catch (SomePkgException e) { + } + } + + void callForTry() { + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/library/testclasses/some/pkg/SomePkgException.java b/archunit/src/test/java/com/tngtech/archunit/library/testclasses/some/pkg/SomePkgException.java new file mode 100644 index 0000000000..5ec343550e --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/library/testclasses/some/pkg/SomePkgException.java @@ -0,0 +1,4 @@ +package com.tngtech.archunit.library.testclasses.some.pkg; + +public class SomePkgException extends RuntimeException { +}