NoOrphanAliasInRelationshipsListener.java
package org.thewonderlemming.c4plantuml.linter.rules.builtin;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ParseTreeListener;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.thewonderlemming.c4plantuml.commons.Reporter;
import org.thewonderlemming.c4plantuml.grammars.C4BaseListener;
import org.thewonderlemming.c4plantuml.grammars.SourceType;
import org.thewonderlemming.c4plantuml.grammars.generated.C4L1Parser;
import org.thewonderlemming.c4plantuml.grammars.generated.C4L2Parser;
import org.thewonderlemming.c4plantuml.grammars.generated.C4L3Parser;
/**
* An ANTLR 4 {@link ParseTreeListener} implementation that verifies that aliases in use in a C4 relationship are all
* referring to a valid entity.
*
* @author thewonderlemming
*
*/
public class NoOrphanAliasInRelationshipsListener extends C4BaseListener {
private class UsedAlias {
public final String alias;
public final ParserRuleContext context;
public UsedAlias(final TerminalNode alias, final ParserRuleContext context) {
this.alias = alias.getText();
this.context = context;
}
}
private static final String MESSAGE_FORMAT = "Alias <%s> is used in the relationship <'%s' -> '%s': '%s'> without being properly declared";
private final Set<String> declaredAliases = new HashSet<>();
private final Reporter reporter;
private final NoOrphanAliasInRelationshipsRule rule;
private final List<UsedAlias> usedAliases = new ArrayList<>();
/**
* Default constructor.
*
* @param rule the {@link NoOrphanAliasInRelationshipsRule} instance to refer to.
* @param reporter {@link Reporter} instance to report to.
*/
public NoOrphanAliasInRelationshipsListener(final NoOrphanAliasInRelationshipsRule rule, final Reporter reporter) {
this.rule = rule;
this.reporter = reporter;
}
/**
* Verifies that the aliases that were collected in relationships were all declared in an entity, or report them.
* Cleans the aliases list once done.
*/
public void checkThatNoAliasIsOrphanThenClearCollected() {
this.usedAliases.forEach(usedAlias -> {
if (!this.declaredAliases.contains(usedAlias.alias)) {
final String message = buildErrorMessageFromContext(usedAlias);
this.reporter.report(message);
}
});
this.usedAliases.clear();
this.declaredAliases.clear();
}
/**
* Collects the alias of a cloud definition in a {@link SourceType#C4_L1} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterCloud(final C4L1Parser.CloudContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the alias of a cloud definition in a {@link SourceType#C4_L2} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterCloud(final C4L2Parser.CloudContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the alias of a cloud definition in a {@link SourceType#C4_L3} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterCloud(final C4L3Parser.CloudContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the alias of a component definition in a {@link SourceType#C4_L3} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterComponent(final C4L3Parser.ComponentContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the alias of a container definition in a {@link SourceType#C4_L2} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterContainer(final C4L2Parser.ContainerContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the alias of a container definition in a {@link SourceType#C4_L3} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterContainer(final C4L3Parser.ContainerContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the aliases of a container boundary definition in a {@link SourceType#C4_L3} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterContainer_boundary(final C4L3Parser.Container_boundaryContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the aliases of an enterprise boundary definition in a {@link SourceType#C4_L1} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterEnterprise_boundary(final C4L1Parser.Enterprise_boundaryContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the aliases of an enterprise boundary definition in a {@link SourceType#C4_L2} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterEnterprise_boundary(final C4L2Parser.Enterprise_boundaryContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the aliases of an enterprise boundary definition in a {@link SourceType#C4_L3} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterEnterprise_boundary(final C4L3Parser.Enterprise_boundaryContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the alias of a person definition in a {@link SourceType#C4_L1} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterPerson(final C4L1Parser.PersonContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the alias of a person definition in a {@link SourceType#C4_L2} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterPerson(final C4L2Parser.PersonContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the alias of a person definition in a {@link SourceType#C4_L3} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterPerson(final C4L3Parser.PersonContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the alias of an external person definition in a {@link SourceType#C4_L1} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterPerson_ext(final C4L1Parser.Person_extContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the alias of an external person definition in a {@link SourceType#C4_L2} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterPerson_ext(final C4L2Parser.Person_extContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the alias of an external person definition in a {@link SourceType#C4_L3} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterPerson_ext(final C4L3Parser.Person_extContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the aliases of a relationship in a {@link SourceType#C4_L1} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterRelationship(final C4L1Parser.RelationshipContext ctx) {
ctx.Alias().forEach(alias -> collectUsedAlias(alias, ctx));
}
/**
* Collects the aliases of a relationship definition in a {@link SourceType#C4_L2} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterRelationship(final C4L2Parser.RelationshipContext ctx) {
ctx.Alias().forEach(alias -> collectUsedAlias(alias, ctx));
}
/**
* Collects the aliases of a relationship definition in a {@link SourceType#C4_L3} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterRelationship(final C4L3Parser.RelationshipContext ctx) {
ctx.Alias().forEach(alias -> collectUsedAlias(alias, ctx));
}
/**
* Collects the alias of a system definition in a {@link SourceType#C4_L1} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterSystem(final C4L1Parser.SystemContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the alias of a system definition in a {@link SourceType#C4_L2} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterSystem(final C4L2Parser.SystemContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the alias of a system definition in a {@link SourceType#C4_L3} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterSystem(final C4L3Parser.SystemContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the alias of a system boundary definition in a {@link SourceType#C4_L2} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterSystem_boundary(final C4L2Parser.System_boundaryContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the alias of an external system definition in a {@link SourceType#C4_L1} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterSystem_ext(final C4L1Parser.System_extContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the alias of an external system definition in a {@link SourceType#C4_L2} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterSystem_ext(final C4L2Parser.System_extContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
/**
* Collects the alias of an external system definition in a {@link SourceType#C4_L3} grammar.
* <p>
* {@inheritDoc}
*/
@Override
public void enterSystem_ext(final C4L3Parser.System_extContext ctx) {
collectDeclaredAlias(ctx.Alias());
}
private String buildErrorMessageFromC4L1Context(final UsedAlias usedAlias) {
final C4L1Parser.RelationshipContext ctx = ((C4L1Parser.RelationshipContext) usedAlias.context);
final String sourceAlias = ctx.Alias(0).getText();
final String targetAlias = ctx.Alias(1).getText();
final String label = ctx.String().getText();
return String.format(MESSAGE_FORMAT, usedAlias.alias, sourceAlias, targetAlias, label);
}
private String buildErrorMessageFromC4L2Context(final UsedAlias usedAlias) {
final C4L2Parser.RelationshipContext ctx = ((C4L2Parser.RelationshipContext) usedAlias.context);
final String sourceAlias = ctx.Alias(0).getText();
final String targetAlias = ctx.Alias(1).getText();
final String label = ctx.String(0).getText();
return String.format(MESSAGE_FORMAT, usedAlias.alias, sourceAlias, targetAlias, label);
}
private String buildErrorMessageFromC4L3Context(final UsedAlias usedAlias) {
final C4L3Parser.RelationshipContext ctx = ((C4L3Parser.RelationshipContext) usedAlias.context);
final String sourceAlias = ctx.Alias(0).getText();
final String targetAlias = ctx.Alias(1).getText();
final String label = ctx.String(0).getText();
return String.format(MESSAGE_FORMAT, usedAlias.alias, sourceAlias, targetAlias, label);
}
private String buildErrorMessageFromContext(final UsedAlias usedAlias) {
if (usedAlias.context instanceof C4L1Parser.RelationshipContext) {
return buildErrorMessageFromC4L1Context(usedAlias);
} else if (usedAlias.context instanceof C4L2Parser.RelationshipContext) {
return buildErrorMessageFromC4L2Context(usedAlias);
} else if (usedAlias.context instanceof C4L3Parser.RelationshipContext) {
return buildErrorMessageFromC4L3Context(usedAlias);
}
this.rule
.getLogger()
.warn(
"The rule was unable to retrieve relationship information. Maybe the current source file is malformed?");
return String.format(MESSAGE_FORMAT, usedAlias.alias, "?", "?", "?");
}
private void collectDeclaredAlias(final TerminalNode alias) {
this.declaredAliases.add(alias.getText());
}
private void collectUsedAlias(final TerminalNode alias, final ParserRuleContext context) {
final UsedAlias used = new UsedAlias(alias, context);
this.usedAliases.add(used);
}
}