# 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);
        }
    }