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