ModuleFacts.java
package li.selman.modulithdocsmarkdown;
import static java.util.stream.Collectors.toCollection;
import com.tngtech.archunit.core.domain.JavaClass;
import java.util.SortedSet;
import java.util.TreeSet;
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.EventType;
import org.springframework.modulith.core.SpringBean;
/**
* The single source of truth for "the facts" a module canvas documents, extracted exclusively from the public
* {@code spring-modulith-core} model. Both {@link ModulithDocusaurusRenderer} and the semantic parity test consume
* this same definition, so a mis-modelled row breaks in exactly one place.
*/
public record ModuleFacts(
String identifier,
String displayName,
String basePackage,
SortedSet<String> springBeans,
SortedSet<String> beanReferences,
SortedSet<String> aggregateRoots,
SortedSet<String> valueTypes,
SortedSet<String> publishedEvents,
SortedSet<String> eventsListenedTo,
SortedSet<String> configProperties) {
public static ModuleFacts from(ApplicationModule module, ApplicationModules modules) {
return new ModuleFacts(
module.getIdentifier().toString(),
module.getDisplayName(),
module.getBasePackage().getName(),
// Matches Documenter's default CanvasOptions (hideInternals = true): only API/exposed beans are listed.
simpleNames(module.getSpringBeans().stream()
.filter(SpringBean::isApiBean)
.map(bean -> bean.toArchitecturallyEvidentType().getType())),
module.getDirectDependencies(modules, DependencyType.USES_COMPONENT)
.uniqueStream(ApplicationModuleDependency::getTargetType)
.map(dependency -> dependency.getTargetType().getSimpleName())
.collect(toCollection(TreeSet::new)),
simpleNames(module.getAggregateRoots().stream()),
simpleNames(module.getValueTypes().stream()),
simpleNames(module.getPublishedEvents().stream()
.map(EventType::getType)
.filter(module::isExposed)),
simpleNames(module.getEventsListenedTo(modules).stream()),
ConfigMetadata.propertyNamesFor(module));
}
/**
* Tokens that MUST appear (as inline code) in any faithful rendering. Description is intentionally excluded — its
* source (package-info Javadoc) is docs-internal in Spring Modulith, see the README's known-gaps section.
*/
SortedSet<String> allTokens() {
var tokens = new TreeSet<String>();
tokens.add(basePackage);
tokens.addAll(springBeans);
tokens.addAll(beanReferences);
tokens.addAll(aggregateRoots);
tokens.addAll(valueTypes);
tokens.addAll(publishedEvents);
tokens.addAll(eventsListenedTo);
tokens.addAll(configProperties);
return tokens;
}
private static SortedSet<String> simpleNames(Stream<JavaClass> types) {
return types.map(JavaClass::getSimpleName).collect(toCollection(TreeSet::new));
}
}