package nl.ipo.cds.validation.execute; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertFalse; import java.math.BigInteger; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import nl.ipo.cds.validation.DefaultValidatorContext; import nl.ipo.cds.validation.Expression; import nl.ipo.cds.validation.Validation; import nl.ipo.cds.validation.ValidationMessage; import nl.ipo.cds.validation.ValidationReporter; import nl.ipo.cds.validation.Validator; import nl.ipo.cds.validation.gml.codelists.CodeList; import nl.ipo.cds.validation.gml.codelists.CodeListFactory; import nl.ipo.cds.validation.gml.codelists.StaticCodeListFactory; import org.junit.Before; import org.junit.Test; public class TestCompiler extends Validation<TestCompiler.MessageKeys, TestCompiler.Context> { private final int N = 10000; private Compiler<Context> compiler; @Before public void createCompiler () { compiler = new Compiler<Context> (Context.class) .addBean ("bean", Bean.class); } @Test public void testCompile () throws Exception { System.out.println (compiler.compile (constant ("Hello, world!"))); System.out.println (compiler.compile (gt (constant (1.0), constant (2.0)))); System.out.println (compiler.compile (stringAttr ("b"))); } @Test public void testMergeConstants () throws Exception { final Executor<Context> executor = compiler.compile (eq (constant (1), constant (1))); // The plan should only have two steps: the constant expressions are merged. assertEquals (2, executor.plan.getExecutionSteps ().size ()); } @Test public void testMergeAttributes () throws Exception { final Executor<Context> executor = compiler.compile (eq (stringAttr ("b"), stringAttr ("b"))); // The plan should only have three steps: the attribute references are merged. // 1) Get unboxed attribute value. // 2) Box attribute value (in Optional). // 3) Equals. System.out.println (executor); assertEquals (2, executor.plan.getExecutionSteps ().size ()); } @Test public void testExecute () throws Exception { assertEquals ("Hello, world!", execute (constant ("Hello, world!"))); assertEquals (true, execute (gt (constant (2.0), constant (1.0)))); assertEquals (true, execute (validate (gt (constant (2.0), constant (1.0))))); assertEquals (false, execute (validate (gt (constant (1.0), constant (2.0))))); } @Test public void testAccessAttribute () throws Exception { assertEquals ("Hello, World!", execute (stringAttr ("b"))); assertEquals (42, execute (intAttr ("a"))); assertEquals (1.0, (double)((Float)execute (floatAttr ("c"))), 0.01); assertEquals(BigInteger.valueOf(123),execute(bigIntegerAttr("f"))); } @Test public void testAccessContextAttribute () throws Exception { assertEquals ("Hello, World!", execute (stringAttr ("contextProperty"))); } @Test public void testIf () throws Exception { assertEquals (true, execute (ifExp (constant (true), constant (true), constant (false)))); assertEquals (false, execute (ifExp (constant (false), constant (true), constant (false)))); assertEquals (true, execute (ifExp (booleanAttr ("d"), booleanAttr ("d"), constant (false)))); } @Test public void testAnd () throws Exception { assertEquals (true, execute (and (constant (true), constant (true), constant (true)))); assertEquals (false, execute (and (constant (true), constant (false), constant (true)))); assertEquals (false, execute (and (constant (false), constant (false), constant (false)))); } @Test public void testAndShortCircuit () throws Exception { assertEquals (true, execute (and (constant (true), constant (true), constant (true)).shortCircuit ())); assertEquals (false, execute (and (constant (true), constant (false), constant (true)).shortCircuit ())); assertEquals (false, execute (and (constant (false), constant (false), constant (false)).shortCircuit ())); // Test short-circuited and operators nested in another operator. These should // work even though the and is internally replaced by a sequence of if-else operations: assertEquals (true, execute ( validate ( and ( validate ( constant (true) ), validate ( constant (true) ), validate ( constant (true) ) ).shortCircuit () ) )); } @Test public void testOr () throws Exception { assertEquals (true, execute (or (constant (true), constant (false), constant (true)))); assertEquals (true, execute (or (constant (true), constant (true), constant (true)))); assertEquals (false, execute (or (constant (false), constant (false), constant (false)))); } @Test public void testForEach () throws Exception { assertEquals (true, execute (forEach ("i", attr ("e", Integer[].class), validate (gt (intAttr ("i"), constant (0)))))); } @Test public void testSplit () throws Exception { assertEquals (true, execute (validate (eq (constant (2), constant (2))))); assertEquals (true, execute (split (constant ("a|b"), constant ("\\|"), validate (eq (intAttr ("length"), constant (2)))))); assertEquals (true, execute (split (constant ("a|b"), constant ("\\|"), validate (eq (stringAttr ("0"), constant ("a")))))); assertEquals (true, execute (split (constant ("a|b"), constant ("\\|"), validate (eq (stringAttr ("1"), constant ("b")))))); assertEquals (true, execute (split (constant ("a|b"), constant ("\\|"), validate (eq (join (attr ("values", String[].class), constant ("|")), constant ("a|b")))))); } @Test public void testFoldCommonUnarySubexpressions () throws Exception { final Executor<Context> executor = compiler.compile (and (stringAttr ("b").isNull (), stringAttr ("b").isNull ())); // The plan should have 4 expressions. The second isNull expression should be folded with the first. System.out.println (executor); assertEquals (3, executor.plan.getExecutionSteps ().size ()); } @Test public void testFoldCommonBinarySubexpressions () throws Exception { final Set<String> values = new HashSet<String> (); final Executor<Context> executor = compiler.compile (and (in (stringAttr ("b"), constant (values)), in (stringAttr ("b"), constant (values)))); // The plan should have 5 expressions. The second In expression should be folded with the first. System.out.println (executor); assertEquals (4, executor.plan.getExecutionSteps ().size ()); } @Test public void testFoldCompareOperators () throws Exception { final Executor<Context> executor = compiler.compile (and (gt (intAttr ("a"), intAttr ("a")), gt (intAttr ("a"), intAttr ("a")))); System.out.println (executor); assertEquals (3, executor.plan.getExecutionSteps ().size ()); } @Test public void testFoldNot () throws Exception { final Executor<Context> executor = compiler.compile (and (not (booleanAttr ("d")), not (booleanAttr ("d")))); System.out.println (executor); assertEquals (3, executor.plan.getExecutionSteps ().size ()); } @Test public void testManyObjects () throws Exception { final Validator<MessageKeys, Context> validator = validate ( and ( gt (intAttr ("a"), constant (100)), not (eq (stringAttr ("b"), constant ("Hello, World!"))), not (not (booleanAttr ("d"))), forEach ("i", attr ("e", Integer[].class), validate ( gt (intAttr ("i"), constant (1000)) )), split (stringAttr ("b"), constant (","), validate ( not (eq (stringAttr ("0"), constant ("Hello"))) )), or ( not (stringAttr ("b").isNull ()), not (intAttr ("a").isNull ()), and ( not (floatAttr ("c").isNull ()), isUrl (stringAttr ("b")), isUUID (stringAttr ("b")) ) ) ) ); final Executor<Context> executor = compiler.compile (validator); { final double startTime = System.currentTimeMillis (); for (int i = 0; i < N; ++ i) { final Bean bean = createBean (i); final Context context = new Context (new StaticCodeListFactory (Collections.<String, CodeList>emptyMap ()), null); executor.execute (context, bean); } final double endTime = System.currentTimeMillis (); System.out.println (String.format ("%d iterations of interpreted validator in %f seconds", N, (endTime - startTime) / 1000.0)); } } private Bean createBean (int i) { final Bean bean = new Bean (); bean.setA (i); bean.setB ("Hello, World! " + i); bean.setC (i * 2.0f); bean.setD (i % 2 == 0); bean.setE (new Integer[] { 1, 2, 3, 4, i }); bean.setF(BigInteger.valueOf(i)); return bean; } @Test public void testExecuteAsInterface () throws Exception { final Bean bean = new Bean (); bean.setD (true); final Context context = new Context (new StaticCodeListFactory (Collections.<String, CodeList>emptyMap ()), null); final ExecuteInterface executor = compiler.compile (booleanAttr ("d"), ExecuteInterface.class); assertTrue (executor.execute (context, bean)); } @Test (expected = IllegalArgumentException.class) public void testExecuteAsInterfaceInvalidType () throws Exception { final Bean bean = new Bean (); bean.setD (false); final Context context = new Context (new StaticCodeListFactory (Collections.<String, CodeList>emptyMap ()), null); final ExecuteInterface executor = compiler.compile (intAttr ("a"), ExecuteInterface.class); executor.execute (context, bean); } @Test public void testValidateCodeSpace () throws Exception { final Bean bean = new Bean (); final Map<String, CodeList> codeLists = new HashMap<> (); final Set<String> codes = new HashSet<> (); codes.add ("a"); codes.add ("b"); codes.add ("c"); final CodeList codeList = new StaticCodeListFactory.StaticCodeList ("http://www.idgis.nl/codes", codes); codeLists.put ("http://www.idgis.nl/codes", codeList); final Context context = new Context (new StaticCodeListFactory (codeLists), null); final ExecuteInterface executor = compiler.compile (codeSpace (constant ("http://www.idgis.nl/codes")), ExecuteInterface.class); assertTrue (executor.execute (context, bean)); } @Test public void testValidateCodeSpaceInvalid () throws Exception { final Bean bean = new Bean (); final Map<String, CodeList> codeLists = new HashMap<> (); final Set<String> codes = new HashSet<> (); codes.add ("a"); codes.add ("b"); codes.add ("c"); final CodeList codeList = new StaticCodeListFactory.StaticCodeList ("http://www.idgis.nl/codes", codes); codeLists.put ("http://www.idgis.nl/codes", codeList); final Context context = new Context (new StaticCodeListFactory (codeLists), null); final ExecuteInterface executor = compiler.compile (codeSpace (constant ("http://www.idgis.nl/codes2")), ExecuteInterface.class); assertFalse (executor.execute (context, bean)); } public Object execute (final Expression<MessageKeys, Context, ?> expression) throws Exception { final Bean bean = new Bean (); bean.setA (42); bean.setB ("Hello, World!"); bean.setC (1.0f); bean.setD (true); bean.setE (new Integer[] { 1, 2, 3, 4 }); bean.setF(BigInteger.valueOf(123)); final Context context = new Context (new StaticCodeListFactory (Collections.<String, CodeList>emptyMap ()), null); context.setContextProperty ("Hello, World!"); final Executor<Context> executor = compiler.compile (expression); return executor.execute (context, bean); } public static enum MessageKeys implements ValidationMessage<MessageKeys, Context> { A, B, C, D, E; @Override public boolean isBlocking () { return true; } @Override public List<Expression<MessageKeys, Context, ?>> getMessageParameters () { return Collections.emptyList (); } } public static interface ExecuteInterface { Boolean execute (Context context, Bean bean); } public static class Context extends DefaultValidatorContext<MessageKeys, Context> { public Context (final CodeListFactory codeListFactory, final ValidationReporter<MessageKeys, Context> reporter) { super (codeListFactory, reporter); } private String contextProperty; public String getContextProperty () { return contextProperty; } public void setContextProperty (final String contextProperty) { this.contextProperty = contextProperty; } } public static class Bean { private int a; private String b; private float c; private boolean d; private Integer[] e; public BigInteger getF() { return f; } public void setF(BigInteger f) { this.f = f; } private BigInteger f; public int getA() { return a; } public void setA(int a) { this.a = a; } public String getB() { return b; } public void setB(String b) { this.b = b; } public float getC() { return c; } public void setC(float c) { this.c = c; } public boolean getD() { return d; } public void setD(boolean d) { this.d = d; } public Integer[] getE() { return e; } public void setE(Integer[] e) { this.e = e; } } }