diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/NamedInterface.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/NamedInterface.java index 3afcace2..d0411831 100644 --- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/NamedInterface.java +++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/NamedInterface.java @@ -20,6 +20,7 @@ import static org.springframework.modulith.core.Types.*; import java.util.Iterator; import java.util.List; +import java.util.stream.Stream; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.util.Assert; @@ -155,6 +156,15 @@ public class NamedInterface implements Iterable { return !classes.that(Predicates.equivalentTo(type)).isEmpty(); } + /** + * Returns a {@link Stream} of all {@link JavaClass}es contained in this interface. + * + * @return will never be {@literal null}. + */ + public Stream asJavaClasses() { + return classes.stream(); + } + /** * Returns whether the given {@link NamedInterface} has the same name as the current one. * 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 12531539..6e6d3f21 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 @@ -17,6 +17,7 @@ package org.springframework.modulith.core.util; import static java.util.stream.Collectors.*; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; @@ -24,13 +25,18 @@ import java.util.Set; import java.util.function.Function; import java.util.stream.Collector; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.springframework.modulith.core.ApplicationModule; import org.springframework.modulith.core.ApplicationModuleDependency; import org.springframework.modulith.core.ApplicationModules; import org.springframework.modulith.core.DependencyType; +import org.springframework.modulith.core.NamedInterface; +import org.springframework.modulith.core.NamedInterfaces; import org.springframework.util.Assert; +import com.tngtech.archunit.core.domain.JavaClass; + /** * Export the structure of {@link ApplicationModules} as JSON. * @@ -38,6 +44,8 @@ import org.springframework.util.Assert; */ public class ApplicationModulesExporter { + private static final Function> TO_EXPOSED_TYPES = it -> it.asJavaClasses() + .map(JavaClass::getName); private static final Function, Set> REMOVE_DEFAULT_DEPENDENCY_TYPE_IF_OTHERS_PRESENT = it -> { if (it.stream().anyMatch(type -> type != DependencyType.DEFAULT)) { @@ -77,7 +85,7 @@ public class ApplicationModulesExporter { Assert.notNull(args, "Arguments must not be null!"); Assert.isTrue(args.length == 1, "A java package name is required as only argument!"); - System.out.println(new ApplicationModulesExporter(ApplicationModules.of(args[0])).toJson()); + System.out.println(new ApplicationModulesExporter(ApplicationModules.of(args[0])).toFullJson()); } /** @@ -86,28 +94,52 @@ public class ApplicationModulesExporter { * @return will never be {@literal null}. */ public String toJson() { - return Json.toString(toMap()); + return Json.toString(toMap(Details.SIMPLE)); } - private Map toMap() { + /** + * Returns the {@link ApplicationModules} structure as JSON String including full details, such as named interfaces + * etc. + * + * @return will never be {@literal null}. + */ + public String toFullJson() { + return Json.toString(toMap(Details.FULL)); + } + + private Map toMap(Details details) { return modules.stream() .collect( - Collectors.toMap(ApplicationModule::getName, it -> toInfo(it, modules), (l, r) -> r, LinkedHashMap::new)); + Collectors.toMap(ApplicationModule::getName, it -> toInfo(it, modules, details), (l, r) -> r, + LinkedHashMap::new)); } - private static Map toInfo(ApplicationModule module, ApplicationModules modules) { + private static Map toInfo(ApplicationModule module, ApplicationModules modules, Details details) { - return Map.of( // - "displayName", module.getDisplayName(), // - "basePackage", module.getBasePackage().getName(), // - "dependencies", module.getDependencies(modules).stream() // - .collect(Collectors.groupingBy(ApplicationModuleDependency::getTargetModule, MAPPER)) - .entrySet() // - .stream() // - .map(ApplicationModulesExporter::toInfo) // - .toList() // - ); + Map json = new LinkedHashMap<>(); + + json.put("displayName", module.getDisplayName()); + json.put("basePackage", module.getBasePackage().getName()); + + if (details.equals(Details.FULL)) { + json.put("namedInterfaces", toNamedInterfaces(module.getNamedInterfaces())); + } + + json.put("dependencies", module.getDependencies(modules).stream() // + .collect(Collectors.groupingBy(ApplicationModuleDependency::getTargetModule, MAPPER)) + .entrySet() // + .stream() // + .map(ApplicationModulesExporter::toInfo) // + .toList()); + + return Collections.unmodifiableMap(json); + } + + private static Map> toNamedInterfaces(NamedInterfaces interfaces) { + + return interfaces.stream() + .collect(groupingBy(it -> it.getName(), flatMapping(TO_EXPOSED_TYPES, toSet()))); } private static Map toInfo(Entry> types) { @@ -117,4 +149,8 @@ public class ApplicationModulesExporter { "types", types.getValue() // ); } + + private static enum Details { + SIMPLE, FULL; + } } diff --git a/spring-modulith-integration-test/src/test/java/org/springframework/modulith/core/util/ApplicationModulesExporterUnitTests.java b/spring-modulith-integration-test/src/test/java/org/springframework/modulith/core/util/ApplicationModulesExporterUnitTests.java index b9fe7feb..ae3b74d5 100644 --- a/spring-modulith-integration-test/src/test/java/org/springframework/modulith/core/util/ApplicationModulesExporterUnitTests.java +++ b/spring-modulith-integration-test/src/test/java/org/springframework/modulith/core/util/ApplicationModulesExporterUnitTests.java @@ -33,10 +33,14 @@ public class ApplicationModulesExporterUnitTests { @Test // #119 void rendersApplicationModulesAsJson() { - var json = new ApplicationModulesExporter(ApplicationModules.of(Application.class)).toJson(); + var exporter = new ApplicationModulesExporter(ApplicationModules.of(Application.class)); assertThatNoException().isThrownBy(() -> { - new ObjectMapper().readTree(json); + new ObjectMapper().readTree(exporter.toFullJson()); + }); + + assertThatNoException().isThrownBy(() -> { + new ObjectMapper().readTree(exporter.toJson()); }); } }