# NoDuplicateRelationshipsListener.java

    package org.thewonderlemming.c4plantuml.linter.rules.builtin;

    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;

    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 relationships are not declared more than once..
     *
     * @author thewonderlemming
     *
     */
    public class NoDuplicateRelationshipsListener extends C4BaseListener {

        private static final String MESSAGE_FORMAT = "Duplicate relationship found for <'%s' -> '%s': '%s'>";

        private final Set<String> declaredRelationShips = new HashSet<>();

        private final Reporter reporter;

        /**
         * Default constructor.
         *
         * @param reporter {@link Reporter} instance to report to.
         */
        public NoDuplicateRelationshipsListener(final Reporter reporter) {
            this.reporter = reporter;
        }

        /**
         * Collects the relationship definition in a {@link SourceType#C4_L1} grammar.
         * <p>
         * {@inheritDoc}
         */
        @Override
        public void enterRelationship(final C4L1Parser.RelationshipContext ctx) {

            final List<TerminalNode> aliases = ctx.Alias();
            final TerminalNode sourceAlias = aliases.get(0);
            final TerminalNode targetAlias = aliases.get(1);
            final TerminalNode label = ctx.String();

            checkRelationshipIsUnique(sourceAlias, targetAlias, label);
        }

        /**
         * Collects the relationship definition in a {@link SourceType#C4_L2} grammar.
         * <p>
         * {@inheritDoc}
         */
        @Override
        public void enterRelationship(final C4L2Parser.RelationshipContext ctx) {

            final List<TerminalNode> aliases = ctx.Alias();
            final TerminalNode sourceAlias = aliases.get(0);
            final TerminalNode targetAlias = aliases.get(1);
            final TerminalNode label = ctx.String(0);

            checkRelationshipIsUnique(sourceAlias, targetAlias, label);
        }

        /**
         * Collects the relationship definition in a {@link SourceType#C4_L3} grammar.
         * <p>
         * {@inheritDoc}
         */
        @Override
        public void enterRelationship(final C4L3Parser.RelationshipContext ctx) {

            final List<TerminalNode> aliases = ctx.Alias();
            final TerminalNode sourceAlias = aliases.get(0);
            final TerminalNode targetAlias = aliases.get(1);
            final TerminalNode label = ctx.String(0);

            checkRelationshipIsUnique(sourceAlias, targetAlias, label);
        }

        private String buildErrorMessage(final TerminalNode sourceAlias, final TerminalNode targetAlias,
            final TerminalNode label) {

            return String.format(MESSAGE_FORMAT, sourceAlias.getText(), targetAlias.getText(), label.getText());
        }

        private void checkRelationshipIsUnique(final TerminalNode sourceAlias, final TerminalNode targetAlias,
            final TerminalNode label) {

            final String relationship = convertRelationshipToString(sourceAlias, targetAlias, label);

            if (this.declaredRelationShips.contains(relationship)) {

                final String errorMessage = buildErrorMessage(sourceAlias, targetAlias, label);
                this.reporter.report(errorMessage);

            } else {
                this.declaredRelationShips.add(relationship);
            }
        }

        private String convertRelationshipToString(final TerminalNode sourceAlias, final TerminalNode targetAlias,
            final TerminalNode label) {

            return sourceAlias.getText() + targetAlias.getText() + label.getText();
        }
    }