Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -51,6 +52,7 @@
* <li>a class (or method/constructor of this class) declares a type parameter referencing another class</li>
* <li>a class (or method/constructor of this class) is annotated with an annotation of a certain type or referencing another class as annotation parameter</li>
* <li>a method/constructor of a class references another class in a throws declaration</li>
* <li>a class references another class in a {@code catch} clause</li>
* <li>a class references another class object (e.g. {@code Example.class})</li>
* <li>a class references another class in an {@code instanceof} check</li>
* </ul>
Expand Down Expand Up @@ -125,6 +127,12 @@ static Set<Dependency> tryCreateFromThrowsDeclaration(ThrowsDeclaration<? extend
return tryCreateDependency(declaration.getLocation(), "throws type", declaration.getRawType());
}

static Set<Dependency> tryCreateFromTryCatchBlock(TryCatchBlock tryCatchBlock) {
return tryCatchBlock.getCaughtThrowables().stream()
.flatMap(caughtThrowable -> tryCreateDependency(tryCatchBlock.getOwner(), "catches type", caughtThrowable, tryCatchBlock.getSourceCodeLocation()).stream())
.collect(Collectors.toSet());
}

static Set<Dependency> tryCreateFromInstanceofCheck(InstanceofCheck instanceofCheck) {
return tryCreateDependency(
instanceofCheck.getOwner(), "checks instanceof",
Expand Down Expand Up @@ -269,6 +277,10 @@ public String getDescription() {
return description;
}

/**
* @implNote For dependencies created by {@code catch} clauses, the line number reported <i>currently</i> refers to
* the first access in the {@code try} block, not to the {@code catch} clause.
*/
@Override
@PublicAPI(usage = ACCESS)
public SourceCodeLocation getSourceCodeLocation() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,11 @@ public Set<InstanceofCheck> getInstanceofChecks() {
return members.getInstanceofChecks();
}

@PublicAPI(usage = ACCESS)
public Set<TryCatchBlock> getTryCatchBlocks() {
return members.getTryCatchBlocks();
}

@PublicAPI(usage = ACCESS)
public Set<ReferencedClassObject> getReferencedClassObjects() {
return members.getReferencedClassObjects();
Expand Down Expand Up @@ -1310,6 +1315,14 @@ public Set<ThrowsDeclaration<JavaMethod>> getMethodThrowsDeclarationsWithTypeOfS
return reverseDependencies.getMethodThrowsDeclarationsWithTypeOf(this);
}

/**
* @return {@link TryCatchBlock TryCatchBlocks} of all imported classes that declare to catch this class.
*/
@PublicAPI(usage = ACCESS)
public Set<TryCatchBlock> getTryCatchBlocksThatCatchSelf() {
return reverseDependencies.getTryCatchBlocksThatCatch(this);
}

/**
* @return Constructors of all imported classes that have a parameter type of this class.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ private Supplier<Set<Dependency>> createDirectDependenciesFromClassSupplier() {
returnTypeDependenciesFromSelf(),
codeUnitParameterDependenciesFromSelf(),
throwsDeclarationDependenciesFromSelf(),
tryCatchBlockDependenciesFromSelf(),
annotationDependenciesFromSelf(),
instanceofCheckDependenciesFromSelf(),
referencedClassObjectDependenciesFromSelf(),
Expand Down Expand Up @@ -166,6 +167,11 @@ private Stream<Dependency> throwsDeclarationDependenciesFromSelf() {
.flatMap(throwsDeclaration -> Dependency.tryCreateFromThrowsDeclaration(throwsDeclaration).stream());
}

private Stream<Dependency> tryCatchBlockDependenciesFromSelf() {
return javaClass.getTryCatchBlocks().stream()
.flatMap(tryCatchBlock -> Dependency.tryCreateFromTryCatchBlock(tryCatchBlock).stream());
}

private Stream<Dependency> annotationDependenciesFromSelf() {
return Streams.concat(
annotationDependencies(javaClass),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,14 @@ Set<InstanceofCheck> getInstanceofChecks() {
return result.build();
}

Set<TryCatchBlock> getTryCatchBlocks() {
ImmutableSet.Builder<TryCatchBlock> result = ImmutableSet.builder();
for (JavaCodeUnit codeUnit : codeUnits) {
result.addAll(codeUnit.getTryCatchBlocks());
}
return result.build();
}

Set<ReferencedClassObject> getReferencedClassObjects() {
ImmutableSet.Builder<ReferencedClassObject> result = ImmutableSet.builder();
for (JavaCodeUnit codeUnit : codeUnits) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ final class ReverseDependencies {
private final SetMultimap<JavaClass, JavaMethod> methodParameterTypeDependencies;
private final SetMultimap<JavaClass, JavaMethod> methodReturnTypeDependencies;
private final SetMultimap<JavaClass, ThrowsDeclaration<JavaMethod>> methodsThrowsDeclarationDependencies;
private final SetMultimap<JavaClass, TryCatchBlock> tryCatchBlockDependencies;
private final SetMultimap<JavaClass, JavaConstructor> constructorParameterTypeDependencies;
private final SetMultimap<JavaClass, ThrowsDeclaration<JavaConstructor>> constructorThrowsDeclarationDependencies;
private final SetMultimap<JavaClass, JavaAnnotation<?>> annotationTypeDependencies;
Expand All @@ -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();
Expand Down Expand Up @@ -114,6 +116,10 @@ Set<ThrowsDeclaration<JavaMethod>> getMethodThrowsDeclarationsWithTypeOf(JavaCla
return methodsThrowsDeclarationDependencies.get(clazz);
}

Set<TryCatchBlock> getTryCatchBlocksThatCatch(JavaClass clazz) {
return tryCatchBlockDependencies.get(clazz);
}

Set<JavaConstructor> getConstructorsWithParameterTypeOf(JavaClass clazz) {
return constructorParameterTypeDependencies.get(clazz);
}
Expand Down Expand Up @@ -150,6 +156,7 @@ static class Creation {
private final ImmutableSetMultimap.Builder<JavaClass, JavaMethod> methodParameterTypeDependencies = ImmutableSetMultimap.builder();
private final ImmutableSetMultimap.Builder<JavaClass, JavaMethod> methodReturnTypeDependencies = ImmutableSetMultimap.builder();
private final ImmutableSetMultimap.Builder<JavaClass, ThrowsDeclaration<JavaMethod>> methodsThrowsDeclarationDependencies = ImmutableSetMultimap.builder();
private final ImmutableSetMultimap.Builder<JavaClass, TryCatchBlock> tryCatchBlockDependencies = ImmutableSetMultimap.builder();
private final ImmutableSetMultimap.Builder<JavaClass, JavaConstructor> constructorParameterTypeDependencies = ImmutableSetMultimap.builder();
private final ImmutableSetMultimap.Builder<JavaClass, ThrowsDeclaration<JavaConstructor>> constructorThrowsDeclarationDependencies = ImmutableSetMultimap.builder();
private final ImmutableSetMultimap.Builder<JavaClass, JavaAnnotation<?>> annotationTypeDependencies = ImmutableSetMultimap.builder();
Expand Down Expand Up @@ -200,6 +207,11 @@ private void registerMethods(JavaClass clazz) {
for (ThrowsDeclaration<JavaMethod> 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);
}
Expand All @@ -214,6 +226,11 @@ private void registerConstructors(JavaClass clazz) {
for (ThrowsDeclaration<JavaConstructor> 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);
}
Expand Down Expand Up @@ -252,11 +269,16 @@ private Set<JavaAnnotation<?>> 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<JavaClass> classes) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Dependency> catchesType(Class<? extends Throwable> 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<Dependency> 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<Dependency> 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)
Expand Down
Loading