diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModule.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModule.java index cd1fad9ed..6ffe741d9 100644 --- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModule.java +++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModule.java @@ -38,6 +38,7 @@ import org.springframework.lang.Nullable; import org.springframework.modulith.core.Types.JMoleculesTypes; +import org.springframework.modulith.core.Types.JavaTypes; import org.springframework.modulith.core.Types.SpringTypes; import org.springframework.modulith.core.Violations.Violation; import org.springframework.util.Assert; @@ -163,19 +164,34 @@ public String getDisplayName() { * @param modules must not be {@literal null}. * @param type must not be {@literal null}. * @return will never be {@literal null}. + * @deprecated since 1.3. Use {@link #getDirectDependencies(ApplicationModules, DependencyType...)} instead. */ + @Deprecated public ApplicationModuleDependencies getDependencies(ApplicationModules modules, DependencyType... type) { + return getDirectDependencies(modules, type); + } - Assert.notNull(modules, "ApplicationModules must not be null!"); - Assert.notNull(type, "DependencyTypes must not be null!"); - - var dependencies = getAllModuleDependencies(modules) // - .filter(it -> type.length == 0 ? true : Arrays.stream(type).anyMatch(it::hasType)) // - .distinct() // - . flatMap(it -> DefaultApplicationModuleDependency.of(it, modules)) // - .toList(); + /** + * Returns the direct {@link ApplicationModuleDependencies} of the current {@link ApplicationModule}. + * + * @param modules must not be {@literal null}. + * @param type must not be {@literal null}. + * @return will never be {@literal null}. + */ + public ApplicationModuleDependencies getDirectDependencies(ApplicationModules modules, DependencyType... type) { + return getDependencies(modules, DependencyDepth.IMMEDIATE, type); + } - return ApplicationModuleDependencies.of(dependencies, modules); + /** + * Returns the all {@link ApplicationModuleDependencies} (including transitive ones) of the current + * {@link ApplicationModule}. + * + * @param modules must not be {@literal null}. + * @param type must not be {@literal null}. + * @return will never be {@literal null}. + */ + public ApplicationModuleDependencies getAllDependencies(ApplicationModules modules, DependencyType... type) { + return getDependencies(modules, DependencyDepth.ALL, type); } /** @@ -299,20 +315,20 @@ public ArchitecturallyEvidentType getArchitecturallyEvidentType(Class type) { * @param type must not be {@literal null}. */ public boolean contains(JavaClass type) { - return classes.contains(type); + return contains(type.getName()); } /** * Returns whether the current module contains the given type. * - * @param type can be {@literal null}. + * @param type must not be {@literal null}. */ - public boolean contains(@Nullable Class type) { - return type != null && getType(type.getName()).isPresent(); + public boolean contains(Class type) { + return contains(type.getName()); } /** - * Returns the {@link JavaClass} for the given candidate simple of fully-qualified type name. + * Returns the {@link JavaClass} for the given candidate simple or fully-qualified type name. * * @param candidate must not be {@literal null} or empty. * @return will never be {@literal null}. @@ -363,23 +379,17 @@ public boolean isRootModule() { } /** - * Returns whether the module has a base package with the given name. + * Returns whether the given module contains a type with the given simple or fully qualified name. * - * @param candidate must not be {@literal null} or empty. - * @return whether the module has a base package with the given name. - * @since 1.1 + * @param candidate must not be {@literal null}. + * @since 1.3 */ - boolean hasBasePackage(String candidate) { - return basePackage.getName().equals(candidate); - } + public boolean contains(String candidate) { - /* - * (non-Javadoc) - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return toString(null); + var candidatePackageName = PackageName.ofType(candidate); + + return (candidatePackageName.isEmpty() || basePackage.getPackageName().contains(candidatePackageName)) + && getType(candidate).isPresent(); } public String toString(@Nullable ApplicationModules modules) { @@ -450,6 +460,17 @@ public String toString(@Nullable ApplicationModules modules) { return builder.toString(); } + /** + * Returns whether the module has a base package with the given name. + * + * @param candidate must not be {@literal null} or empty. + * @return whether the module has a base package with the given name. + * @since 1.1 + */ + boolean hasBasePackage(String candidate) { + return basePackage.getName().equals(candidate); + } + Classes getSpringBeansInternal() { return springBeans.get(); } @@ -482,19 +503,6 @@ DeclaredDependencies getDeclaredDependencies(ApplicationModules modules) { .collect(Collectors.collectingAndThen(Collectors.toList(), DeclaredDependencies::closed)); } - /** - * Returns whether the given module contains a type with the given simple or fully qualified name. - * - * @param candidate must not be {@literal null} or empty. - * @return - */ - boolean contains(String candidate) { - - Assert.hasText(candidate, "Candidate must not be null or empty!"); - - return getType(candidate).isPresent(); - } - /** * Returns whether the {@link ApplicationModule} contains the package with the given name, which means the given * package is either the module's base package or a sub package of it. @@ -540,6 +548,26 @@ boolean containsTypeInAnyParent(JavaClass type, ApplicationModules modules) { .isPresent(); } + /* + * (non-Javadoc) + * @see org.springframework.modulith.core.ApplicationModuleDependenciesAware#getDependencies(org.springframework.modulith.core.ApplicationModules, org.springframework.modulith.core.DependencyDepth, org.springframework.modulith.core.DependencyType[]) + */ + public ApplicationModuleDependencies getDependencies(ApplicationModules modules, DependencyDepth depth, + DependencyType... types) { + + Assert.notNull(modules, "ApplicationModules must not be null!"); + Assert.notNull(depth, "DependencyDepth must not be null!"); + Assert.notNull(types, "DependencyTypes must not be null!"); + + return getAllModuleDependencies(modules) // + .filter(it -> types.length == 0 ? true : Arrays.stream(types).anyMatch(it::hasType)) // + .distinct() // + .flatMap(it -> DefaultApplicationModuleDependency.of(it, modules)) // + .distinct() // + .flatMap(it -> resolveRecurseively(modules, it, depth, types)) // + .collect(Collectors.collectingAndThen(Collectors.toList(), ApplicationModuleDependencies::of)); + } + /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) @@ -575,6 +603,15 @@ public int hashCode() { valueTypes); } + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return toString(null); + } + /* * (non-Javadoc) * @see java.lang.Comparable#compareTo(java.lang.Object) @@ -646,33 +683,6 @@ private Stream getDirectModuleBootstrapDependencies(Applicati .flatMap(it -> it.map(Stream::of).orElseGet(Stream::empty)); } - private Stream getModuleDependenciesOf(JavaClass type, ApplicationModules modules) { - - var evidentType = ArchitecturallyEvidentType.of(type, getSpringBeansInternal()); - - var injections = QualifiedDependency.fromType(evidentType) // - .filter(it -> isDependencyToOtherModule(it.getTarget(), modules)); // - - var directDependencies = type.getDirectDependenciesFromSelf().stream() // - .filter(it -> isDependencyToOtherModule(it.getTargetClass(), modules)) // - .map(QualifiedDependency::new); - - return Stream.concat(injections, directDependencies).distinct(); - } - - private boolean isDependencyToOtherModule(JavaClass dependency, ApplicationModules modules) { - return modules.contains(dependency) && !contains(dependency); - } - - private Classes findAggregateRoots(Classes source) { - - return source.stream() // - .map(it -> ArchitecturallyEvidentType.of(it, getSpringBeansInternal())) - .filter(ArchitecturallyEvidentType::isAggregateRoot) // - .map(ArchitecturallyEvidentType::getType) // - .collect(Classes.toClasses()); - } - /** * Returns the current module's immediate parent module, if present. * @@ -744,6 +754,58 @@ private Collection doGetNestedModules(ApplicationModules modu return result.toList(); } + private List findArchitecturallyEvidentType(Predicate selector) { + + var springBeansInternal = getSpringBeansInternal(); + + return classes.stream() + .map(it -> ArchitecturallyEvidentType.of(it, springBeansInternal)) + .filter(selector) + .map(ArchitecturallyEvidentType::getType) + .toList(); + } + + private Stream getModuleDependenciesOf(JavaClass type, ApplicationModules modules) { + + var evidentType = ArchitecturallyEvidentType.of(type, getSpringBeansInternal()); + + var injections = QualifiedDependency.fromType(evidentType) // + .filter(it -> isDependencyToOtherModule(it.getTarget(), modules)); // + + var directDependencies = type.getDirectDependenciesFromSelf().stream() // + .filter(it -> JavaTypes.IS_NOT_CORE_JAVA_TYPE.test(it.getTargetClass())) // + .filter(it -> isDependencyToOtherModule(it.getTargetClass(), modules)) // + .map(QualifiedDependency::new); + + return Stream.concat(injections, directDependencies).distinct(); + } + + private boolean isDependencyToOtherModule(JavaClass dependency, ApplicationModules modules) { + return modules.contains(dependency) && !contains(dependency); + } + + private Classes findAggregateRoots(Classes source) { + + return source.stream() // + .map(it -> ArchitecturallyEvidentType.of(it, getSpringBeansInternal())) + .filter(ArchitecturallyEvidentType::isAggregateRoot) // + .map(ArchitecturallyEvidentType::getType) // + .collect(Classes.toClasses()); + } + + private Stream resolveRecurseively(ApplicationModules modules, + DefaultApplicationModuleDependency dependency, DependencyDepth depth, DependencyType... type) { + + var tail = depth == DependencyDepth.ALL + ? dependency.getTargetModule() + .getDependencies(modules, depth, type) + .stream() + .distinct() + : Stream. empty(); + + return Stream.concat(Stream.of(dependency), tail); + } + private static Classes filterSpringBeans(Classes source) { Map> collect = source.that(isConfiguration()).stream() // @@ -769,17 +831,6 @@ private static Predicate hasSimpleOrFullyQualifiedName(String candida return it -> it.getSimpleName().equals(candidate) || it.getFullName().equals(candidate); } - private List findArchitecturallyEvidentType(Predicate selector) { - - var springBeansInternal = getSpringBeansInternal(); - - return classes.stream() - .map(it -> ArchitecturallyEvidentType.of(it, springBeansInternal)) - .filter(selector) - .map(ArchitecturallyEvidentType::getType) - .toList(); - } - static class DeclaredDependency { private static final String INVALID_EXPLICIT_MODULE_DEPENDENCY = "Invalid explicit module dependency in %s! No module found with name '%s'."; @@ -1084,21 +1135,31 @@ public QualifiedDependency(JavaClass source, JavaClass target, String descriptio DependencyType.forDependency(dependency)); } - static QualifiedDependency fromCodeUnitParameter(JavaCodeUnit codeUnit, JavaClass parameter) { + static Stream fromCodeUnitParameter(JavaCodeUnit codeUnit, JavaClass parameter) { + + if (JavaTypes.IS_CORE_JAVA_TYPE.test(parameter)) { + return Stream.empty(); + } var description = createDescription(codeUnit, parameter, "parameter"); var type = DependencyType.forCodeUnit(codeUnit) // .defaultOr(() -> DependencyType.forParameter(parameter)); - return new QualifiedDependency(codeUnit.getOwner(), parameter, description, type); + return Stream.of(new QualifiedDependency(codeUnit.getOwner(), parameter, description, type)); } - static QualifiedDependency fromCodeUnitReturnType(JavaCodeUnit codeUnit) { + static Stream fromCodeUnitReturnType(JavaCodeUnit codeUnit) { + + var returnType = codeUnit.getRawReturnType(); + + if (JavaTypes.IS_CORE_JAVA_TYPE.test(returnType)) { + return Stream.empty(); + } var description = createDescription(codeUnit, codeUnit.getRawReturnType(), "return type"); - return new QualifiedDependency(codeUnit.getOwner(), codeUnit.getRawReturnType(), description, - DependencyType.DEFAULT); + return Stream.of(new QualifiedDependency(codeUnit.getOwner(), codeUnit.getRawReturnType(), description, + DependencyType.DEFAULT)); } static Stream fromType(ArchitecturallyEvidentType type) { @@ -1112,9 +1173,10 @@ static Stream allFrom(JavaCodeUnit codeUnit) { var parameterDependencies = codeUnit.getRawParameterTypes()// .stream() // - .map(it -> fromCodeUnitParameter(codeUnit, it)); + .filter(JavaTypes.IS_NOT_CORE_JAVA_TYPE) // + .flatMap(it -> fromCodeUnitParameter(codeUnit, it)); - var returnType = Stream.of(fromCodeUnitReturnType(codeUnit)); + var returnType = fromCodeUnitReturnType(codeUnit); return Stream.concat(parameterDependencies, returnType); } @@ -1268,6 +1330,7 @@ private static Stream fromConstructorOf(ArchitecturallyEvid return constructors.stream() // .filter(it -> constructors.size() == 1 || isInjectionPoint(it)) // .flatMap(it -> it.getRawParameterTypes().stream() // + .filter(Predicate.not(JavaTypes.IS_CORE_JAVA_TYPE)) .map(parameter -> { return source.isInjectable() && !source.isConfigurationProperties() ? new InjectionDependency(it, parameter) @@ -1279,12 +1342,17 @@ private static Stream fromConstructorOf(ArchitecturallyEvid private static Stream fromFieldsOf(JavaClass source) { return source.getAllFields().stream() // + .filter(it -> JavaTypes.IS_NOT_CORE_JAVA_TYPE.test(it.getRawType())) // .filter(QualifiedDependency::isInjectionPoint) // .map(field -> new InjectionDependency(field, field.getRawType())); } private static Stream fromMethodsOf(JavaClass source) { + if (JavaTypes.IS_CORE_JAVA_TYPE.test(source)) { + return Stream.empty(); + } + var methods = source.getAllMethods().stream() // .filter(it -> !it.getOwner().isEquivalentTo(Object.class)) // .collect(Collectors.toSet()); @@ -1295,8 +1363,8 @@ private static Stream fromMethodsOf(JavaClass source) { var returnTypes = methods.stream() // .filter(it -> !it.getRawReturnType().isPrimitive()) // - .filter(it -> !it.getRawReturnType().getPackageName().startsWith("java")) // - .map(it -> fromCodeUnitReturnType(it)); + .filter(it -> JavaTypes.IS_NOT_CORE_JAVA_TYPE.test(it.getRawReturnType())) // + .flatMap(it -> fromCodeUnitReturnType(it)); var injectionMethods = methods.stream() // .filter(QualifiedDependency::isInjectionPoint) // @@ -1304,12 +1372,14 @@ private static Stream fromMethodsOf(JavaClass source) { var methodInjections = injectionMethods.stream() // .flatMap(it -> it.getRawParameterTypes().stream() // + .filter(JavaTypes.IS_NOT_CORE_JAVA_TYPE) // .map(parameter -> new InjectionDependency(it, parameter))); var otherMethods = methods.stream() // .filter(it -> !injectionMethods.contains(it)) // .flatMap(it -> it.getRawParameterTypes().stream() // - .map(parameter -> fromCodeUnitParameter(it, parameter))); + .filter(JavaTypes.IS_NOT_CORE_JAVA_TYPE) // + .flatMap(parameter -> fromCodeUnitParameter(it, parameter))); return Stream.concat(Stream.concat(methodInjections, otherMethods), returnTypes); } @@ -1427,7 +1497,8 @@ private static String getDescriptionFor(JavaMember member) { } } - private static class DefaultApplicationModuleDependency implements ApplicationModuleDependency { + private static class DefaultApplicationModuleDependency + implements ApplicationModuleDependency { private final QualifiedDependency dependency; private final ApplicationModule target; @@ -1455,7 +1526,7 @@ private DefaultApplicationModuleDependency(QualifiedDependency dependency, Appli * @param dependency must not be {@literal null}. * @param modules must not be {@literal null}. */ - static Stream of(QualifiedDependency dependency, ApplicationModules modules) { + static Stream of(QualifiedDependency dependency, ApplicationModules modules) { return modules.getModuleByType(dependency.getTarget()).stream() .map(it -> new DefaultApplicationModuleDependency(dependency, it)); diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleDependencies.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleDependencies.java index 365f7dbe3..c876d99bf 100644 --- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleDependencies.java +++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModuleDependencies.java @@ -15,6 +15,7 @@ */ package org.springframework.modulith.core; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.function.Function; @@ -30,22 +31,23 @@ public class ApplicationModuleDependencies { private final List dependencies; - private final ApplicationModules modules; + private final Collection modules; /** * Creates a new {@link ApplicationModuleDependencies} for the given {@link List} of * {@link ApplicationModuleDependency} and {@link ApplicationModules}. * * @param dependencies must not be {@literal null}. - * @param modules must not be {@literal null}. */ - private ApplicationModuleDependencies(List dependencies, ApplicationModules modules) { + private ApplicationModuleDependencies(List dependencies) { Assert.notNull(dependencies, "ApplicationModuleDependency list must not be null!"); - Assert.notNull(modules, "ApplicationModules must not be null!"); this.dependencies = dependencies; - this.modules = modules; + this.modules = dependencies.stream() + .map(ApplicationModuleDependency::getTargetModule) + .distinct() + .toList(); } /** @@ -56,10 +58,8 @@ private ApplicationModuleDependencies(List dependen * @param modules must not be {@literal null}. * @return will never be {@literal null}. */ - static ApplicationModuleDependencies of(List dependencies, - ApplicationModules modules) { - - return new ApplicationModuleDependencies(dependencies, modules); + static ApplicationModuleDependencies of(List dependencies) { + return new ApplicationModuleDependencies(dependencies); } /** @@ -72,9 +72,7 @@ public boolean contains(ApplicationModule module) { Assert.notNull(module, "ApplicationModule must not be null!"); - return dependencies.stream() - .map(ApplicationModuleDependency::getTargetModule) - .anyMatch(module::equals); + return modules.contains(module); } /** @@ -87,8 +85,7 @@ public boolean containsModuleNamed(String name) { Assert.hasText(name, "Module name must not be null or empty!"); - return dependencies.stream() - .map(ApplicationModuleDependency::getTargetModule) + return modules.stream() .map(ApplicationModule::getName) .anyMatch(name::equals); } @@ -119,13 +116,22 @@ public Stream uniqueStream(Function seenTargets.add(extractor.apply(it))); } + /** + * Returns a new {@link ApplicationModuleDependencies} instance containing only the dependencies of the given + * {@link DependencyType}. + * + * @param type must not be {@literal null}. + * @return + */ public ApplicationModuleDependencies withType(DependencyType type) { + Assert.notNull(type, "DependencyType must not be null!"); + var filtered = dependencies.stream() .filter(it -> it.getDependencyType().equals(type)) .toList(); - return ApplicationModuleDependencies.of(filtered, modules); + return ApplicationModuleDependencies.of(filtered); } /** @@ -134,6 +140,45 @@ public ApplicationModuleDependencies withType(DependencyType type) { * @return will never be {@literal null}. */ public boolean isEmpty() { - return dependencies.isEmpty(); + return modules.isEmpty(); + } + + /** + * Returns whether the dependencies contain the type with the given fully-qualified name. + * + * @param type must not be {@literal null} or empty. + * @return + * @since 1.3 + */ + public boolean contains(String type) { + + Assert.hasText(type, "Type must not be null or empty!"); + + return uniqueModules().anyMatch(it -> it.contains(type)); + } + + /** + * Returns all unique {@link ApplicationModule}s involved in the dependencies. + * + * @return will never be {@literal null}. + */ + public Stream uniqueModules() { + return modules.stream(); + } + + /** + * Returns the {@link ApplicationModule} containing the given type. + * + * @param name must not be {@literal null} or empty. + * @return will never be {@literal null}. + */ + public ApplicationModule getModuleByType(String name) { + + Assert.hasText(name, "Name must not be null or empty!"); + + return uniqueModules() + .filter(it -> it.contains(name)) + .findFirst() + .orElse(null); } } diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java index a50ac793f..359cd010c 100644 --- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java +++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java @@ -778,8 +778,7 @@ private static List topologicallySortModules(ApplicationModules modules) graph.addVertex(project); - project.getDependencies(modules).stream() // - .map(ApplicationModuleDependency::getTargetModule) // + project.getDirectDependencies(modules).uniqueModules() // .forEach(dependency -> { graph.addVertex(dependency); graph.addEdge(project, dependency); diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/PackageName.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/PackageName.java index 67662eca6..f0ed5b02e 100644 --- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/PackageName.java +++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/PackageName.java @@ -16,6 +16,7 @@ package org.springframework.modulith.core; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; /** * The name of a Java package. Packages are sortable comparing their individual segments and deeper packages sorted @@ -32,16 +33,29 @@ class PackageName implements Comparable { /** * Creates a new {@link PackageName} with the given name. * - * @param name must not be {@literal null} or empty. + * @param name must not be {@literal null}. */ public PackageName(String name) { - Assert.hasText(name, "Name must not be null or empty!"); + Assert.notNull(name, "Name must not be null!"); this.name = name; this.segments = name.split("\\."); } + /** + * Creates a new {@link PackageName} for the given fully-qualified type name. + * + * @param fullyQualifiedName must not be {@literal null} or empty. + * @return will never be {@literal null}. + */ + public static PackageName ofType(String fullyQualifiedName) { + + Assert.notNull(fullyQualifiedName, "Type name must not be null!"); + + return new PackageName(ClassUtils.getPackageName(fullyQualifiedName)); + } + /** * Returns the length of the package name. * @@ -118,6 +132,19 @@ boolean isParentPackageOf(PackageName reference) { return reference.name.startsWith(name + "."); } + /** + * Returns whether the package name contains the given one, i.e. if the given one either is the current one or a + * sub-package of it. + * + * @param reference must not be {@literal null}. + */ + boolean contains(PackageName reference) { + + Assert.notNull(reference, "Reference package name must not be null!"); + + return this.equals(reference) || reference.isSubPackageOf(this); + } + /** * Returns whether the current {@link PackageName} is the name of a sub-package with the given name. * @@ -131,6 +158,10 @@ boolean isSubPackageOf(PackageName reference) { return name.startsWith(reference.name + "."); } + boolean isEmpty() { + return length() == 0; + } + /* * (non-Javadoc) * @see java.lang.Comparable#compareTo(java.lang.Object) diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/Types.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/Types.java index 0a7d5b80c..0d1f6ff52 100644 --- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/Types.java +++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/Types.java @@ -19,6 +19,7 @@ import static org.springframework.modulith.core.SyntacticSugar.*; import java.lang.annotation.Annotation; +import java.util.function.Predicate; import org.springframework.lang.Nullable; import org.springframework.modulith.PackageInfo; @@ -79,6 +80,14 @@ public static boolean areRulesPresent() { } } + static class JavaTypes { + + static Predicate IS_CORE_JAVA_TYPE = it -> it.getName().startsWith("java.") + || it.getName().startsWith("javax."); + + static Predicate IS_NOT_CORE_JAVA_TYPE = Predicate.not(IS_CORE_JAVA_TYPE); + } + static class JavaXTypes { private static final String BASE_PACKAGE = "jakarta"; diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/util/ApplicationModulesExporter.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/util/ApplicationModulesExporter.java index 12e7c1982..3a389bac2 100644 --- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/util/ApplicationModulesExporter.java +++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/util/ApplicationModulesExporter.java @@ -138,7 +138,7 @@ private static Map toInfo(ApplicationModule module, ApplicationM json.put("namedInterfaces", toNamedInterfaces(module.getNamedInterfaces())); } - json.put("dependencies", module.getDependencies(modules).stream() // + json.put("dependencies", module.getDirectDependencies(modules).stream() // .collect(Collectors.groupingBy(ApplicationModuleDependency::getTargetModule, MAPPER)) .entrySet() // .stream() // diff --git a/spring-modulith-core/src/test/java/org/springframework/modulith/core/PackageNameUnitTests.java b/spring-modulith-core/src/test/java/org/springframework/modulith/core/PackageNameUnitTests.java index e3085a31b..3f5f469e2 100644 --- a/spring-modulith-core/src/test/java/org/springframework/modulith/core/PackageNameUnitTests.java +++ b/spring-modulith-core/src/test/java/org/springframework/modulith/core/PackageNameUnitTests.java @@ -44,4 +44,15 @@ void sortsPackagesByNameAndDepth() { .map(it -> it.getLocalName("com"))) .containsExactly("acme.b", "acme.a.second", "acme.a.first.one", "acme.a.first", "acme.a", "acme"); } + + @Test // GH-802 + void caculatesNestingCorrectly() { + + var comAcme = new PackageName("com.acme"); + var comAcmeA = new PackageName("com.acme.a"); + + assertThat(comAcme.contains(comAcme)).isTrue(); + assertThat(comAcme.contains(comAcmeA)).isTrue(); + assertThat(comAcmeA.contains(comAcme)).isFalse(); + } } diff --git a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Asciidoctor.java b/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Asciidoctor.java index 23da80b81..7a1e5c1ed 100644 --- a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Asciidoctor.java +++ b/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Asciidoctor.java @@ -359,7 +359,7 @@ public String toAsciidoctor(String source) { */ public String renderBeanReferences(ApplicationModule module) { - var bullets = module.getDependencies(modules, DependencyType.USES_COMPONENT) + var bullets = module.getDirectDependencies(modules, DependencyType.USES_COMPONENT) .uniqueStream(ApplicationModuleDependency::getTargetType) .map(it -> "%s (in %s)".formatted(toInlineCode(it.getTargetType()), it.getTargetModule().getDisplayName())) .map(this::toBulletPoint) diff --git a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Documenter.java b/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Documenter.java index dc6c79de9..662dbcd6c 100644 --- a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Documenter.java +++ b/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Documenter.java @@ -36,7 +36,6 @@ import org.springframework.lang.Nullable; import org.springframework.modulith.core.ApplicationModule; -import org.springframework.modulith.core.ApplicationModuleDependency; import org.springframework.modulith.core.ApplicationModules; import org.springframework.modulith.core.DependencyDepth; import org.springframework.modulith.core.DependencyType; @@ -442,8 +441,7 @@ private void addDependencies(ApplicationModule module, Component component, Diag DEPENDENCY_DESCRIPTIONS.entrySet().stream().forEach(entry -> { - module.getDependencies(modules, entry.getKey()).stream() // - .map(ApplicationModuleDependency::getTargetModule) // + module.getDirectDependencies(modules, entry.getKey()).uniqueModules() // .map(it -> getComponents(options).get(it)) // .map(it -> component.uses(it, entry.getValue())) // .filter(it -> it != null) // @@ -475,8 +473,7 @@ private void addComponentsToView(ApplicationModule module, ComponentView view, D Supplier> bootstrapDependencies = () -> module.getBootstrapDependencies(modules, options.dependencyDepth); Supplier> otherDependencies = () -> options.getDependencyTypes() - .flatMap(it -> module.getDependencies(modules, it).stream() - .map(ApplicationModuleDependency::getTargetModule)); + .flatMap(it -> module.getDirectDependencies(modules, it).uniqueModules()); Supplier> dependencies = () -> Stream.concat(bootstrapDependencies.get(), otherDependencies.get()); diff --git a/spring-modulith-integration-test/src/test/java/com/acme/myproject/ModulithTest.java b/spring-modulith-integration-test/src/test/java/com/acme/myproject/ModulithTest.java index 0cfbc8eed..7b874c8d7 100644 --- a/spring-modulith-integration-test/src/test/java/com/acme/myproject/ModulithTest.java +++ b/spring-modulith-integration-test/src/test/java/com/acme/myproject/ModulithTest.java @@ -110,10 +110,10 @@ void configrationPropertiesTypesEstablishSimpleDependency() { assertThat(modules.getModuleByName("moduleD")).hasValueSatisfying(it -> { - assertThat(it.getDependencies(modules, DependencyType.DEFAULT)) + assertThat(it.getDirectDependencies(modules, DependencyType.DEFAULT)) .matches(inner -> inner.containsModuleNamed("moduleC")); - assertThat(it.getDependencies(modules, DependencyType.USES_COMPONENT)) + assertThat(it.getDirectDependencies(modules, DependencyType.USES_COMPONENT)) .matches(ApplicationModuleDependencies::isEmpty); }); } diff --git a/spring-modulith-integration-test/src/test/java/org/springframework/modulith/core/ApplicationModulesIntegrationTest.java b/spring-modulith-integration-test/src/test/java/org/springframework/modulith/core/ApplicationModulesIntegrationTest.java index 62e20c022..6eb216e6f 100644 --- a/spring-modulith-integration-test/src/test/java/org/springframework/modulith/core/ApplicationModulesIntegrationTest.java +++ b/spring-modulith-integration-test/src/test/java/org/springframework/modulith/core/ApplicationModulesIntegrationTest.java @@ -127,7 +127,7 @@ void moduleBListensToModuleA() { ApplicationModule moduleA = modules.getModuleByName("moduleA").orElseThrow(IllegalStateException::new); assertThat(module).hasValueSatisfying(it -> { - assertThat(it.getDependencies(modules, DependencyType.EVENT_LISTENER).contains(moduleA)).isTrue(); + assertThat(it.getDirectDependencies(modules, DependencyType.EVENT_LISTENER).contains(moduleA)).isTrue(); }); } @@ -261,6 +261,17 @@ void detectsContributedApplicationModules() { } } + @Test // GH-802 + void detectsFullDependencyChain() { + + assertThat(modules.getModuleByName("moduleC")).hasValueSatisfying(it -> { + + assertThat(it.getAllDependencies(modules).uniqueModules()) + .extracting(ApplicationModule::getName) + .containsExactlyInAnyOrder("moduleB", "moduleA"); + }); + } + private static void verifyNamedInterfaces(NamedInterfaces interfaces, String name, Class... types) { Stream.of(types).forEach(type -> {