diff --git a/src/main/java/de/vill/main/IterativeParseTreeWalker.java b/src/main/java/de/vill/main/IterativeParseTreeWalker.java
new file mode 100644
index 0000000..b06e06a
--- /dev/null
+++ b/src/main/java/de/vill/main/IterativeParseTreeWalker.java
@@ -0,0 +1,69 @@
+package de.vill.main;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+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.RuleNode;
+import org.antlr.v4.runtime.tree.TerminalNode;
+
+/**
+ * Iterative variant of ANTLR's ParseTreeWalker.
+ *
+ * ANTLR's default ParseTreeWalker recursively walks the parse tree.
+ * Deeply nested constraints such as F0 | F1 | ... | F6999 can therefore
+ * overflow the Java call stack before the UVL model is constructed.
+ */
+final class IterativeParseTreeWalker extends ParseTreeWalker {
+
+ @Override
+ public void walk(ParseTreeListener listener, ParseTree tree) {
+ final Deque stack = new ArrayDeque<>();
+ stack.push(new Frame(tree));
+
+ while (!stack.isEmpty()) {
+ final Frame frame = stack.peek();
+ final ParseTree current = frame.tree;
+
+ if (current instanceof ErrorNode) {
+ listener.visitErrorNode((ErrorNode) current);
+ stack.pop();
+ continue;
+ }
+
+ if (current instanceof TerminalNode) {
+ listener.visitTerminal((TerminalNode) current);
+ stack.pop();
+ continue;
+ }
+
+ final RuleNode ruleNode = (RuleNode) current;
+
+ if (!frame.entered) {
+ enterRule(listener, ruleNode);
+ frame.entered = true;
+ }
+
+ if (frame.nextChildIndex < current.getChildCount()) {
+ stack.push(new Frame(current.getChild(frame.nextChildIndex)));
+ frame.nextChildIndex++;
+ } else {
+ exitRule(listener, ruleNode);
+ stack.pop();
+ }
+ }
+ }
+
+ private static final class Frame {
+ private final ParseTree tree;
+ private boolean entered;
+ private int nextChildIndex;
+
+ private Frame(ParseTree tree) {
+ this.tree = tree;
+ }
+ }
+}
diff --git a/src/main/java/de/vill/main/UVLModelFactory.java b/src/main/java/de/vill/main/UVLModelFactory.java
index 483e512..924c1a0 100644
--- a/src/main/java/de/vill/main/UVLModelFactory.java
+++ b/src/main/java/de/vill/main/UVLModelFactory.java
@@ -33,7 +33,6 @@
import org.antlr.v4.runtime.ConsoleErrorListener;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
-import org.antlr.v4.runtime.tree.ParseTreeWalker;
import java.io.IOException;
import java.nio.file.FileSystems;
@@ -134,7 +133,8 @@ public void syntaxError(Recognizer, ?> recognizer, Object offendingSymbol, int
});
UVLListener uvlListener = createUVLListener();
- ParseTreeWalker walker = new ParseTreeWalker();
+ IterativeParseTreeWalker walker = new IterativeParseTreeWalker();
+
walker.walk(uvlListener, UVLJavaParser.constraintLine());
return uvlListener.getConstraint();
@@ -314,8 +314,10 @@ public void syntaxError(Recognizer, ?> recognizer, Object offendingSymbol, int
});
+
UVLListener uvlListener = createUVLListener();
- ParseTreeWalker walker = new ParseTreeWalker();
+ IterativeParseTreeWalker walker = new IterativeParseTreeWalker();
+
walker.walk(uvlListener, UVLJavaParser.featureModel());
FeatureModel featureModel = null;
@@ -388,42 +390,55 @@ public void syntaxError(Recognizer, ?> recognizer, Object offendingSymbol, int
}
private void resolveImportPlaceholders(Constraint constraint, FeatureModel featureModel) {
- if (constraint instanceof AndConstraint || constraint instanceof OrConstraint || constraint instanceof NotConstraint || constraint instanceof ImplicationConstraint || constraint instanceof ParenthesisConstraint || constraint instanceof EquivalenceConstraint) {
- for (Constraint subPart : constraint.getConstraintSubParts()) {
- resolveImportPlaceholders(subPart, featureModel);
+ final Deque stack = new ArrayDeque<>();
+ stack.push(constraint);
+
+ while (!stack.isEmpty()) {
+ final Constraint current = stack.pop();
+
+ if (current instanceof ExpressionConstraint) {
+ ExpressionConstraint expressionConstraint = (ExpressionConstraint) current;
+ resolveImportPlaceholders(expressionConstraint.getLeft(), featureModel);
+ resolveImportPlaceholders(expressionConstraint.getRight(), featureModel);
+ } else if (current instanceof LiteralConstraint) {
+ LiteralConstraint literalConstraint = (LiteralConstraint) current;
+ if (literalConstraint.getReference() instanceof ImportedVariablePlaceholder) {
+ ImportedVariablePlaceholder placeholder = (ImportedVariablePlaceholder) literalConstraint.getReference();
+ literalConstraint.setReference(resolvePlaceholder(placeholder, featureModel));
+ }
}
- } else if (constraint instanceof ExpressionConstraint) {
- ExpressionConstraint expressionConstraint = (ExpressionConstraint) constraint;
- resolveImportPlaceholders(expressionConstraint.getLeft(), featureModel);
- resolveImportPlaceholders(expressionConstraint.getRight(), featureModel);
- } else if (constraint instanceof LiteralConstraint) {
- LiteralConstraint literalConstraint = (LiteralConstraint) constraint;
- if (literalConstraint.getReference() instanceof ImportedVariablePlaceholder) {
- ImportedVariablePlaceholder placeholder = (ImportedVariablePlaceholder) literalConstraint.getReference();
- literalConstraint.setReference(resolvePlaceholder(placeholder, featureModel));
+
+ final List subConstraints = current.getConstraintSubParts();
+ for (int i = subConstraints.size() - 1; i >= 0; i--) {
+ stack.push(subConstraints.get(i));
}
}
}
private void resolveImportPlaceholders(Expression expression, FeatureModel featureModel) {
- if (expression instanceof BinaryExpression) {
- BinaryExpression binaryExpression = (BinaryExpression) expression;
- resolveImportPlaceholders(binaryExpression.getLeft(), featureModel);
- resolveImportPlaceholders(binaryExpression.getRight(), featureModel);
- } else if (expression instanceof ParenthesisExpression) {
- ParenthesisExpression parenthesisExpression = (ParenthesisExpression) expression;
- resolveImportPlaceholders(parenthesisExpression.getContent(), featureModel);
- } else if (expression instanceof LengthAggregateFunctionExpression) {
- LengthAggregateFunctionExpression lengthAggregateFunctionExpression = (LengthAggregateFunctionExpression) expression;
- if (lengthAggregateFunctionExpression.getReference() instanceof ImportedVariablePlaceholder) {
- ImportedVariablePlaceholder placeholder = (ImportedVariablePlaceholder) lengthAggregateFunctionExpression.getReference();
- lengthAggregateFunctionExpression.setReference(resolvePlaceholder(placeholder, featureModel));
+ final Deque stack = new ArrayDeque<>();
+ stack.push(expression);
+
+ while (!stack.isEmpty()) {
+ final Expression current = stack.pop();
+
+ if (current instanceof LengthAggregateFunctionExpression) {
+ LengthAggregateFunctionExpression lengthAggregateFunctionExpression = (LengthAggregateFunctionExpression) current;
+ if (lengthAggregateFunctionExpression.getReference() instanceof ImportedVariablePlaceholder) {
+ ImportedVariablePlaceholder placeholder = (ImportedVariablePlaceholder) lengthAggregateFunctionExpression.getReference();
+ lengthAggregateFunctionExpression.setReference(resolvePlaceholder(placeholder, featureModel));
+ }
+ } else if (current instanceof LiteralExpression) {
+ LiteralExpression literalExpression = (LiteralExpression) current;
+ if (literalExpression.getContent() instanceof ImportedVariablePlaceholder) {
+ ImportedVariablePlaceholder placeholder = (ImportedVariablePlaceholder) literalExpression.getContent();
+ literalExpression.setContent(resolvePlaceholder(placeholder, featureModel));
+ }
}
- } else if (expression instanceof LiteralExpression) {
- LiteralExpression literalExpression = (LiteralExpression) expression;
- if (literalExpression.getContent() instanceof ImportedVariablePlaceholder) {
- ImportedVariablePlaceholder placeholder = (ImportedVariablePlaceholder) literalExpression.getContent();
- literalExpression.setContent(resolvePlaceholder(placeholder, featureModel));
+
+ final List subExpressions = current.getExpressionSubParts();
+ for (int i = subExpressions.size() - 1; i >= 0; i--) {
+ stack.push(subExpressions.get(i));
}
}
}
@@ -521,28 +536,38 @@ private void validateTypeLevelConstraints(final FeatureModel featureModel) {
}
private boolean validateTypeLevelConstraint(final Constraint constraint) {
- boolean result = true;
- if (constraint instanceof ExpressionConstraint) {
- String leftReturnType = ((ExpressionConstraint) constraint).getLeft().getReturnType();
- String rightReturnType = ((ExpressionConstraint) constraint).getRight().getReturnType();
+ final Deque stack = new ArrayDeque<>();
+ stack.push(constraint);
- if (!(leftReturnType.equalsIgnoreCase(Constants.TRUE) || rightReturnType.equalsIgnoreCase(Constants.TRUE))) {
- // if not attribute constraint
- result = result && ((ExpressionConstraint) constraint).getLeft().getReturnType().equalsIgnoreCase(((ExpressionConstraint) constraint).getRight().getReturnType());
- }
- if (!result) {
- return false;
- }
- for (final Expression expr: ((ExpressionConstraint) constraint).getExpressionSubParts()) {
- result = result && validateTypeLevelExpression(expr);
+ while (!stack.isEmpty()) {
+ final Constraint current = stack.pop();
+
+ if (current instanceof ExpressionConstraint) {
+ final ExpressionConstraint expressionConstraint = (ExpressionConstraint) current;
+
+ final String leftReturnType = expressionConstraint.getLeft().getReturnType();
+ final String rightReturnType = expressionConstraint.getRight().getReturnType();
+
+ if (!(leftReturnType.equalsIgnoreCase(Constants.TRUE) || rightReturnType.equalsIgnoreCase(Constants.TRUE))) {
+ if (!leftReturnType.equalsIgnoreCase(rightReturnType)) {
+ return false;
+ }
+ }
+
+ for (final Expression expr : expressionConstraint.getExpressionSubParts()) {
+ if (!validateTypeLevelExpression(expr)) {
+ return false;
+ }
+ }
}
- }
- for (final Constraint subCons: constraint.getConstraintSubParts()) {
- result = result && validateTypeLevelConstraint(subCons);
+ final List subConstraints = current.getConstraintSubParts();
+ for (int i = subConstraints.size() - 1; i >= 0; i--) {
+ stack.push(subConstraints.get(i));
+ }
}
- return result;
+ return true;
}
private boolean validateTypeLevelExpression(final Expression expression) {
diff --git a/src/test/java/de/vill/parsing/LongConstraintParsingTest.java b/src/test/java/de/vill/parsing/LongConstraintParsingTest.java
new file mode 100644
index 0000000..cb47478
--- /dev/null
+++ b/src/test/java/de/vill/parsing/LongConstraintParsingTest.java
@@ -0,0 +1,44 @@
+package de.vill.parsing;
+
+import de.vill.main.UVLModelFactory;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+class LongConstraintParsingTest {
+
+ @Test
+ void parsesLongOrConstraint() {
+ final int numberOfLiterals = 7000;
+ final String model = createModel(numberOfLiterals);
+
+ final UVLModelFactory factory = new UVLModelFactory();
+
+ assertDoesNotThrow(() -> factory.parse(model));
+ }
+
+ private String createModel(int numberOfLiterals) {
+ final StringBuilder builder = new StringBuilder();
+
+ builder.append("features\n");
+ builder.append(" Root\n");
+ builder.append(" optional\n");
+
+ for (int i = 0; i < numberOfLiterals; i++) {
+ builder.append(" F").append(i).append("\n");
+ }
+
+ builder.append("constraints\n");
+ builder.append(" ");
+
+ for (int i = 0; i < numberOfLiterals; i++) {
+ if (i > 0) {
+ builder.append(" | ");
+ }
+ builder.append("F").append(i);
+ }
+
+ builder.append("\n");
+ return builder.toString();
+ }
+}