# C4GraphParseTreeListener.java

    package org.thewonderlemming.c4plantuml.graphml;

    import java.nio.file.Path;
    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.ParseTree;
    import org.antlr.v4.runtime.tree.ParseTreeListener;
    import org.antlr.v4.runtime.tree.ParseTreeWalker;
    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.C4L2ParserListenerDecorator;
    import org.thewonderlemming.c4plantuml.grammars.C4L3ParserListenerDecorator;
    import org.thewonderlemming.c4plantuml.grammars.generated.C4L1ParserListener;
    import org.thewonderlemming.c4plantuml.grammars.generated.C4L2ParserListener;
    import org.thewonderlemming.c4plantuml.grammars.generated.C4L3ParserListener;
    import org.thewonderlemming.c4plantuml.graphml.model.GraphMLModel;
    import org.thewonderlemming.c4plantuml.graphml.model.GraphModel;
    import org.thewonderlemming.c4plantuml.graphml.model.NodeModel;
    import org.thewonderlemming.c4plantuml.graphml.parse.C4L1GraphParseTreeListener;
    import org.thewonderlemming.c4plantuml.graphml.parse.C4L2GraphParseTreeListener;
    import org.thewonderlemming.c4plantuml.graphml.parse.C4L3GraphParseTreeListener;
    import org.thewonderlemming.c4plantuml.graphml.parse.C4ParseException;

    /**
     * An implementation of the ANTLR 4 {@link ParseTreeListener} that translates the C4 syntax tree to a JAXB model..
     *
     * @author thewonderlemming
     *
     */
    public class C4GraphParseTreeListener
        implements C4L1ParserListenerDecorator, C4L2ParserListenerDecorator, C4L3ParserListenerDecorator {

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

        private final C4L1GraphParseTreeListener c4l1Listener = new C4L1GraphParseTreeListener();

        private final C4L2GraphParseTreeListener c4l2Listener = new C4L2GraphParseTreeListener();

        private final GraphModel c4l3Graph;

        private final C4L3GraphParseTreeListener c4l3Listener = new C4L3GraphParseTreeListener();

        private final List<NodeModel> c4l3Nodes;

        private Path currentPath;

        private final List<Throwable> exceptions;

        private final GraphMLModel model;

        private final boolean throwOnParseError;

        /**
         * A constructor that turns off the failing of the process on syntax errors.
         *
         * @param model the empty JAXB model instance to fill in.
         */
        public C4GraphParseTreeListener(final GraphMLModel model) {
            this(model, false);
        }

        /**
         * Default constructor.
         *
         * @param model the empty JAXB model instance to fill in.
         * @param throwOnParseError a flag that tells whether or not the process should stop and throw on syntax errors.
         */
        public C4GraphParseTreeListener(final GraphMLModel model, final boolean throwOnParseError) {

            this.throwOnParseError = throwOnParseError;
            this.exceptions = new ArrayList<>();
            this.model = model;
            this.c4l3Nodes = new ArrayList<>();
            this.c4l3Graph = GraphModel
                .builder()
                    .withId("c4l3")
                    .withDefaultDirection()
                    .withoutData()
                    .withoutNodes()
                    .withoutEdges()
                    .build();
        }

        /**
         * Does nothing.
         * <p>
         * {@inheritDoc}
         */
        @Override
        public void enterEveryRule(final ParserRuleContext ctx) {
            // Base silent implementation. Override this to add behavior
        }

        /**
         * Does nothing.
         * <p>
         * {@inheritDoc}
         */
        @Override
        public void exitEveryRule(final ParserRuleContext ctx) {
            // Base silent implementation. Override this to add behavior
        }

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

        /**
         * {@inheritDoc}
         */
        @Override
        public C4L2ParserListener getDecoratedC4L2ParserListener() {
            return this.c4l2Listener;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public C4L3ParserListener getDecoratedC4L3ParserListener() {
            return this.c4l3Listener;
        }

        /**
         * Initializes the inner parsers and walks through the given {@link ParseTree} to fill in the {@link GraphMLModel}.
         *
         * @param tree the {@link ParseTree} instance that represents the current C4 source file (an AST).
         * @param currentPath the path to the current C4 source file.
         */
        public void parseTree(final ParseTree tree, final Path currentPath) {

            this.currentPath = currentPath;
            this.c4l3Listener.reset(this.c4l3Nodes.size());
            new ParseTreeWalker().walk(this, tree);
            stackNewC4L3GraphIfNotEmpty();
        }

        /**
         * Logs syntax errors and throws a {@link C4ParseException} if the
         * {@link C4GraphParseTreeListener#throwOnParseError} flag is truned on.
         * <p>
         * {@inheritDoc}
         */
        @Override
        public void visitErrorNode(final ErrorNode node) {

            final String errMsg = String.format("Parse error in file '%s': %s", this.currentPath, node.getPayload());

            LOGGER.error(errMsg);

            if (this.throwOnParseError) {
                throw new C4ParseException(errMsg);
            }
        }

        /**
         * Does nothing.
         * <p>
         * {@inheritDoc}
         */
        @Override
        public void visitTerminal(final TerminalNode node) {
            // Base silent implementation. Override this to add behavior
        }

        /**
         * Merges the computes graphs into the given {@link GraphMLModel} instance.
         *
         * @throws Throwable if any exception was collected during the process.
         */
        public void wrapUp() throws Throwable {

            addL1GraphToGraphML();
            addL2GraphToGraphML();
            addL3GraphToGraphML();

            if (!this.exceptions.isEmpty()) {
                LOGGER.error("Some errors were encountered during the parsing. Throwing the first one.");
                throw this.exceptions.get(0);
            }
        }

        private void addL1GraphToGraphML() {

            if (this.c4l1Listener.getGraph().isNotEmpty()) {
                this.model.addGraph(this.c4l1Listener.getGraph());
            }
        }

        private void addL2GraphToGraphML() {

            this.exceptions.addAll(this.c4l1Listener.getUnrecoverableErrors());

            if (this.c4l2Listener.getGraph().isNotEmpty()) {
                this.model.addGraph(this.c4l2Listener.getGraph());
            }
        }

        private void addL3GraphToGraphML() {

            this.exceptions.addAll(this.c4l2Listener.getUnrecoverableErrors());
            this.c4l3Nodes.forEach(this.c4l3Graph::addOrReplaceNode);

            if (this.c4l3Graph.isNotEmpty()) {
                this.model.addGraph(c4l3Graph);
            }
        }

        private void stackNewC4L3GraphIfNotEmpty() {

            final GraphModel newC4L3Graph = this.c4l3Listener.getGraph();

            if (newC4L3Graph.isNotEmpty()) {

                final NodeModel c4l3Node = NodeModel
                    .builder()
                        .withId("c4l3::" + this.c4l3Nodes.size())
                        .withoutData()
                        .build();

                c4l3Node.setGraph(newC4L3Graph);
                this.c4l3Nodes.add(c4l3Node);

                this.exceptions.addAll(this.c4l3Listener.getUnrecoverableErrors());
            }
        }
    }