diff --git a/graphwalker-core/src/main/java/org/graphwalker/core/condition/AlternativeCondition.java b/graphwalker-core/src/main/java/org/graphwalker/core/condition/AlternativeCondition.java index 6bbee1658..40057f73b 100644 --- a/graphwalker-core/src/main/java/org/graphwalker/core/condition/AlternativeCondition.java +++ b/graphwalker-core/src/main/java/org/graphwalker/core/condition/AlternativeCondition.java @@ -90,7 +90,14 @@ public double getFulfilment() { @Override public StringBuilder toString(StringBuilder builder) { - return builder.append(conditions.stream().map(StopCondition::toString) + if (conditions.size()>1) { + builder.append("("); + } + builder.append(conditions.stream().map(StopCondition::toString) .collect(Collectors.joining(" OR "))); + if (conditions.size()>1) { + builder.append(")"); + } + return builder; } } diff --git a/graphwalker-core/src/main/java/org/graphwalker/core/condition/CombinedCondition.java b/graphwalker-core/src/main/java/org/graphwalker/core/condition/CombinedCondition.java index 51900dbdd..1e93e37f2 100644 --- a/graphwalker-core/src/main/java/org/graphwalker/core/condition/CombinedCondition.java +++ b/graphwalker-core/src/main/java/org/graphwalker/core/condition/CombinedCondition.java @@ -89,7 +89,14 @@ public double getFulfilment() { @Override public StringBuilder toString(StringBuilder builder) { - return builder.append(conditions.stream().map(StopCondition::toString) + if (conditions.size()>1) { + builder.append("("); + } + builder.append(conditions.stream().map(StopCondition::toString) .collect(Collectors.joining(" AND "))); + if (conditions.size()>1) { + builder.append(")"); + } + return builder; } } diff --git a/graphwalker-core/src/test/java/org/graphwalker/core/condition/AlternativeConditionTest.java b/graphwalker-core/src/test/java/org/graphwalker/core/condition/AlternativeConditionTest.java index 061b7dfda..0f683e9c8 100644 --- a/graphwalker-core/src/test/java/org/graphwalker/core/condition/AlternativeConditionTest.java +++ b/graphwalker-core/src/test/java/org/graphwalker/core/condition/AlternativeConditionTest.java @@ -69,7 +69,7 @@ public void testFulfilment() throws Exception { assertThat(condition.getFulfilment(), is(0.0)); machine.getNextStep(); assertThat(condition.getFulfilment(), is(1.0)); - assertThat(condition.toString(), is("VertexCoverage(100) OR ReachedEdge(e1)")); + assertThat(condition.toString(), is("(VertexCoverage(100) OR ReachedEdge(e1))")); } @Test @@ -87,7 +87,7 @@ public void testIsFulfilled() throws Exception { assertThat(condition.getFulfilment(), is(0.0)); machine.getNextStep(); assertTrue(condition.isFulfilled()); - assertThat(condition.toString(), is("VertexCoverage(100) OR ReachedEdge(e1)")); + assertThat(condition.toString(), is("(VertexCoverage(100) OR ReachedEdge(e1))")); } @Test @@ -99,10 +99,10 @@ public void testMultipleCondition() throws Exception { assertThat("Should be false", condition.isFulfilled(), is(false)); condition.addStopCondition(new Never()); assertThat("Should be false", condition.isFulfilled(), is(false)); - assertThat(condition.toString(), is("Never() OR Never() OR Never()")); + assertThat(condition.toString(), is("(Never() OR Never() OR Never())")); condition.addStopCondition(new Always()); assertThat("Should be true", condition.isFulfilled(), is(true)); - assertThat(condition.toString(), is("Never() OR Never() OR Never() OR Always()")); + assertThat(condition.toString(), is("(Never() OR Never() OR Never() OR Always())")); } private class Always extends StopConditionBase { diff --git a/graphwalker-core/src/test/java/org/graphwalker/core/condition/CombinedConditionTest.java b/graphwalker-core/src/test/java/org/graphwalker/core/condition/CombinedConditionTest.java index 4137b01bf..56fc5639b 100644 --- a/graphwalker-core/src/test/java/org/graphwalker/core/condition/CombinedConditionTest.java +++ b/graphwalker-core/src/test/java/org/graphwalker/core/condition/CombinedConditionTest.java @@ -62,6 +62,6 @@ public void testIsFulfilled() throws Exception { assertThat("Should be false", condition.isFulfilled(), is(false)); condition.addStopCondition(new Never()); assertThat("Should be false", condition.isFulfilled(), is(false)); - assertThat(condition.toString(), is("Never() AND Never() AND Never()")); + assertThat(condition.toString(), is("(Never() AND Never() AND Never())")); } } diff --git a/graphwalker-core/src/test/java/org/graphwalker/core/condition/ComplexConditionsTest.java b/graphwalker-core/src/test/java/org/graphwalker/core/condition/ComplexConditionsTest.java index 166c3c86a..0013b21e3 100644 --- a/graphwalker-core/src/test/java/org/graphwalker/core/condition/ComplexConditionsTest.java +++ b/graphwalker-core/src/test/java/org/graphwalker/core/condition/ComplexConditionsTest.java @@ -34,6 +34,8 @@ import org.graphwalker.core.model.*; import org.junit.Test; +import java.util.concurrent.TimeUnit; + /** * @author Nils Olsson */ @@ -150,4 +152,25 @@ public void alternativeCondition_vertex_edge() throws Exception { } } + // random((reached_vertex(v_Browse) AND edge_coverage(100)) OR time(10)) + @Test + public void and_plus_or() throws Exception { + Context context = new TestExecutionContext(); + + CombinedCondition c1 = new CombinedCondition(); + c1.addStopCondition(new ReachedVertex("v_Browse")); + c1.addStopCondition(new EdgeCoverage(100)); + + AlternativeCondition c2 = new AlternativeCondition(); + c2.addStopCondition(new TimeDuration(10, TimeUnit.SECONDS)); + c2.addStopCondition(c1); + + context.setModel(model.build()).setPathGenerator(new RandomPath(c2)); + context.setNextElement(context.getModel().findElements("e_Init").get(0)); + + Machine machine = new SimpleMachine(context); + while (machine.hasNextStep()) { + machine.getNextStep(); + } + } } diff --git a/graphwalker-dsl/src/main/antlr4/org/graphwalker/dsl/generator/GeneratorParser.g4 b/graphwalker-dsl/src/main/antlr4/org/graphwalker/dsl/generator/GeneratorParser.g4 index e0ba22f17..f8aae3606 100644 --- a/graphwalker-dsl/src/main/antlr4/org/graphwalker/dsl/generator/GeneratorParser.g4 +++ b/graphwalker-dsl/src/main/antlr4/org/graphwalker/dsl/generator/GeneratorParser.g4 @@ -16,13 +16,19 @@ generator ; logicalExpression - : booleanAndExpression ( OR booleanAndExpression )* + : primaryExpression + | andExpression + | orExpression ; -booleanAndExpression +andExpression : primaryExpression ( AND primaryExpression )* ; +orExpression + : primaryExpression ( OR primaryExpression )* + ; + primaryExpression : stopCondition | '(' logicalExpression ')' diff --git a/graphwalker-dsl/src/main/java/org/graphwalker/dsl/antlr/generator/GeneratorLoader.java b/graphwalker-dsl/src/main/java/org/graphwalker/dsl/antlr/generator/GeneratorLoader.java index 7011b83a6..26827daa0 100644 --- a/graphwalker-dsl/src/main/java/org/graphwalker/dsl/antlr/generator/GeneratorLoader.java +++ b/graphwalker-dsl/src/main/java/org/graphwalker/dsl/antlr/generator/GeneratorLoader.java @@ -46,59 +46,121 @@ public class GeneratorLoader extends GeneratorParserBaseListener { private StopCondition stopCondition = null; private final List pathGenerators = new ArrayList<>(); - private final List stopConditions = new ArrayList<>(); + + static public class Node{ + private T data = null; + private List children = new ArrayList<>(); + private Node parent = null; + + public Node(T data) { + this.data = data; + } + public void addChild(Node child) { + child.setParent(this); + this.children.add(child); + } + + public void addChild(T data) { + Node newChild = new Node<>(data); + this.addChild(newChild); + } + + public List getChildren() { + return children; + } + + public T getData() { + return data; + } + + private void setParent(Node parent) { + this.parent = parent; + } + } + + Node root = null; + Node currentNode = null; + + @Override + public void enterAndExpression(GeneratorParser.AndExpressionContext ctx) { + Node node = new Node(new CombinedCondition()); + currentNode.addChild(node); + currentNode = node; + } @Override - public void exitBooleanAndExpression(GeneratorParser.BooleanAndExpressionContext ctx) { - if (!ctx.AND().isEmpty()) { - CombinedCondition combinedCondition = new CombinedCondition(); - stopConditions.forEach(combinedCondition::addStopCondition); - stopCondition = combinedCondition; + public void exitAndExpression(GeneratorParser.AndExpressionContext ctx) { + CombinedCondition combinedCondition = (CombinedCondition) currentNode.getData(); + for ( Object child: currentNode.getChildren() ) { + combinedCondition.addStopCondition((StopCondition) ((Node)child).getData()); } + currentNode = currentNode.parent; + } + + @Override + public void enterOrExpression(GeneratorParser.OrExpressionContext ctx) { + Node node = new Node(new AlternativeCondition()); + currentNode.addChild(node); + currentNode = node; + } + + @Override + public void exitOrExpression(GeneratorParser.OrExpressionContext ctx) { + AlternativeCondition alternativeCondition = (AlternativeCondition) currentNode.getData(); + for ( Object child: currentNode.getChildren() ) { + alternativeCondition.addStopCondition((StopCondition) ((Node)child).getData()); + } + currentNode = currentNode.parent; } @Override public void exitStopCondition(GeneratorParser.StopConditionContext ctx) { String conditionName = ctx.getChild(0).getText().toLowerCase(); if ("never".equals(conditionName)) { - stopConditions.add(new Never()); + currentNode.addChild(new Never()); } else if ("edge_coverage".equals(conditionName) || "edgecoverage".equals(conditionName)) { - stopConditions.add(new EdgeCoverage(Integer.parseInt(ctx.getChild(2).getText()))); + currentNode.addChild(new EdgeCoverage(Integer.parseInt(ctx.getChild(2).getText()))); } else if ("vertex_coverage".equals(conditionName) || "vertexcoverage".equals(conditionName)) { - stopConditions.add(new VertexCoverage(Integer.parseInt(ctx.getChild(2).getText()))); + currentNode.addChild(new VertexCoverage(Integer.parseInt(ctx.getChild(2).getText()))); } else if ("reached_vertex".equals(conditionName) || "reachedvertex".equals(conditionName)) { - stopConditions.add(new ReachedVertex(ctx.getChild(2).getText())); + currentNode.addChild(new ReachedVertex(ctx.getChild(2).getText())); } else if ("reached_shared_state".equals(conditionName) || "reachedsharedstate".equals(conditionName)) { - stopConditions.add(new ReachedSharedState(ctx.getChild(2).getText())); + currentNode.addChild(new ReachedSharedState(ctx.getChild(2).getText())); } else if ("reached_edge".equals(conditionName) || "reachededge".equals(conditionName)) { - stopConditions.add(new ReachedEdge(ctx.getChild(2).getText())); + currentNode.addChild(new ReachedEdge(ctx.getChild(2).getText())); } else if ("time_duration".equals(conditionName) || "timeduration".equals(conditionName)) { - stopConditions.add(new TimeDuration(Long.parseLong(ctx.getChild(2).getText()), TimeUnit.SECONDS)); + currentNode.addChild(new TimeDuration(Long.parseLong(ctx.getChild(2).getText()), TimeUnit.SECONDS)); } else if ("dependency_edge_coverage".equals(conditionName) || "dependencyedgecoverage".equals(conditionName)) { - stopConditions.add(new DependencyEdgeCoverage(Integer.parseInt(ctx.getChild(2).getText()))); + currentNode.addChild(new DependencyEdgeCoverage(Integer.parseInt(ctx.getChild(2).getText()))); } else if ("requirement_coverage".equals(conditionName) || "requirementcoverage".equals(conditionName)) { - stopConditions.add(new RequirementCoverage(Integer.parseInt(ctx.getChild(2).getText()))); + currentNode.addChild(new RequirementCoverage(Integer.parseInt(ctx.getChild(2).getText()))); } else if ("length".equals(conditionName)) { - stopConditions.add(new Length(Integer.parseInt(ctx.getChild(2).getText()))); + currentNode.addChild(new Length(Integer.parseInt(ctx.getChild(2).getText()))); } else if ("predefined_path".equals(conditionName) || "predefinedpath".equals(conditionName)) { - stopConditions.add(new PredefinedPathStopCondition()); + currentNode.addChild(new PredefinedPathStopCondition()); } } - @Override - public void exitLogicalExpression(GeneratorParser.LogicalExpressionContext ctx) { - if (!ctx.OR().isEmpty()) { - AlternativeCondition alternativeCondition = new AlternativeCondition(); - stopConditions.forEach(alternativeCondition::addStopCondition); - stopCondition = alternativeCondition; - } + public void enterGenerator(GeneratorParser.GeneratorContext context) { + root = new Node(null); + currentNode = root; } @Override public void exitGenerator(GeneratorParser.GeneratorContext context) { - if (stopConditions.size() == 1) { - stopCondition = stopConditions.get(0); + if ( root.getData() != null ) { + stopCondition = (StopCondition)root.getData(); + } else { + if (root.getChildren().size() == 0 ) { + stopCondition = null; + } else { + Node node = (Node) root.getChildren().get(0); + stopCondition = (StopCondition) node.getData(); + } } + + + String generatorName = context.getChild(0).getText().toLowerCase(); if ("random".equals(generatorName) || "randompath".equals(generatorName)) { pathGenerators.add(new RandomPath(stopCondition)); @@ -118,7 +180,6 @@ public void exitGenerator(GeneratorParser.GeneratorContext context) { pathGenerators.add(pathGenerator); logger.debug("Generator: " + pathGenerator.getClass().getName() + " is loaded"); } - stopConditions.clear(); } public PathGenerator getGenerator() { diff --git a/graphwalker-dsl/src/test/java/org/graphwalker/dsl/GeneratorFactoryTest.java b/graphwalker-dsl/src/test/java/org/graphwalker/dsl/GeneratorFactoryTest.java index bb63fb971..9bef3e1e2 100644 --- a/graphwalker-dsl/src/test/java/org/graphwalker/dsl/GeneratorFactoryTest.java +++ b/graphwalker-dsl/src/test/java/org/graphwalker/dsl/GeneratorFactoryTest.java @@ -297,6 +297,40 @@ public void multipleAlternativeStopConditions() { assertThat(((AlternativeCondition) generator.getStopCondition()).getStopConditions().size(), is(3)); } + @Test + public void issue_341_a() { + PathGenerator generator = GeneratorFactory.parse("Random((vertex_coverage(100) and reached_vertex(myVertex)) or time_duration(90))"); + assertThat(generator, instanceOf(RandomPath.class)); + assertThat(generator.getStopCondition(), instanceOf(AlternativeCondition.class)); + + AlternativeCondition c1 = (AlternativeCondition) generator.getStopCondition(); + assertThat(c1.getStopConditions().size(), is(2)); + assertThat(c1.getStopConditions().get(0), instanceOf(CombinedCondition.class)); + assertThat(c1.getStopConditions().get(1), instanceOf(TimeDuration.class)); + + CombinedCondition c2 = (CombinedCondition)c1.getStopConditions().get(0); + assertThat(c2.getStopConditions().size(), is(2)); + assertThat(c2.getStopConditions().get(0), instanceOf(VertexCoverage.class)); + assertThat(c2.getStopConditions().get(1), instanceOf(ReachedVertex.class)); + } + + @Test + public void issue_341_b() { + PathGenerator generator = GeneratorFactory.parse("Random(time_duration(90) or (vertex_coverage(100) and reached_vertex(myVertex)))"); + assertThat(generator, instanceOf(RandomPath.class)); + assertThat(generator.getStopCondition(), instanceOf(AlternativeCondition.class)); + + AlternativeCondition c1 = (AlternativeCondition) generator.getStopCondition(); + assertThat(c1.getStopConditions().size(), is(2)); + assertThat(c1.getStopConditions().get(0), instanceOf(TimeDuration.class)); + assertThat(c1.getStopConditions().get(1), instanceOf(CombinedCondition.class)); + + CombinedCondition c2 = (CombinedCondition)c1.getStopConditions().get(1); + assertThat(c2.getStopConditions().size(), is(2)); + assertThat(c2.getStopConditions().get(0), instanceOf(VertexCoverage.class)); + assertThat(c2.getStopConditions().get(1), instanceOf(ReachedVertex.class)); + } + @Test public void predefinedPath_predefinedPathStopCondition() { PathGenerator generator = GeneratorFactory.parse("predefined_path(predefined_path)"); diff --git a/graphwalker-dsl/src/test/java/org/graphwalker/dsl/GrammarTest.java b/graphwalker-dsl/src/test/java/org/graphwalker/dsl/GrammarTest.java index 7af806f34..87945c921 100644 --- a/graphwalker-dsl/src/test/java/org/graphwalker/dsl/GrammarTest.java +++ b/graphwalker-dsl/src/test/java/org/graphwalker/dsl/GrammarTest.java @@ -62,7 +62,8 @@ public class GrammarTest { "random(reached_vertex(e_SomeEdge) and edge_coverage(100))", "random((reached_vertex(e_SomeEdge) and reached_edge(e_SomeEdge)) || time(5000))", "random(edge_coverage(100) and never) a_star(reached_vertex(v_SomeName) || edge_coverage(90))", - "random(reached_vertex(e_SomeEdge) and edge_coverage(100)) random((reached_vertex(e_SomeEdge) and reached_edge(e_SomeEdge)) || time(5000))" + "random(reached_vertex(e_SomeEdge) and edge_coverage(100)) random((reached_vertex(e_SomeEdge) and reached_edge(e_SomeEdge)) || time(5000))", + "Random((vertex_coverage(100) and reached_vertex(myVertex)) or time_duration(90))" ); @Test