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 b5385eac..c47d38b7 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 @@ -35,6 +35,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; +import com.structurizr.export.plantuml.AbstractPlantUMLExporter; import org.springframework.lang.Nullable; import org.springframework.modulith.core.ApplicationModule; import org.springframework.modulith.core.ApplicationModuleDependency; @@ -565,6 +566,7 @@ public class Documenter { case C4: var c4PlantUmlExporter = new C4PlantUMLExporter(); + addSkinParamsFromOptions(c4PlantUmlExporter, options); var diagram = c4PlantUmlExporter.export(view); return diagram.getDefinition(); @@ -573,12 +575,19 @@ public class Documenter { default: var plantUmlExporter = new CustomizedPlantUmlExporter(); + addSkinParamsFromOptions(plantUmlExporter, options); plantUmlExporter.addSkinParam("componentStyle", "uml1"); return plantUmlExporter.export(view).getDefinition(); } } + private void addSkinParamsFromOptions(AbstractPlantUMLExporter exporter, DiagramOptions options) { + for (var skinParamEntry : options.skinParams.entrySet()) { + exporter.addSkinParam(skinParamEntry.getKey(), skinParamEntry.getValue()); + } + } + private String createPlantUml(DiagramOptions options) { ComponentView componentView = createComponentView(options); @@ -692,6 +701,7 @@ public class Documenter { private final Function defaultDisplayName; private final DiagramStyle style; private final ElementsWithoutRelationships elementsWithoutRelationships; + private final Map skinParams; /** * @param dependencyTypes must not be {@literal null}. @@ -704,13 +714,15 @@ public class Documenter { * @param defaultDisplayName must not be {@literal null}. * @param style must not be {@literal null}. * @param elementsWithoutRelationships must not be {@literal null}. + * @param skinParams must not be {@literal null}. */ DiagramOptions(Set dependencyTypes, DependencyDepth dependencyDepth, Predicate exclusions, Predicate componentFilter, Predicate targetOnly, @Nullable String targetFileName, Function> colorSelector, Function defaultDisplayName, DiagramStyle style, - ElementsWithoutRelationships elementsWithoutRelationships) { + ElementsWithoutRelationships elementsWithoutRelationships, + Map skinParams) { Assert.notNull(dependencyTypes, "Dependency types must not be null!"); Assert.notNull(dependencyDepth, "Dependency depth must not be null!"); @@ -721,6 +733,7 @@ public class Documenter { Assert.notNull(defaultDisplayName, "Default display name must not be null!"); Assert.notNull(style, "DiagramStyle must not be null!"); Assert.notNull(elementsWithoutRelationships, "ElementsWithoutRelationships must not be null!"); + Assert.notNull(skinParams, "SkinParams must not be null!"); this.dependencyTypes = dependencyTypes; this.dependencyDepth = dependencyDepth; @@ -732,6 +745,7 @@ public class Documenter { this.defaultDisplayName = defaultDisplayName; this.style = style; this.elementsWithoutRelationships = elementsWithoutRelationships; + this.skinParams = skinParams; } /** @@ -739,7 +753,7 @@ public class Documenter { */ public DiagramOptions withDependencyDepth(DependencyDepth dependencyDepth) { return new DiagramOptions(dependencyTypes, dependencyDepth, exclusions, componentFilter, targetOnly, - targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships); + targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships, skinParams); } /** @@ -747,7 +761,7 @@ public class Documenter { */ public DiagramOptions withExclusions(Predicate exclusions) { return new DiagramOptions(dependencyTypes, dependencyDepth, exclusions, componentFilter, targetOnly, - targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships); + targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships, skinParams); } /** @@ -755,7 +769,7 @@ public class Documenter { */ public DiagramOptions withComponentFilter(Predicate componentFilter) { return new DiagramOptions(dependencyTypes, dependencyDepth, exclusions, componentFilter, targetOnly, - targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships); + targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships, skinParams); } /** @@ -765,7 +779,7 @@ public class Documenter { */ public DiagramOptions withTargetOnly(Predicate targetOnly) { return new DiagramOptions(dependencyTypes, dependencyDepth, exclusions, componentFilter, targetOnly, - targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships); + targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships, skinParams); } /** @@ -777,7 +791,7 @@ public class Documenter { Assert.isTrue(targetFileName.contains("%s"), () -> INVALID_FILE_NAME_PATTERN.formatted(targetFileName)); return new DiagramOptions(dependencyTypes, dependencyDepth, exclusions, componentFilter, targetOnly, - targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships); + targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships, skinParams); } /** @@ -785,7 +799,7 @@ public class Documenter { */ public DiagramOptions withColorSelector(Function> colorSelector) { return new DiagramOptions(dependencyTypes, dependencyDepth, exclusions, componentFilter, targetOnly, - targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships); + targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships, skinParams); } /** @@ -794,7 +808,7 @@ public class Documenter { */ public DiagramOptions withDefaultDisplayName(Function defaultDisplayName) { return new DiagramOptions(dependencyTypes, dependencyDepth, exclusions, componentFilter, targetOnly, - targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships); + targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships, skinParams); } /** @@ -802,7 +816,7 @@ public class Documenter { */ public DiagramOptions withStyle(DiagramStyle style) { return new DiagramOptions(dependencyTypes, dependencyDepth, exclusions, componentFilter, targetOnly, - targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships); + targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships, skinParams); } /** @@ -815,7 +829,18 @@ public class Documenter { */ public DiagramOptions withElementsWithoutRelationships(ElementsWithoutRelationships elementsWithoutRelationships) { return new DiagramOptions(dependencyTypes, dependencyDepth, exclusions, componentFilter, targetOnly, - targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships); + targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships, skinParams); + } + + /** + * Configuration setting to add arbitrary skin parameters to the created diagrams. + * + * Applies to both the UML and C4 {@link DiagramStyle styles}. + */ + public DiagramOptions withSkinParam(String name, String value) { + skinParams.put(name, value); + return new DiagramOptions(dependencyTypes, dependencyDepth, exclusions, componentFilter, targetOnly, + targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships, skinParams); } /** @@ -828,7 +853,7 @@ public class Documenter { public static DiagramOptions defaults() { return new DiagramOptions(ALL_TYPES, DependencyDepth.IMMEDIATE, it -> false, it -> true, it -> false, null, __ -> Optional.empty(), it -> it.getDisplayName(), DiagramStyle.C4, - ElementsWithoutRelationships.HIDDEN); + ElementsWithoutRelationships.HIDDEN, new LinkedHashMap<>()); } /** @@ -845,7 +870,7 @@ public class Documenter { return new DiagramOptions(dependencyTypes, dependencyDepth, exclusions, componentFilter, targetOnly, targetFileName, - colorSelector, defaultDisplayName, style, elementsWithoutRelationships); + colorSelector, defaultDisplayName, style, elementsWithoutRelationships, skinParams); } private Optional getTargetFileName() {