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