From 66d97c5b50956dcbd1feedf36b2591c62dc07c43 Mon Sep 17 00:00:00 2001 From: Manfred Hanke Date: Tue, 15 Dec 2020 22:20:51 +0100 Subject: [PATCH] initialize non-imported JavaClass with empty dependencies So far, if a user would call `JavaClass.getDirectDependency{From/To}Self()` on a class that was not directly imported (e.g. a class that was not present in the original import, but accessed by a class from that import) it would cause a NPE. While we do want to cut the class graph at that point (i.e. not further track dependencies for classes that are not part of the import), we do not want this exceptional behavior. This change fixes the behavior and sets empty dependencies for all non-directly imported classes, be it classes that have been resolved from the classpath later on, or stub classes, because further resolution has been turned off. Resolves: #397 Signed-off-by: Manfred Hanke --- .../archunit/core/domain/JavaClass.java | 4 +- .../core/importer/ClassFileImporterTest.java | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java index 33666da087..e81382ac25 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 @@ -145,8 +145,8 @@ public Set get() { .build(); } }); - private JavaClassDependencies javaClassDependencies; - private ReverseDependencies reverseDependencies; + private JavaClassDependencies javaClassDependencies = new JavaClassDependencies(this); // just for stubs; will be overwritten for imported classes + private ReverseDependencies reverseDependencies = ReverseDependencies.EMPTY; // just for stubs; will be overwritten for imported classes JavaClass(JavaClassBuilder builder) { source = checkNotNull(builder.getSource()); diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java index 2cfd6e1a9e..e95aa75ac4 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java @@ -205,6 +205,8 @@ import static com.tngtech.archunit.testutil.ReflectionTestUtils.field; import static com.tngtech.archunit.testutil.ReflectionTestUtils.method; import static com.tngtech.archunit.testutil.TestUtils.namesOf; +import static com.tngtech.java.junit.dataprovider.DataProviders.$; +import static com.tngtech.java.junit.dataprovider.DataProviders.$$; import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assume.assumeTrue; @@ -1711,6 +1713,48 @@ public void resolve_missing_dependencies_from_classpath_can_be_toogled() throws assertThat(clazz.getSuperClass().get().getMethods()).isEmpty(); } + @DataProvider + public static Object[][] classes_not_directly_imported() { + class Element { + } + @SuppressWarnings("unused") + class DependsOnArray { + Element[] array; + } + ArchConfiguration.get().setResolveMissingDependenciesFromClassPath(true); + JavaClass resolvedFromClasspath = new ClassFileImporter().importClasses(DependsOnArray.class) + .get(DependsOnArray.class).getField("array").getRawType().getComponentType(); + + ArchConfiguration.get().setResolveMissingDependenciesFromClassPath(false); + JavaClass stub = new ClassFileImporter().importClasses(DependsOnArray.class) + .get(DependsOnArray.class).getField("array").getRawType().getComponentType(); + + return $$( + $("Resolved from classpath", resolvedFromClasspath), + $("Stub class", stub) + ); + } + + @Test + @UseDataProvider("classes_not_directly_imported") + public void classes_not_directly_imported_have_empty_dependencies(@SuppressWarnings("unused") String description, JavaClass notDirectlyImported) { + assertThat(notDirectlyImported.getDirectDependenciesFromSelf()).isEmpty(); + assertThat(notDirectlyImported.getDirectDependenciesToSelf()).isEmpty(); + assertThat(notDirectlyImported.getFieldAccessesToSelf()).isEmpty(); + assertThat(notDirectlyImported.getMethodCallsToSelf()).isEmpty(); + assertThat(notDirectlyImported.getConstructorCallsToSelf()).isEmpty(); + assertThat(notDirectlyImported.getAccessesToSelf()).isEmpty(); + assertThat(notDirectlyImported.getFieldsWithTypeOfSelf()).isEmpty(); + assertThat(notDirectlyImported.getMethodsWithParameterTypeOfSelf()).isEmpty(); + assertThat(notDirectlyImported.getMethodsWithReturnTypeOfSelf()).isEmpty(); + assertThat(notDirectlyImported.getMethodThrowsDeclarationsWithTypeOfSelf()).isEmpty(); + assertThat(notDirectlyImported.getConstructorsWithParameterTypeOfSelf()).isEmpty(); + assertThat(notDirectlyImported.getConstructorsWithThrowsDeclarationTypeOfSelf()).isEmpty(); + assertThat(notDirectlyImported.getAnnotationsWithTypeOfSelf()).isEmpty(); + assertThat(notDirectlyImported.getAnnotationsWithParameterTypeOfSelf()).isEmpty(); + assertThat(notDirectlyImported.getInstanceofChecksWithTypeOfSelf()).isEmpty(); + } + @Test public void import_is_resilient_against_broken_class_files() throws Exception { Class expectedClass = getClass();