From a0e9ce63ee510723c60d2db8ce6569c2cca356ea Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Sat, 19 May 2018 20:32:13 +0000 Subject: [PATCH] knock out some fixmes; add support for custom function lookup and custom bindings passed into expression evaluation git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/exprs@1151 f203690c-595d-4dc9-a70b-905162fa7fd2 --- .../jackcess/Database.java | 11 +++- .../jackcess/expr/EvalConfig.java | 12 +++-- .../jackcess/expr/EvalContext.java | 9 +++- .../jackcess/expr/FunctionLookup.java | 26 ++++++++++ .../jackcess/impl/BaseEvalContext.java | 13 +++++ .../jackcess/impl/DBEvalContext.java | 27 +++++++--- .../jackcess/impl/expr/DefaultFunctions.java | 35 ++++++++----- .../jackcess/impl/expr/RandomContext.java | 6 +-- .../impl/expr/ExpressionatorTest.java | 51 ++++++++++++------- 9 files changed, 142 insertions(+), 48 deletions(-) create mode 100644 src/main/java/com/healthmarketscience/jackcess/expr/FunctionLookup.java diff --git a/src/main/java/com/healthmarketscience/jackcess/Database.java b/src/main/java/com/healthmarketscience/jackcess/Database.java index a146154..d853fe8 100644 --- a/src/main/java/com/healthmarketscience/jackcess/Database.java +++ b/src/main/java/com/healthmarketscience/jackcess/Database.java @@ -451,9 +451,18 @@ public interface Database extends Iterable, Closeable, Flushable */ public void setAllowAutoNumberInsert(Boolean allowAutoNumInsert); - // FIXME, docme + /** + * Gets the current expression evaluation policy. Expression evaluation is + * currently an experimental feature, and is therefore disabled by default. + */ public boolean isEvaluateExpressions(); + /** + * Sets the current expression evaluation policy. Expression evaluation is + * currently an experimental feature, and is therefore disabled by default. + * If {@code null}, resets to the default value. + * @usage _intermediate_method_ + */ public void setEvaluateExpressions(Boolean evaluateExpressions); /** diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/EvalConfig.java b/src/main/java/com/healthmarketscience/jackcess/expr/EvalConfig.java index e83fbbc..07ac492 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/EvalConfig.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/EvalConfig.java @@ -16,17 +16,23 @@ limitations under the License. package com.healthmarketscience.jackcess.expr; +import javax.script.Bindings; + /** * * @author James Ahlborn */ -public interface EvalConfig +public interface EvalConfig { public TemporalConfig getTemporalConfig(); public void setTemporalConfig(TemporalConfig temporal); - public void putCustomExpressionFunction(Function func); + public FunctionLookup getFunctionLookup(); + + public void setFunctionLookup(FunctionLookup lookup); + + public Bindings getBindings(); - public Function getCustomExpressionFunction(String name); + public void setBindings(Bindings bindings); } diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java b/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java index c168f1c..f1dbab3 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/EvalContext.java @@ -17,12 +17,13 @@ limitations under the License. package com.healthmarketscience.jackcess.expr; import java.text.SimpleDateFormat; +import javax.script.Bindings; /** * * @author James Ahlborn */ -public interface EvalContext +public interface EvalContext { public TemporalConfig getTemporalConfig(); @@ -35,4 +36,10 @@ public interface EvalContext public Value getThisColumnValue(); public Value getIdentifierValue(Identifier identifier); + + public Bindings getBindings(); + + public Object get(String key); + + public void put(String key, Object value); } diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/FunctionLookup.java b/src/main/java/com/healthmarketscience/jackcess/expr/FunctionLookup.java new file mode 100644 index 0000000..8314c41 --- /dev/null +++ b/src/main/java/com/healthmarketscience/jackcess/expr/FunctionLookup.java @@ -0,0 +1,26 @@ +/* +Copyright (c) 2018 James Ahlborn + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package com.healthmarketscience.jackcess.expr; + +/** + * + * @author James Ahlborn + */ +public interface FunctionLookup +{ + public Function getFunction(String name); +} diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java b/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java index 30de2a1..230afe2 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.Date; import java.util.EnumMap; import java.util.Map; +import javax.script.Bindings; import com.healthmarketscience.jackcess.DataType; import com.healthmarketscience.jackcess.JackcessException; @@ -97,6 +98,18 @@ public abstract class BaseEvalContext implements EvalContext throw new UnsupportedOperationException(); } + public Bindings getBindings() { + return _dbCtx.getBindings(); + } + + public Object get(String key) { + return _dbCtx.getBindings().get(key); + } + + public void put(String key, Object value) { + _dbCtx.getBindings().put(key, value); + } + public Object eval() throws IOException { try { return _expr.eval(this); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java b/src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java index 2aabd01..ab8a2d4 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java @@ -18,9 +18,12 @@ package com.healthmarketscience.jackcess.impl; import java.text.SimpleDateFormat; import java.util.Map; +import javax.script.Bindings; +import javax.script.SimpleBindings; import com.healthmarketscience.jackcess.expr.EvalConfig; import com.healthmarketscience.jackcess.expr.Function; +import com.healthmarketscience.jackcess.expr.FunctionLookup; import com.healthmarketscience.jackcess.expr.TemporalConfig; import com.healthmarketscience.jackcess.impl.expr.DefaultFunctions; import com.healthmarketscience.jackcess.impl.expr.Expressionator; @@ -35,11 +38,13 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig private static final int MAX_CACHE_SIZE = 10; private final DatabaseImpl _db; + private FunctionLookup _funcs = DefaultFunctions.LOOKUP; private Map _sdfs; private TemporalConfig _temporal; private final RandomContext _rndCtx = new RandomContext(); + private Bindings _bindings = new SimpleBindings(); - public DBEvalContext(DatabaseImpl db) + public DBEvalContext(DatabaseImpl db) { _db = db; } @@ -56,13 +61,20 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig _temporal = temporal; } - public void putCustomExpressionFunction(Function func) { - // FIXME writeme + public FunctionLookup getFunctionLookup() { + return _funcs; } - public Function getCustomExpressionFunction(String name) { - // FIXME writeme - return null; + public void setFunctionLookup(FunctionLookup lookup) { + _funcs = lookup; + } + + public Bindings getBindings() { + return _bindings; + } + + public void setBindings(Bindings bindings) { + _bindings = bindings; } public SimpleDateFormat createDateFormat(String formatStr) { @@ -82,7 +94,6 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig } public Function getExpressionFunction(String name) { - // FIXME, support custom function context? - return DefaultFunctions.getFunction(name); + return _funcs.getFunction(name); } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java index 35b8428..a10b557 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java @@ -24,6 +24,7 @@ import java.util.Map; import com.healthmarketscience.jackcess.expr.EvalContext; import com.healthmarketscience.jackcess.expr.EvalException; import com.healthmarketscience.jackcess.expr.Function; +import com.healthmarketscience.jackcess.expr.FunctionLookup; import com.healthmarketscience.jackcess.expr.Value; import com.healthmarketscience.jackcess.impl.DatabaseImpl; @@ -31,13 +32,13 @@ import com.healthmarketscience.jackcess.impl.DatabaseImpl; * * @author James Ahlborn */ -public class DefaultFunctions +public class DefaultFunctions { - private static final Map FUNCS = + private static final Map FUNCS = new HashMap(); private static final char NON_VAR_SUFFIX = '$'; - + static { // load all default functions DefaultTextFunctions.init(); @@ -45,7 +46,13 @@ public class DefaultFunctions DefaultDateFunctions.init(); DefaultFinancialFunctions.init(); } - + + public static final FunctionLookup LOOKUP = new FunctionLookup() { + public Function getFunction(String name) { + return DefaultFunctions.getFunction(name); + } + }; + private DefaultFunctions() {} public static Function getFunction(String name) { @@ -93,7 +100,7 @@ public class DefaultFunctions paramStr.substring(1, paramStr.length() - 1) + ")}"; return new IllegalStateException(msg, t); } - + @Override public String toString() { return getName() + "()"; @@ -197,7 +204,7 @@ public class DefaultFunctions } } - protected abstract Value eval3(EvalContext ctx, + protected abstract Value eval3(EvalContext ctx, Value param1, Value param2, Value param3); } @@ -257,10 +264,10 @@ public class DefaultFunctions } } - + public static final Function IIF = registerFunc(new Func3("IIf") { @Override - protected Value eval3(EvalContext ctx, + protected Value eval3(EvalContext ctx, Value param1, Value param2, Value param3) { // null is false return ((!param1.isNull() && param1.getAsBoolean()) ? param2 : param3); @@ -307,7 +314,7 @@ public class DefaultFunctions return params[idx]; } }); - + public static final Function SWITCH = registerFunc(new FuncVar("Switch") { @Override protected Value evalVar(EvalContext ctx, Value[] params) { @@ -322,7 +329,7 @@ public class DefaultFunctions return BuiltinOperators.NULL_VAL; } }); - + public static final Function OCT = registerStringFunc(new Func1NullIsNull("Oct") { @Override protected Value eval1(EvalContext ctx, Value param1) { @@ -334,7 +341,7 @@ public class DefaultFunctions return BuiltinOperators.toValue(Integer.toOctalString(lv)); } }); - + public static final Function CBOOL = registerFunc(new Func1("CBool") { @Override protected Value eval1(EvalContext ctx, Value param1) { @@ -480,7 +487,7 @@ public class DefaultFunctions case BIG_DEC: // vbDecimal vType = 14; - break; + break; default: throw new EvalException("Unknown type " + type); } @@ -513,7 +520,7 @@ public class DefaultFunctions break; case BIG_DEC: tName = "Decimal"; - break; + break; default: throw new EvalException("Unknown type " + type); } @@ -521,7 +528,7 @@ public class DefaultFunctions } }); - + // https://www.techonthenet.com/access/functions/ // https://support.office.com/en-us/article/Access-Functions-by-category-b8b136c3-2716-4d39-94a2-658ce330ed83 diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/RandomContext.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/RandomContext.java index b3a21c2..71a3f6d 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/RandomContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/RandomContext.java @@ -26,7 +26,7 @@ import java.util.Random; * * @author James Ahlborn */ -public class RandomContext +public class RandomContext { private Source _defRnd; private Map _rnds; @@ -34,7 +34,7 @@ public class RandomContext // returned yet private float _lastVal = 1.953125E-02f; - public RandomContext() + public RandomContext() { } @@ -85,7 +85,7 @@ public class RandomContext } private static Random createRandom(long seed) { - // FIXME, support SecureRandom? + // TODO, support SecureRandom? return new Random(seed); } diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java index d779d5a..f659a86 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java @@ -19,6 +19,8 @@ package com.healthmarketscience.jackcess.impl.expr; import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.util.Date; +import javax.script.Bindings; +import javax.script.SimpleBindings; import com.healthmarketscience.jackcess.DatabaseBuilder; import com.healthmarketscience.jackcess.TestUtil; @@ -34,7 +36,7 @@ import junit.framework.TestCase; * * @author James Ahlborn */ -public class ExpressionatorTest extends TestCase +public class ExpressionatorTest extends TestCase { private static final double[] DBLS = { -10.3d,-9.0d,-8.234d,-7.11111d,-6.99999d,-5.5d,-4.0d,-3.4159265d,-2.84d, @@ -49,7 +51,7 @@ public class ExpressionatorTest extends TestCase public void testParseSimpleExprs() throws Exception { validateExpr("\"A\"", "{\"A\"}"); - + validateExpr("13", "{13}"); validateExpr("-42", "{- {42}}"); @@ -91,7 +93,7 @@ public class ExpressionatorTest extends TestCase private static void doTestSimpleBinOp(String opName, String... ops) throws Exception { for(String op : ops) { - validateExpr("\"A\" " + op + " \"B\"", + validateExpr("\"A\" " + op + " \"B\"", "<" + opName + ">{{\"A\"} " + op + " {\"B\"}}"); } @@ -99,37 +101,37 @@ public class ExpressionatorTest extends TestCase public void testOrderOfOperations() throws Exception { - validateExpr("\"A\" Eqv \"B\"", + validateExpr("\"A\" Eqv \"B\"", "{{\"A\"} Eqv {\"B\"}}"); - validateExpr("\"A\" Eqv \"B\" Xor \"C\"", + validateExpr("\"A\" Eqv \"B\" Xor \"C\"", "{{\"A\"} Eqv {{\"B\"} Xor {\"C\"}}}"); - validateExpr("\"A\" Eqv \"B\" Xor \"C\" Or \"D\"", + validateExpr("\"A\" Eqv \"B\" Xor \"C\" Or \"D\"", "{{\"A\"} Eqv {{\"B\"} Xor {{\"C\"} Or {\"D\"}}}}"); - validateExpr("\"A\" Eqv \"B\" Xor \"C\" Or \"D\" And \"E\"", + validateExpr("\"A\" Eqv \"B\" Xor \"C\" Or \"D\" And \"E\"", "{{\"A\"} Eqv {{\"B\"} Xor {{\"C\"} Or {{\"D\"} And {\"E\"}}}}}"); - validateExpr("\"A\" Or \"B\" Or \"C\"", + validateExpr("\"A\" Or \"B\" Or \"C\"", "{{{\"A\"} Or {\"B\"}} Or {\"C\"}}"); - validateExpr("\"A\" & \"B\" Is Null", + validateExpr("\"A\" & \"B\" Is Null", "{{{\"A\"} & {\"B\"}} Is Null}"); - validateExpr("\"A\" Or \"B\" Is Null", + validateExpr("\"A\" Or \"B\" Is Null", "{{\"A\"} Or {{\"B\"} Is Null}}"); - validateExpr("Not \"A\" & \"B\"", + validateExpr("Not \"A\" & \"B\"", "{Not {{\"A\"} & {\"B\"}}}"); - validateExpr("Not \"A\" Or \"B\"", + validateExpr("Not \"A\" Or \"B\"", "{{Not {\"A\"}} Or {\"B\"}}"); - validateExpr("\"A\" + \"B\" Not Between 37 - 15 And 52 / 4", + validateExpr("\"A\" + \"B\" Not Between 37 - 15 And 52 / 4", "{{{\"A\"} + {\"B\"}} Not Between {{37} - {15}} And {{52} / {4}}}"); - validateExpr("\"A\" + (\"B\" Not Between 37 - 15 And 52) / 4", + validateExpr("\"A\" + (\"B\" Not Between 37 - 15 And 52) / 4", "{{\"A\"} + {{({{\"B\"} Not Between {{37} - {15}} And {52}})} / {4}}}"); @@ -324,13 +326,13 @@ public class ExpressionatorTest extends TestCase validateExpr(exprStr, debugStr, exprStr); } - private static void validateExpr(String exprStr, String debugStr, + private static void validateExpr(String exprStr, String debugStr, String cleanStr) { Expression expr = Expressionator.parse( Expressionator.Type.FIELD_VALIDATOR, exprStr, null); String foundDebugStr = expr.toDebugString(); if(foundDebugStr.startsWith("")) { - assertEquals("{{} = " + + assertEquals("{{} = " + debugStr + "}", foundDebugStr); } else { assertEquals(debugStr, foundDebugStr); @@ -365,7 +367,7 @@ public class ExpressionatorTest extends TestCase return new BigDecimal(d).setScale(0, BuiltinOperators.ROUND_MODE) .intValueExact(); } - + private static final class TestParseContext implements Expressionator.ParseContext { public TemporalConfig getTemporalConfig() { @@ -386,6 +388,7 @@ public class ExpressionatorTest extends TestCase { private final Value _thisVal; private final RandomContext _rndCtx = new RandomContext(); + private final Bindings _bindings = new SimpleBindings(); private TestEvalContext(Value thisVal) { _thisVal = thisVal; @@ -418,6 +421,18 @@ public class ExpressionatorTest extends TestCase public float getRandom(Integer seed) { return _rndCtx.getRandom(seed); - } + } + + public Bindings getBindings() { + return _bindings; + } + + public Object get(String key) { + return _bindings.get(key); + } + + public void put(String key, Object value) { + _bindings.put(key, value); + } } } -- 2.39.5