Skip to content

Commit

Permalink
ignore unknown components in dependencies of PlantUML diagrams
Browse files Browse the repository at this point in the history
The current implementation prevents using PlantUML diagrams as rules
that are used for wider documentation purposes (e.g. containing a
database component or some other dependency that's irrelevant from the
point of an ArchUnit test).
The consequence of ignoring such unknown components is only to allow
fewer dependencies and fewer classes to be present.
So, at best the test will fail as a false positive, if the components
are declared wrongly by accident.
Thus, there is no major harm in simply ignoring these unknown components
and giving users a little more freedom what elements they want to use
in their diagrams.

Issue: TNG#1132

Signed-off-by: Tomasz Fijalkowski <[email protected]>
  • Loading branch information
tfij authored and codecholeric committed Apr 10, 2024
1 parent 7781b41 commit c1ecd8c
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@

import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;

import com.google.common.collect.FluentIterable;

import static com.tngtech.archunit.library.plantuml.rules.PlantUmlComponent.Functions.TO_EXISTING_ALIAS;
import static java.util.Objects.requireNonNull;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;

Expand All @@ -43,19 +45,16 @@ Collection<PlantUmlComponent> getComponentsWithAlias() {
return componentsByAlias.values();
}

PlantUmlComponent findComponentWith(String nameOrAlias) {
Optional<PlantUmlComponent> tryFindComponentWith(String nameOrAlias) {
ComponentName componentName = new ComponentName(nameOrAlias);
Alias alias = new Alias(nameOrAlias);
PlantUmlComponent result = componentsByAlias.containsKey(alias) ? componentsByAlias.get(alias) : componentsByName.get(componentName);
if (result == null) {
throw new IllegalDiagramException("There is no Component with name or alias = '%s'. %s", nameOrAlias,
"Components must be specified separately from dependencies.");
}
return result;
return Optional.ofNullable(result);
}

PlantUmlComponent findComponentWith(ComponentIdentifier identifier) {
return componentsByName.get(identifier.getComponentName());
return requireNonNull(componentsByName.get(identifier.getComponentName()),
() -> String.format("Unknown component '%s'", identifier.getComponentName()));
}

private static final Predicate<PlantUmlComponent> WITH_ALIAS = input -> input.getAlias().isPresent();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,17 @@
import com.google.common.io.CharStreams;
import com.tngtech.archunit.library.plantuml.rules.PlantUmlPatterns.PlantUmlComponentMatcher;
import com.tngtech.archunit.library.plantuml.rules.PlantUmlPatterns.PlantUmlDependencyMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

class PlantUmlParser {
private static final Logger log = LoggerFactory.getLogger(PlantUmlParser.class);

private final PlantUmlPatterns plantUmlPatterns = new PlantUmlPatterns();

PlantUmlDiagram parse(URL url) {
Expand Down Expand Up @@ -78,9 +82,16 @@ private Set<PlantUmlComponent> parseComponents(List<String> plantUmlDiagramLines
private ImmutableList<ParsedDependency> parseDependencies(PlantUmlComponents plantUmlComponents, List<String> plantUmlDiagramLines) {
ImmutableList.Builder<ParsedDependency> result = ImmutableList.builder();
for (PlantUmlDependencyMatcher matcher : plantUmlPatterns.matchDependencies(plantUmlDiagramLines)) {
PlantUmlComponent origin = findComponentMatching(plantUmlComponents, matcher.matchOrigin());
PlantUmlComponent target = findComponentMatching(plantUmlComponents, matcher.matchTarget());
result.add(new ParsedDependency(origin.getIdentifier(), target.getIdentifier()));
Optional<PlantUmlComponent> origin = tryFindComponentMatching(plantUmlComponents, matcher.matchOrigin());
Optional<PlantUmlComponent> target = tryFindComponentMatching(plantUmlComponents, matcher.matchTarget());
if (origin.isPresent() && target.isPresent()) {
result.add(new ParsedDependency(origin.get().getIdentifier(), target.get().getIdentifier()));
} else {
log.trace(
"Ignoring dependency from {} to {}, because origin or target couldn't be parsed",
matcher.matchOrigin(), matcher.matchTarget()
);
}
}
return result.build();
}
Expand Down Expand Up @@ -113,11 +124,11 @@ private ImmutableSet<Stereotype> identifyStereotypes(PlantUmlComponentMatcher ma
return result;
}

private PlantUmlComponent findComponentMatching(PlantUmlComponents plantUmlComponents, String originOrTargetString) {
private Optional<PlantUmlComponent> tryFindComponentMatching(PlantUmlComponents plantUmlComponents, String originOrTargetString) {
originOrTargetString = originOrTargetString.trim()
.replaceAll("^\\[", "")
.replaceAll("]$", "");

return plantUmlComponents.findComponentWith(originOrTargetString);
return plantUmlComponents.tryFindComponentWith(originOrTargetString);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -206,18 +206,6 @@ public void does_not_include_dependency_descriptions() {
.isEqualTo(new ComponentName("otherComponent"));
}

@Test
public void throws_exception_with_components_that_are_not_yet_defined() {
File file = TestDiagram.in(temporaryFolder)
.dependencyFrom("[NotYetDefined]").to("[AlsoNotYetDefined]")
.write();

assertThatThrownBy(() -> createDiagram(file))
.isInstanceOf(IllegalDiagramException.class)
.hasMessageContaining("There is no Component with name or alias = 'NotYetDefined'")
.hasMessageContaining("Components must be specified separately from dependencies");
}

@Test
public void throws_exception_with_components_without_stereotypes() {
File file = TestDiagram.in(temporaryFolder)
Expand Down Expand Up @@ -418,6 +406,37 @@ public void parses_a_component_diagram_that_uses_alias_with_and_without_brackets
assertThat(bar.getDependencies()).isEmpty();
}

@Test
public void ignores_components_that_are_not_yet_defined() {
File file = TestDiagram.in(temporaryFolder)
.dependencyFrom("[NotYetDefined]").to("[AlsoNotYetDefined]")
.write();

PlantUmlDiagram diagram = createDiagram(file);

assertThat(diagram.getComponentsWithAlias()).isEmpty();
}

@Test
public void ignores_database_components() {
File file = TestDiagram.in(temporaryFolder)
.rawLine("database \"DB\"")
.component("componentA").withAlias("aliasA").withStereoTypes("..packageA..")
.component("componentB").withAlias("aliasB").withStereoTypes("..packageB..")
.dependencyFrom("aliasA").to("aliasB")
.dependencyFrom("aliasB").to("DB")
.dependencyFrom("componentA").to("DB")
.write();

PlantUmlDiagram diagram = createDiagram(file);

PlantUmlComponent a = getComponentWithAlias(new Alias("aliasA"), diagram);
PlantUmlComponent b = getComponentWithAlias(new Alias("aliasB"), diagram);

assertThat(a.getDependencies()).containsOnly(b);
assertThat(b.getDependencies()).isEmpty();
}

private PlantUmlComponent getComponentWithName(String componentName, PlantUmlDiagram diagram) {
PlantUmlComponent component = diagram.getAllComponents().stream()
.filter(c -> c.getComponentName().asString().equals(componentName))
Expand Down

0 comments on commit c1ecd8c

Please sign in to comment.