From 81cfe2d338dc2fbbe3fec7c98fe5560b11e9eea4 Mon Sep 17 00:00:00 2001 From: John Burns Date: Wed, 10 Dec 2025 15:06:55 -0600 Subject: [PATCH] Allow package info to be resolved as a dependency fix for #1564 exclude package-info classes from package metrics calculations Signed-off-by: John Burns --- .../domain/DomainObjectCreationContext.java | 4 +++ .../archunit/core/domain/JavaClass.java | 23 ++++++++++++- .../core/importer/ClassFileProcessor.java | 6 +++- .../core/importer/ClassGraphCreator.java | 2 ++ .../importer/DependencyResolutionProcess.java | 11 ++++++ .../library/metrics/MetricsComponents.java | 8 ++++- .../archunit/core/domain/JavaClassTest.java | 11 ++++++ .../annotated/WithinAnnotatedPackage.java | 4 +++ ...ssFileImporterAutomaticResolutionTest.java | 34 +++++++++++++++++++ .../ClassThatReferencesOtherClass.java | 7 ++++ 10 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/WithinAnnotatedPackage.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/packageinforesolution/ClassThatReferencesOtherClass.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 f8cbdb9ce9..9417fcd883 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 @@ -76,6 +76,10 @@ public static void completeClassHierarchy(JavaClass javaClass, ImportContext imp javaClass.completeClassHierarchyFrom(importContext); } + public static void completePackage(JavaClass javaClass, ImportContext importContext) { + javaClass.completePackageInfoFrom(importContext); + } + public static void completeEnclosingDeclaration(JavaClass javaClass, ImportContext importContext) { javaClass.completeEnclosingDeclarationFrom(importContext); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java index e4480a4dae..0be174dbb8 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 @@ -16,6 +16,7 @@ package com.tngtech.archunit.core.domain; import java.lang.annotation.Annotation; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -1429,6 +1430,13 @@ void completeClassHierarchyFrom(ImportContext context) { completionProcess.markClassHierarchyComplete(); } + void completePackageInfoFrom(ImportContext context) { + this.javaPackage = JavaPackage.from(Arrays.asList( + this, + context.resolveClass(this.getPackageName() + ".package-info"))); + completionProcess.markPackageComplete(); + } + private void completeSuperclassFrom(ImportContext context) { Optional rawSuperclass = context.createSuperclass(this); if (rawSuperclass.isPresent()) { @@ -1663,6 +1671,10 @@ public void markAnnotationsComplete() { @Override public void markDependenciesComplete() { } + + @Override + public void markPackageComplete() { + } }; abstract boolean hasFinished(); @@ -1683,6 +1695,8 @@ public void markDependenciesComplete() { public abstract void markDependenciesComplete(); + public abstract void markPackageComplete(); + static CompletionProcess start() { return new FullCompletionProcess(); } @@ -1701,6 +1715,7 @@ private static class FullCompletionProcess extends CompletionProcess { private boolean membersComplete = false; private boolean annotationsComplete = false; private boolean dependenciesComplete = false; + private boolean packageComplete = false; @Override boolean hasFinished() { @@ -1711,7 +1726,8 @@ boolean hasFinished() { && genericInterfacesComplete && membersComplete && annotationsComplete - && dependenciesComplete; + && dependenciesComplete + && packageComplete; } @Override @@ -1753,6 +1769,11 @@ public void markAnnotationsComplete() { public void markDependenciesComplete() { this.dependenciesComplete = true; } + + @Override + public void markPackageComplete() { + this.packageComplete = true; + } } /** 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 a3762bdaf3..51467a2158 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 @@ -99,7 +99,11 @@ public void onNewClass(String className, Optional superclassName, List currentTypeNames = new HashSet<>(); private int runNumber = 1; private boolean shouldContinue; @@ -116,6 +121,12 @@ void registerGenericSignatureType(String typeName) { } } + void registerPackageInfo(String typeName) { + if (runNumberHasNotExceeded(maxRunsForPackageInfo)) { + currentTypeNames.add(typeName); + } + } + void resolve(ImportedClasses classes) { logConfiguration(); do { diff --git a/archunit/src/main/java/com/tngtech/archunit/library/metrics/MetricsComponents.java b/archunit/src/main/java/com/tngtech/archunit/library/metrics/MetricsComponents.java index 185096d340..9001a114b7 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/metrics/MetricsComponents.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/metrics/MetricsComponents.java @@ -19,6 +19,7 @@ import java.util.Map; import java.util.Optional; import java.util.function.Function; +import java.util.stream.Collectors; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableSet; @@ -121,7 +122,12 @@ public static MetricsComponents from(Collection elements, Function fromPackages(Collection packages) { ImmutableSet.Builder> components = ImmutableSet.builder(); for (JavaPackage javaPackage : packages) { - components.add(MetricsComponent.of(javaPackage.getName(), javaPackage.getClassesInPackageTree())); + components.add(MetricsComponent.of( + javaPackage.getName(), + javaPackage.getClassesInPackageTree().stream() + .filter(jc -> !jc.getSimpleName().equals("package-info")) + .collect(Collectors.toList()) + )); } return MetricsComponents.of(components.build()); } 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..2a620fb20f 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 @@ -25,6 +25,8 @@ import com.tngtech.archunit.base.ArchUnitException.InvalidSyntaxUsageException; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.base.HasDescription; +import com.tngtech.archunit.core.domain.packageexamples.annotated.PackageLevelAnnotation; +import com.tngtech.archunit.core.domain.packageexamples.annotated.WithinAnnotatedPackage; import com.tngtech.archunit.core.domain.testobjects.AAccessingB; import com.tngtech.archunit.core.domain.testobjects.AExtendingSuperAImplementingInterfaceForA; import com.tngtech.archunit.core.domain.testobjects.AReferencingB; @@ -1595,6 +1597,15 @@ public void function_getPackage() { .isEqualTo(javaClass.getPackageName()); } + @Test + public void function_getPackageInfo() { + JavaClass javaClass = importClassWithContext(WithinAnnotatedPackage.class); + + assertThat(javaClass.getPackage().isAnnotatedWith(PackageLevelAnnotation.class)) + .as("package info is available") + .isTrue(); + } + @Test public void functions_get_members() { JavaClass javaClass = importClassWithContext(ClassWithSeveralConstructorsFieldsAndMethods.class); diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/WithinAnnotatedPackage.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/WithinAnnotatedPackage.java new file mode 100644 index 0000000000..4e225eca9b --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/packageexamples/annotated/WithinAnnotatedPackage.java @@ -0,0 +1,4 @@ +package com.tngtech.archunit.core.domain.packageexamples.annotated; + +public class WithinAnnotatedPackage { +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAutomaticResolutionTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAutomaticResolutionTest.java index c394871fb6..42ca32f232 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAutomaticResolutionTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterAutomaticResolutionTest.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Optional; import java.util.Map; import java.util.function.Supplier; @@ -22,10 +23,12 @@ import com.tngtech.archunit.core.domain.JavaConstructorCall; import com.tngtech.archunit.core.domain.JavaConstructorReference; import com.tngtech.archunit.core.domain.JavaEnumConstant; +import com.tngtech.archunit.core.domain.JavaField; import com.tngtech.archunit.core.domain.JavaFieldAccess; import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.core.domain.JavaMethodCall; import com.tngtech.archunit.core.domain.JavaMethodReference; +import com.tngtech.archunit.core.domain.JavaPackage; import com.tngtech.archunit.core.domain.JavaParameterizedType; import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.JavaWildcardType; @@ -33,6 +36,7 @@ import com.tngtech.archunit.core.domain.ThrowsDeclaration; import com.tngtech.archunit.core.domain.properties.HasAnnotations; import com.tngtech.archunit.core.importer.DependencyResolutionProcessTestUtils.ImporterWithAdjustedResolutionRuns; +import com.tngtech.archunit.core.importer.testexamples.OtherClass; import com.tngtech.archunit.core.importer.testexamples.SomeAnnotation; import com.tngtech.archunit.core.importer.testexamples.annotatedclassimport.ClassWithUnimportedAnnotation; import com.tngtech.archunit.core.importer.testexamples.annotatedparameters.ClassWithMethodWithAnnotatedParameters; @@ -44,9 +48,11 @@ import com.tngtech.archunit.core.importer.testexamples.annotationresolution.SomeAnnotationWithAnnotationParameter; import com.tngtech.archunit.core.importer.testexamples.annotationresolution.SomeAnnotationWithClassParameter; import com.tngtech.archunit.core.importer.testexamples.classhierarchyresolution.Child; +import com.tngtech.archunit.core.importer.testexamples.packageinforesolution.ClassThatReferencesOtherClass; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; +import org.assertj.core.api.Assertions; import org.junit.Test; import org.junit.runner.RunWith; @@ -59,6 +65,7 @@ import static com.tngtech.archunit.core.importer.DependencyResolutionProcess.MAX_ITERATIONS_FOR_GENERIC_SIGNATURE_TYPES_PROPERTY_NAME; import static com.tngtech.archunit.core.importer.DependencyResolutionProcess.MAX_ITERATIONS_FOR_MEMBER_TYPES_PROPERTY_NAME; import static com.tngtech.archunit.core.importer.DependencyResolutionProcess.MAX_ITERATIONS_FOR_SUPERTYPES_PROPERTY_NAME; +import static com.tngtech.archunit.core.importer.DependencyResolutionProcess.MAX_ITERATIONS_FOR_PACKAGE_INFO_PROPERTY_NAME; import static com.tngtech.archunit.core.importer.testexamples.SomeEnum.OTHER_VALUE; import static com.tngtech.archunit.core.importer.testexamples.SomeEnum.SOME_VALUE; import static com.tngtech.archunit.core.importer.testexamples.annotatedparameters.ClassWithMethodWithAnnotatedParameters.methodWithOneAnnotatedParameterWithTwoAnnotations; @@ -620,6 +627,33 @@ class Innermost { assertThatType(outermost).matches(Outermost.class); } + @Test + public void automatically_resolves_packages() { + JavaClass otherClass = new ClassFileImporter() + .importClass(OtherClass.class); + + JavaPackage checkedClassPackageInfo = otherClass.getPackage(); + assertThat(checkedClassPackageInfo.getAnnotations()).hasSize(1); + assertThat(checkedClassPackageInfo.isAnnotatedWith(SomeAnnotation.class)).isTrue(); + } + + @Test + public void automatically_resolves_dependency_packages() { + JavaClass checkedClass = ImporterWithAdjustedResolutionRuns + .disableAllIterationsExcept(MAX_ITERATIONS_FOR_PACKAGE_INFO_PROPERTY_NAME, MAX_ITERATIONS_FOR_MEMBER_TYPES_PROPERTY_NAME) + .importClass(ClassThatReferencesOtherClass.class); + + Optional firstField = checkedClass.getAllFields().stream().findFirst(); + assertThat(firstField).isPresent(); + JavaClass otherClass = firstField.get().getRawType(); + assertThat(otherClass).isFullyImported(true); + + JavaPackage otherClassPackageInfo = otherClass.getPackage(); + + Assertions.assertThat(otherClassPackageInfo.getAnnotations()).hasSize(1); + Assertions.assertThat(otherClassPackageInfo.isAnnotatedWith(SomeAnnotation.class)).isTrue(); + } + @DataProvider public static Object[][] data_automatically_resolves_generic_type_parameter_bounds() { @SuppressWarnings("unused") diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/packageinforesolution/ClassThatReferencesOtherClass.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/packageinforesolution/ClassThatReferencesOtherClass.java new file mode 100644 index 0000000000..313a2e2b81 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/packageinforesolution/ClassThatReferencesOtherClass.java @@ -0,0 +1,7 @@ +package com.tngtech.archunit.core.importer.testexamples.packageinforesolution; + +import com.tngtech.archunit.core.importer.testexamples.OtherClass; + +public class ClassThatReferencesOtherClass { + OtherClass otherClass; +}