# C4L1GraphParseTreeListener.java

    package org.thewonderlemming.c4plantuml.graphml.parse;

    import java.util.ArrayList;
    import java.util.List;

    import org.antlr.v4.runtime.ParserRuleContext;
    import org.antlr.v4.runtime.tree.ErrorNode;
    import org.antlr.v4.runtime.tree.ParseTreeListener;
    import org.antlr.v4.runtime.tree.TerminalNode;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.thewonderlemming.c4plantuml.grammars.C4L1ParserListenerDecorator;
    import org.thewonderlemming.c4plantuml.grammars.SourceType;
    import org.thewonderlemming.c4plantuml.grammars.generated.C4L1Parser;
    import org.thewonderlemming.c4plantuml.grammars.generated.C4L1ParserBaseListener;
    import org.thewonderlemming.c4plantuml.grammars.generated.C4L1ParserListener;
    import org.thewonderlemming.c4plantuml.graphml.model.CannotComputeEdgeModelId;
    import org.thewonderlemming.c4plantuml.graphml.model.EdgeModel;
    import org.thewonderlemming.c4plantuml.graphml.model.EntityType;
    import org.thewonderlemming.c4plantuml.graphml.model.GraphModel;
    import org.thewonderlemming.c4plantuml.graphml.model.NodeModel;

    /**
     * An ANTLR 4 {@link ParseTreeListener} that parses C4 L1 grammar and feeds the JAXB model with the retrieved
     * information.
     *
     * @author thewonderlemming
     *
     */
    public class C4L1GraphParseTreeListener implements C4L1ParserListenerDecorator {

        private static final Logger LOGGER = LoggerFactory.getLogger(C4L1GraphParseTreeListener.class);

        private final C4L1ParserBaseListener decorated = new C4L1ParserBaseListener();

        private final GraphModel graph;

        private final List<Throwable> unrecoverableErrors;

        /**
         * Default constructor.
         */
        public C4L1GraphParseTreeListener() {

            this.unrecoverableErrors = new ArrayList<>();
            this.graph = GraphModel
                .builder()
                    .withId("c4l1")
                    .withDefaultDirection()
                    .withoutData()
                    .withoutNodes()
                    .withoutEdges()
                    .build();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void enterCloud(final C4L1Parser.CloudContext ctx) {

            assert (ctx.String().size() == 2) : "Expected to find two strings within C4L1 Cloud rule";

            final String alias = ctx.Alias().getText();
            final String name = ctx.String(0).getText();
            final String description = ctx.String(1).getText();

            addNewNodeWithAlias(alias)
                .withName(name)
                    .withDescription(description)
                    .withEntityType(EntityType.C1_CLOUD);
        }

        /**
         * {@inheritDoc}
         * <p>
         * Does nothing.
         */
        @Override
        public void enterEveryRule(final ParserRuleContext ctx) {
            // This is too generic to be called in a C4L1Parser specific grammar
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void enterPerson(final C4L1Parser.PersonContext ctx) {

            assert (ctx.String().size() == 2) : "Expected to find two strings within C4L1 Person rule";

            final String alias = ctx.Alias().getText();
            final String name = ctx.String(0).getText();
            final String description = ctx.String(1).getText();

            addNewNodeWithAlias(alias)
                .withName(name)
                    .withDescription(description)
                    .withEntityType(EntityType.C1_PERSON);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void enterPerson_ext(final C4L1Parser.Person_extContext ctx) {

            assert (ctx.String().size() == 2) : "Expected to find two strings within C4L1 PersonExt rule";

            final String alias = ctx.Alias().getText();
            final String name = ctx.String(0).getText();
            final String description = ctx.String(1).getText();

            addNewNodeWithAlias(alias)
                .withName(name)
                    .withDescription(description)
                    .withEntityType(EntityType.C1_PERSON_EXT);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void enterRelationship(final C4L1Parser.RelationshipContext ctx) {

            assert (ctx.Alias().size() == 2) : "Expected to find two aliases within C4L1 Relationship rule";

            final String sourceAlias = ctx.Alias(0).getText();
            final String targetAlias = ctx.Alias(1).getText();
            final String label = ctx.String().getText();

            try {

                final String edgeId = EdgeModel
                    .generateIdFrom(this.graph.getId())
                        .withSource(sourceAlias)
                        .withTarget(targetAlias)
                        .withLabel(label)
                        .withoutProtocol()
                        .withC4L1Level();

                final EdgeModel relationshipEdge = EdgeModel
                    .builder()
                        .withId(edgeId)
                        .withSource(this.graph.getId() + "::" + sourceAlias)
                        .withTarget(this.graph.getId() + "::" + targetAlias)
                        .withoutData()
                        .build();

                relationshipEdge.setC4Level(SourceType.C4_L1);
                relationshipEdge.setLabel(label);

                this.graph.addOrReplaceEdge(relationshipEdge);

            } catch (final CannotComputeEdgeModelId e) {

                final String errMsg = String
                    .format("Cannot compute id for relationship '%s' -> '%s' = '%s' because of the following: %s",
                        sourceAlias,
                        targetAlias,
                        label,
                        e.getMessage());

                LOGGER.error(errMsg, e);
                this.unrecoverableErrors.add(new Exception(errMsg, e));
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void enterSystem(final C4L1Parser.SystemContext ctx) {

            assert (ctx.String().size() == 2) : "Expected to find two strings within C4L1 System rule";

            final String alias = ctx.Alias().getText();
            final String name = ctx.String(0).getText();
            final String description = ctx.String(1).getText();

            addNewNodeWithAlias(alias)
                .withName(name)
                    .withDescription(description)
                    .withEntityType(EntityType.C1_SYSTEM);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void enterSystem_ext(final C4L1Parser.System_extContext ctx) {

            assert (ctx.String().size() == 2) : "Expected to find two strings within C4L1 SystemExt rule";

            final String alias = ctx.Alias().getText();
            final String name = ctx.String(0).getText();
            final String description = ctx.String(1).getText();

            addNewNodeWithAlias(alias)
                .withName(name)
                    .withDescription(description)
                    .withEntityType(EntityType.C1_SYSTEM_EXT);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void enterTitle(final C4L1Parser.TitleContext ctx) {

            graph.setTitle(ctx.String(0).getText());

            if (ctx.String().size() > 1) {
                graph.setAspect(ctx.String(1).getText());
            }
        }

        /**
         * {@inheritDoc}
         * <p>
         * Does nothing.
         */
        @Override
        public void exitEveryRule(final ParserRuleContext ctx) {
            // This is too generic to be called in a C4L1Parser specific grammar
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public C4L1ParserListener getDecoratedC4L1ParserListener() {
            return this.decorated;
        }

        /**
         * Returns the result of the parsing as a graph.
         *
         * @return the graph result.
         */
        public GraphModel getGraph() {
            return this.graph;
        }

        /**
         * Returns any unrecoverable exceptions that could have occurred during the parsing process.
         *
         * @return the unrecoverable exceptions that happened during the parsing.
         */
        public List<Throwable> getUnrecoverableErrors() {
            return unrecoverableErrors;
        }

        /**
         * {@inheritDoc}
         * <p>
         * Does nothing but logging a generic message.
         */
        @Override
        public void visitErrorNode(final ErrorNode node) {
            LOGGER.debug("Parse errors are not handled there at this stage");
        }

        /**
         * {@inheritDoc}
         * <p>
         * Does nothing.
         */
        @Override
        public void visitTerminal(final TerminalNode node) {
            // This is too generic to be called in a C4L1Parser specific grammar
        }

        private WithName<WithDescription<WithEntityType<Void>>> addNewNodeWithAlias(final String alias) {

            return name -> description -> entityType -> {

                final NodeModel node = NodeModel
                    .builder()
                        .withId(this.graph.getId() + "::" + alias)
                        .withoutData()
                        .build();

                node.setName(name);
                node.setDescription(description);
                node.setEntityType(entityType);

                this.graph.addOrReplaceNode(node);
                return null;
            };
        }
    }