Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/main/java/de/vill/main/IterativeParseTreeWalker.java
Original file line number Diff line number Diff line change
@@ -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<Frame> 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;
}
}
}
123 changes: 74 additions & 49 deletions src/main/java/de/vill/main/UVLModelFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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<Constraint> 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<Constraint> 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<Expression> 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<Expression> subExpressions = current.getExpressionSubParts();
for (int i = subExpressions.size() - 1; i >= 0; i--) {
stack.push(subExpressions.get(i));
}
}
}
Expand Down Expand Up @@ -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<Constraint> 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<Constraint> 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) {
Expand Down
44 changes: 44 additions & 0 deletions src/test/java/de/vill/parsing/LongConstraintParsingTest.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
Loading