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