From 2c8d00df810dfd7f721745e0f96205d1386461c5 Mon Sep 17 00:00:00 2001 From: James Harris Date: Thu, 19 Jun 2014 15:58:44 +1000 Subject: [PATCH] Completed implementation/testing of Evaluator. --- .../evaluator/AbstractPatternMatcher.java | 120 +++++ .../CaseInsensitivePatternMatcher.java | 14 +- .../CaseSensitivePatternMatcher.java | 13 +- .../dialekt/evaluator/EvaluationResult.java | 10 +- .../icecave/dialekt/evaluator/Evaluator.java | 37 +- .../evaluator/EvaluationResultTest.java | 55 ++ .../dialekt/evaluator/EvaluatorTest.java | 488 ++++++++++++++++++ .../evaluator/ExpressionResultTest.java | 59 +++ 8 files changed, 773 insertions(+), 23 deletions(-) create mode 100644 src/main/java/co/icecave/dialekt/evaluator/AbstractPatternMatcher.java create mode 100644 src/test/java/co/icecave/dialekt/evaluator/EvaluationResultTest.java create mode 100644 src/test/java/co/icecave/dialekt/evaluator/EvaluatorTest.java create mode 100644 src/test/java/co/icecave/dialekt/evaluator/ExpressionResultTest.java diff --git a/src/main/java/co/icecave/dialekt/evaluator/AbstractPatternMatcher.java b/src/main/java/co/icecave/dialekt/evaluator/AbstractPatternMatcher.java new file mode 100644 index 0000000..f72ea80 --- /dev/null +++ b/src/main/java/co/icecave/dialekt/evaluator/AbstractPatternMatcher.java @@ -0,0 +1,120 @@ +package co.icecave.dialekt.evaluator; + +import co.icecave.dialekt.ast.EmptyExpression; +import co.icecave.dialekt.ast.ExpressionInterface; +import co.icecave.dialekt.ast.LogicalAnd; +import co.icecave.dialekt.ast.LogicalNot; +import co.icecave.dialekt.ast.LogicalOr; +import co.icecave.dialekt.ast.Pattern; +import co.icecave.dialekt.ast.PatternChildInterface; +import co.icecave.dialekt.ast.PatternLiteral; +import co.icecave.dialekt.ast.PatternWildcard; +import co.icecave.dialekt.ast.Tag; +import co.icecave.dialekt.ast.VisitorInterface; + +abstract class AbstractPatternMatcher implements TagMatcherInterface, VisitorInterface +{ + public AbstractPatternMatcher(Pattern pattern) + { + this.pattern = pattern; + this.regex = this.compilePattern( + pattern.accept(this) + ); + } + + public boolean match(String tag) + { + return this.regex.matcher(tag).matches(); + } + + protected abstract java.util.regex.Pattern compilePattern(String pattern); + + /** + * Visit a LogicalAnd node. + * + * @param node The node to visit. + */ + public String visit(LogicalAnd node) + { + throw new UnsupportedOperationException(); + } + + /** + * Visit a LogicalOr node. + * + * @param node The node to visit. + */ + public String visit(LogicalOr node) + { + throw new UnsupportedOperationException(); + } + + /** + * Visit a LogicalNot node. + * + * @param node The node to visit. + */ + public String visit(LogicalNot node) + { + throw new UnsupportedOperationException(); + } + + /** + * Visit a Tag node. + * + * @param node The node to visit. + */ + public String visit(Tag node) + { + throw new UnsupportedOperationException(); + } + + /** + * Visit a pattern node. + * + * @param node The node to visit. + */ + public String visit(Pattern node) + { + String pattern = ""; + + for (PatternChildInterface child : node.children()) { + pattern += child.accept(this); + } + + return '^' + pattern + '$'; + } + + /** + * Visit a PatternLiteral node. + * + * @param node The node to visit. + */ + public String visit(PatternLiteral node) + { + return java.util.regex.Pattern.quote(node.string()); + } + + /** + * Visit a PatternWildcard node. + * + * @param node The node to visit. + */ + public String visit(PatternWildcard node) + { + return ".*"; + } + + /** + * Visit a EmptyExpression node. + * + * @param node The node to visit. + */ + public String visit(EmptyExpression node) + { + throw new UnsupportedOperationException(); + } + + private Pattern pattern; + private java.util.regex.Pattern regex; +} diff --git a/src/main/java/co/icecave/dialekt/evaluator/CaseInsensitivePatternMatcher.java b/src/main/java/co/icecave/dialekt/evaluator/CaseInsensitivePatternMatcher.java index 618af9c..cd93578 100644 --- a/src/main/java/co/icecave/dialekt/evaluator/CaseInsensitivePatternMatcher.java +++ b/src/main/java/co/icecave/dialekt/evaluator/CaseInsensitivePatternMatcher.java @@ -2,17 +2,19 @@ import co.icecave.dialekt.ast.Pattern; -class CaseInsensitivePatternMatcher implements TagMatcherInterface +class CaseInsensitivePatternMatcher extends AbstractPatternMatcher { public CaseInsensitivePatternMatcher(Pattern pattern) { - this.pattern = pattern; + super(pattern); } - public boolean match(String tag) + @Override + protected java.util.regex.Pattern compilePattern(String pattern) { - return false; + return java.util.regex.Pattern.compile( + pattern, + java.util.regex.Pattern.CASE_INSENSITIVE + ); } - - private Pattern pattern; } diff --git a/src/main/java/co/icecave/dialekt/evaluator/CaseSensitivePatternMatcher.java b/src/main/java/co/icecave/dialekt/evaluator/CaseSensitivePatternMatcher.java index 2ff90b7..3e23c5b 100644 --- a/src/main/java/co/icecave/dialekt/evaluator/CaseSensitivePatternMatcher.java +++ b/src/main/java/co/icecave/dialekt/evaluator/CaseSensitivePatternMatcher.java @@ -2,17 +2,18 @@ import co.icecave.dialekt.ast.Pattern; -class CaseSensitivePatternMatcher implements TagMatcherInterface +class CaseSensitivePatternMatcher extends AbstractPatternMatcher { public CaseSensitivePatternMatcher(Pattern pattern) { - this.pattern = pattern; + super(pattern); } - public boolean match(String tag) + @Override + protected java.util.regex.Pattern compilePattern(String pattern) { - return false; + return java.util.regex.Pattern.compile( + pattern + ); } - - private Pattern pattern; } diff --git a/src/main/java/co/icecave/dialekt/evaluator/EvaluationResult.java b/src/main/java/co/icecave/dialekt/evaluator/EvaluationResult.java index 4987d53..f3479b5 100644 --- a/src/main/java/co/icecave/dialekt/evaluator/EvaluationResult.java +++ b/src/main/java/co/icecave/dialekt/evaluator/EvaluationResult.java @@ -40,9 +40,15 @@ public boolean isMatch() * * @return The result for the given expression. */ - public ExpressionResult resultOf(ExpressionInterface expression) + public ExpressionResult resultOf(ExpressionInterface expression) throws IndexOutOfBoundsException { - return this.expressionResults.get(expression); + ExpressionResult result = this.expressionResults.get(expression); + + if (null == result) { + throw new IndexOutOfBoundsException(); + } + + return result; } private boolean isMatch; diff --git a/src/main/java/co/icecave/dialekt/evaluator/Evaluator.java b/src/main/java/co/icecave/dialekt/evaluator/Evaluator.java index 3654277..02111d2 100644 --- a/src/main/java/co/icecave/dialekt/evaluator/Evaluator.java +++ b/src/main/java/co/icecave/dialekt/evaluator/Evaluator.java @@ -155,11 +155,16 @@ public ExpressionResult visit(LogicalNot node) */ public ExpressionResult visit(Tag node) { + if (this.caseSensitive) { + return this.matchTags( + node, + new CaseSensitiveTagMatcher(node) + ); + } + return this.matchTags( node, - this.caseSensitive - ? new CaseSensitiveTagMatcher(node) - : new CaseInsensitiveTagMatcher(node) + new CaseInsensitiveTagMatcher(node) ); } @@ -170,11 +175,16 @@ public ExpressionResult visit(Tag node) */ public ExpressionResult visit(Pattern node) { + if (this.caseSensitive) { + return this.matchTags( + node, + new CaseSensitivePatternMatcher(node) + ); + } + return this.matchTags( node, - this.caseSensitive - ? new CaseSensitivePatternMatcher(node) - : new CaseInsensitivePatternMatcher(node) + new CaseInsensitivePatternMatcher(node) ); } @@ -205,11 +215,20 @@ public ExpressionResult visit(PatternWildcard node) */ public ExpressionResult visit(EmptyExpression node) { + if (this.emptyIsWildcard) { + return this.createExpressionResult( + node, + true, + this.tags, + Collections.EMPTY_SET + ); + } + return this.createExpressionResult( node, - this.emptyIsWildcard, - this.emptyIsWildcard ? this.tags : Collections.EMPTY_SET, - this.emptyIsWildcard ? Collections.EMPTY_SET : this.tags + false, + Collections.EMPTY_SET, + this.tags ); } diff --git a/src/test/java/co/icecave/dialekt/evaluator/EvaluationResultTest.java b/src/test/java/co/icecave/dialekt/evaluator/EvaluationResultTest.java new file mode 100644 index 0000000..c2de211 --- /dev/null +++ b/src/test/java/co/icecave/dialekt/evaluator/EvaluationResultTest.java @@ -0,0 +1,55 @@ +package co.icecave.dialekt.evaluator; + +import co.icecave.dialekt.ast.ExpressionInterface; +import java.util.Arrays; +import java.util.Collections; +import org.mockito.Mockito; +import org.testng.annotations.Test; +import org.testng.Assert; + +public class EvaluationResultTest +{ + public EvaluationResultTest() + { + this.expression = Mockito.mock(ExpressionInterface.class); + + this.expressionResult = new ExpressionResult( + this.expression, + true, + Collections.EMPTY_SET, + Collections.EMPTY_SET + ); + + this.result = new EvaluationResult( + true, + Arrays.asList(this.expressionResult) + ); + } + + @Test + public void testIsMatch() + { + Assert.assertTrue(this.result.isMatch()); + } + + @Test + public void testResultOf() + { + Assert.assertSame( + this.result.resultOf(this.expression), + this.expressionResult + ); + } + + @Test(expectedExceptions = IndexOutOfBoundsException.class) + public void testResultOfWithUnknownExpression() + { + this.result.resultOf( + Mockito.mock(ExpressionInterface.class) + ); + } + + private ExpressionInterface expression; + private ExpressionResult expressionResult; + private EvaluationResult result; +} diff --git a/src/test/java/co/icecave/dialekt/evaluator/EvaluatorTest.java b/src/test/java/co/icecave/dialekt/evaluator/EvaluatorTest.java new file mode 100644 index 0000000..9d25776 --- /dev/null +++ b/src/test/java/co/icecave/dialekt/evaluator/EvaluatorTest.java @@ -0,0 +1,488 @@ +package co.icecave.dialekt.evaluator; + +import co.icecave.dialekt.ast.EmptyExpression; +import co.icecave.dialekt.ast.ExpressionInterface; +import co.icecave.dialekt.ast.LogicalAnd; +import co.icecave.dialekt.ast.LogicalNot; +import co.icecave.dialekt.ast.LogicalOr; +import co.icecave.dialekt.ast.Pattern; +import co.icecave.dialekt.ast.PatternLiteral; +import co.icecave.dialekt.ast.PatternWildcard; +import co.icecave.dialekt.ast.Tag; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import org.mockito.Mockito; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import org.testng.Assert; + +public class EvaluatorTest +{ + public EvaluatorTest() + { + this.evaluator = new Evaluator(); + } + + /** + * @dataProvider evaluateTestVectors + */ + @Test(dataProvider = "evaluateTestVectors") + public void testEvaluate(ExpressionInterface expression, List tags, boolean expected) + { + EvaluationResult result = this.evaluator.evaluate( + expression, + new HashSet(tags) + ); + + Assert.assertEquals( + result.isMatch(), + expected + ); + } + + @Test + public void testEvaluateTagCaseSensitive() + { + this.evaluator = new Evaluator(true); + + ExpressionInterface expression = new Tag("foo"); + + Assert.assertTrue( + this.evaluator.evaluate( + expression, + new HashSet(Arrays.asList("foo")) + ).isMatch() + ); + + Assert.assertFalse( + this.evaluator.evaluate( + expression, + new HashSet(Arrays.asList("FOO")) + ).isMatch() + ); + } + + @Test + public void testEvaluatePatternCaseSensitive() + { + this.evaluator = new Evaluator(true); + + ExpressionInterface expression = new Pattern( + new PatternLiteral("foo"), + new PatternWildcard() + ); + + Assert.assertTrue( + this.evaluator.evaluate( + expression, + new HashSet(Arrays.asList("foobar")) + ).isMatch() + ); + + Assert.assertFalse( + this.evaluator.evaluate( + expression, + new HashSet(Arrays.asList("FOOBAR")) + ).isMatch() + ); + } + + @Test + public void testEvaluateEmptyExpressionEmptyAsWildcard() + { + this.evaluator = new Evaluator(false, true); + + Assert.assertTrue( + this.evaluator.evaluate( + new EmptyExpression(), + new HashSet(Arrays.asList("foo")) + ).isMatch() + ); + } + + @Test + public void testEvaluateLogicalAnd() + { + ExpressionInterface innerExpression1 = new Tag("foo"); + ExpressionInterface innerExpression2 = new Tag("bar"); + ExpressionInterface innerExpression3 = new Tag("bar"); + ExpressionInterface expression = new LogicalAnd( + innerExpression1, + innerExpression2, + innerExpression3 + ); + + EvaluationResult result = this.evaluator.evaluate( + expression, + new HashSet(Arrays.asList("foo", "bar", "spam")) + ); + + Assert.assertTrue(result.isMatch()); + + ExpressionResult expressionResult; + + expressionResult = result.resultOf(expression); + Assert.assertTrue(expressionResult.isMatch()); + Assert.assertEquals( + expressionResult.matchedTags(), + new HashSet(Arrays.asList("foo", "bar")) + ); + Assert.assertEquals( + expressionResult.unmatchedTags(), + new HashSet(Arrays.asList("spam")) + ); + + expressionResult = result.resultOf(innerExpression1); + Assert.assertTrue(expressionResult.isMatch()); + Assert.assertEquals( + expressionResult.matchedTags(), + new HashSet(Arrays.asList("foo")) + ); + Assert.assertEquals( + expressionResult.unmatchedTags(), + new HashSet(Arrays.asList("bar", "spam")) + ); + + expressionResult = result.resultOf(innerExpression2); + Assert.assertTrue(expressionResult.isMatch()); + Assert.assertEquals( + expressionResult.matchedTags(), + new HashSet(Arrays.asList("bar")) + ); + Assert.assertEquals( + expressionResult.unmatchedTags(), + new HashSet(Arrays.asList("foo", "spam")) + ); + + expressionResult = result.resultOf(innerExpression3); + Assert.assertTrue(expressionResult.isMatch()); + Assert.assertEquals( + expressionResult.matchedTags(), + new HashSet(Arrays.asList("bar")) + ); + Assert.assertEquals( + expressionResult.unmatchedTags(), + new HashSet(Arrays.asList("foo", "spam")) + ); + } + + @Test + public void testEvaluateLogicalOr() + { + ExpressionInterface innerExpression1 = new Tag("foo"); + ExpressionInterface innerExpression2 = new Tag("bar"); + ExpressionInterface innerExpression3 = new Tag("doom"); + ExpressionInterface expression = new LogicalOr( + innerExpression1, + innerExpression2, + innerExpression3 + ); + + EvaluationResult result = this.evaluator.evaluate( + expression, + new HashSet(Arrays.asList("foo", "bar", "spam")) + ); + + Assert.assertTrue(result.isMatch()); + + ExpressionResult expressionResult; + + expressionResult = result.resultOf(expression); + Assert.assertTrue(expressionResult.isMatch()); + Assert.assertEquals( + expressionResult.matchedTags(), + new HashSet(Arrays.asList("foo", "bar")) + ); + Assert.assertEquals( + expressionResult.unmatchedTags(), + new HashSet(Arrays.asList("spam")) + ); + + expressionResult = result.resultOf(innerExpression1); + Assert.assertTrue(expressionResult.isMatch()); + Assert.assertEquals( + expressionResult.matchedTags(), + new HashSet(Arrays.asList("foo")) + ); + Assert.assertEquals( + expressionResult.unmatchedTags(), + new HashSet(Arrays.asList("bar", "spam")) + ); + + expressionResult = result.resultOf(innerExpression2); + Assert.assertTrue(expressionResult.isMatch()); + Assert.assertEquals( + expressionResult.matchedTags(), + new HashSet(Arrays.asList("bar")) + ); + Assert.assertEquals( + expressionResult.unmatchedTags(), + new HashSet(Arrays.asList("foo", "spam")) + ); + + expressionResult = result.resultOf(innerExpression3); + Assert.assertFalse(expressionResult.isMatch()); + Assert.assertEquals( + expressionResult.matchedTags(), + Collections.EMPTY_SET + ); + Assert.assertEquals( + expressionResult.unmatchedTags(), + new HashSet(Arrays.asList("foo", "bar", "spam")) + ); + } + + @Test + public void testEvaluateTag() + { + ExpressionInterface expression = new Tag("foo"); + + EvaluationResult result = this.evaluator.evaluate( + expression, + new HashSet(Arrays.asList("foo", "bar")) + ); + + Assert.assertTrue(result.isMatch()); + + ExpressionResult expressionResult = result.resultOf(expression); + Assert.assertTrue(expressionResult.isMatch()); + Assert.assertEquals( + expressionResult.matchedTags(), + new HashSet(Arrays.asList("foo")) + ); + Assert.assertEquals( + expressionResult.unmatchedTags(), + new HashSet(Arrays.asList("bar")) + ); + } + + @Test + public void testEvaluatePattern() + { + ExpressionInterface expression = new Pattern( + new PatternLiteral("foo"), + new PatternWildcard() + ); + + EvaluationResult result = this.evaluator.evaluate( + expression, + new HashSet(Arrays.asList("foo1", "foo2", "bar")) + ); + + Assert.assertTrue(result.isMatch()); + + ExpressionResult expressionResult = result.resultOf(expression); + Assert.assertTrue(expressionResult.isMatch()); + Assert.assertEquals( + expressionResult.matchedTags(), + new HashSet(Arrays.asList("foo1", "foo2")) + ); + Assert.assertEquals( + expressionResult.unmatchedTags(), + new HashSet(Arrays.asList("bar")) + ); + } + + @Test + public void testEvaluateEmptyExpression() + { + ExpressionInterface expression = new EmptyExpression(); + + EvaluationResult result = this.evaluator.evaluate( + expression, + new HashSet(Arrays.asList("foo", "bar")) + ); + + Assert.assertFalse(result.isMatch()); + + ExpressionResult expressionResult = result.resultOf(expression); + Assert.assertFalse(expressionResult.isMatch()); + Assert.assertEquals( + expressionResult.matchedTags(), + Collections.EMPTY_SET + ); + Assert.assertEquals( + expressionResult.unmatchedTags(), + new HashSet(Arrays.asList("foo", "bar")) + ); + } + + @DataProvider(name = "evaluateTestVectors") + public Object[][] evaluateTestVectors() + { + return new Object[][] { + { + new EmptyExpression(), + Arrays.asList("foo"), + false, + }, + { + new Tag("foo"), + Arrays.asList("foo"), + true, + }, + { + new Tag("foo"), + Arrays.asList("bar"), + false, + }, + { + new Tag("foo"), + Arrays.asList("foo", "bar"), + true, + }, + { + new Pattern( + new PatternLiteral("foo"), + new PatternWildcard() + ), + Arrays.asList("foobar"), + true, + }, + { + new LogicalAnd( + new Tag("foo"), + new Tag("bar") + ), + Arrays.asList("foo"), + false, + }, + { + new LogicalAnd( + new Tag("foo"), + new Tag("bar") + ), + Arrays.asList("bar"), + false, + }, + { + new LogicalAnd( + new Tag("foo"), + new Tag("bar") + ), + Arrays.asList("foo", "bar"), + true, + }, + { + new LogicalAnd( + new Tag("foo"), + new Tag("bar") + ), + Arrays.asList("foo", "bar", "spam"), + true, + }, + { + new LogicalAnd( + new Tag("foo"), + new Tag("bar") + ), + Arrays.asList("foo", "spam"), + false, + }, + { + new LogicalOr( + new Tag("foo"), + new Tag("bar") + ), + Arrays.asList("foo"), + true, + }, + { + new LogicalOr( + new Tag("foo"), + new Tag("bar") + ), + Arrays.asList("bar"), + true, + }, + { + new LogicalOr( + new Tag("foo"), + new Tag("bar") + ), + Arrays.asList("foo", "spam"), + true, + }, + { + new LogicalOr( + new Tag("foo"), + new Tag("bar") + ), + Arrays.asList("spam"), + false, + }, + { + new LogicalNot( + new Tag("foo") + ), + Arrays.asList("foo"), + false, + }, + { + new LogicalNot( + new Tag("foo") + ), + Arrays.asList("foo", "bar"), + false, + }, + { + new LogicalNot( + new Tag("foo") + ), + Arrays.asList("bar"), + true, + }, + { + new LogicalNot( + new Tag("foo") + ), + Arrays.asList("bar", "spam"), + true, + }, + { + new LogicalAnd( + new Tag("foo"), + new LogicalNot( + new Tag("bar") + ) + ), + Arrays.asList("foo"), + true, + }, + { + new LogicalAnd( + new Tag("foo"), + new LogicalNot( + new Tag("bar") + ) + ), + Arrays.asList("foo", "spam"), + true, + }, + { + new LogicalAnd( + new Tag("foo"), + new LogicalNot( + new Tag("bar") + ) + ), + Arrays.asList("foo", "bar", "spam"), + false, + }, + { + new LogicalAnd( + new Tag("foo"), + new LogicalNot( + new Tag("bar") + ) + ), + Arrays.asList("spam"), + false, + }, + }; + } + + private Evaluator evaluator; +} diff --git a/src/test/java/co/icecave/dialekt/evaluator/ExpressionResultTest.java b/src/test/java/co/icecave/dialekt/evaluator/ExpressionResultTest.java new file mode 100644 index 0000000..5dab4c6 --- /dev/null +++ b/src/test/java/co/icecave/dialekt/evaluator/ExpressionResultTest.java @@ -0,0 +1,59 @@ +package co.icecave.dialekt.evaluator; + +import co.icecave.dialekt.ast.ExpressionInterface; +import java.util.Arrays; +import java.util.HashSet; +import org.mockito.Mockito; +import org.testng.annotations.Test; +import org.testng.Assert; + +public class ExpressionResultTest +{ + public ExpressionResultTest() + { + this.expression = Mockito.mock(ExpressionInterface.class); + + this.result = new ExpressionResult( + this.expression, + true, + new HashSet(Arrays.asList("foo")), + new HashSet(Arrays.asList("bar")) + ); + } + + @Test + public void testExpression() + { + Assert.assertSame( + this.result.expression(), + this.expression + ); + } + + @Test + public void testIsMatch() + { + Assert.assertTrue(this.result.isMatch()); + } + + @Test + public void testMatchedTags() + { + Assert.assertEquals( + this.result.matchedTags(), + new HashSet(Arrays.asList("foo")) + ); + } + + @Test + public void testUnmatchedTags() + { + Assert.assertEquals( + this.result.unmatchedTags(), + new HashSet(Arrays.asList("bar")) + ); + } + + private ExpressionInterface expression; + private ExpressionResult result; +}