From 5dbcd361698afc6da7e88a7186ee459a658fcf6d Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Fri, 24 Aug 2018 04:11:43 +0000 Subject: [PATCH] minor refactor of function support; start adding top level docs for evaluation support git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1190 f203690c-595d-4dc9-a70b-905162fa7fd2 --- .../jackcess/expr/Expression.java | 8 + .../jackcess/expr/package-info.java | 223 ++++++++++++++++ .../impl/expr/DefaultDateFunctions.java | 1 + .../impl/expr/DefaultFinancialFunctions.java | 110 ++++---- .../jackcess/impl/expr/DefaultFunctions.java | 210 +-------------- .../impl/expr/DefaultNumberFunctions.java | 3 +- .../impl/expr/DefaultTextFunctions.java | 1 + .../jackcess/impl/expr/FunctionSupport.java | 241 ++++++++++++++++++ src/site/xdoc/index.xml | 9 + .../jackcess/PropertyExpressionTest.java | 3 +- 10 files changed, 543 insertions(+), 266 deletions(-) create mode 100644 src/main/java/com/healthmarketscience/jackcess/expr/package-info.java create mode 100644 src/main/java/com/healthmarketscience/jackcess/impl/expr/FunctionSupport.java diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/Expression.java b/src/main/java/com/healthmarketscience/jackcess/expr/Expression.java index 963af7b..cffd7b9 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/Expression.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/Expression.java @@ -19,6 +19,14 @@ package com.healthmarketscience.jackcess.expr; import java.util.Collection; /** + * FIXME, doc me and my friend + * + * An Expression is an executable handle to an Access expression. While the + * expression framework is implemented as separate from the core database + * functionality, most usage of Expressions will happen indirectly within the + * context of normal database operations. Thus, most users will not ever + * directly interact with an Expression instance. That said, Expressions may + * be executed independently of a Database instance if desired. * * @author James Ahlborn */ diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/package-info.java b/src/main/java/com/healthmarketscience/jackcess/expr/package-info.java new file mode 100644 index 0000000..6e02e88 --- /dev/null +++ b/src/main/java/com/healthmarketscience/jackcess/expr/package-info.java @@ -0,0 +1,223 @@ +/* +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. +*/ + +/** + * Jackcess has support for evaluating Access expressions (beta support as of + * the 2.2.0 release). This functionality is currently disabled by default + * but can be globally enabled via the system property + * "com.healthmarketscience.jackcess.enableExpressionEvaluation" or + * selectively enabled on a per database basis using {@link com.healthmarketscience.jackcess.Database#setEvaluateExpressions(Boolean)}. Expressions can be used in a + * number of different places within an Access database. + *

+ * When enabled, Jackcess supports the following usage: + *

+ *

+ *

Supporting Classes

+ *

+ * The classes in this package make up the public api for expression handling + * in Jackcess. They geneerally fall into two categoreies: + *

+ *

General Use Classes

+ *

+ *

+ *

+ *

Advanced Use Classes

+ *

+ *

+ *

+ *

+ *

Function Support

+ *

+ * Jackcess supports many of the standard Access functions. The following + * tables list the (hopefully) current status of support built into Jackcess. + * + *

Conversion

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
FunctionSupported
AscY
AscWY
ChrY
ChrWY
EuroConvert
FormatCurrency
FormatDateTime
FormatNumber
FormatPercent
GUIDFromString
Hex($)Y
NzY
Oct($)Y
Str($)Y
StringFromGUID
Val
CBoolY
CByteY
CCurY
CDateY
CVDateY
CDblY
CDecY
CIntY
CLngY
CSngY
CStrY
CVarY
+ * + *

Date/Time

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
FunctionSupported
DayY
Date Y
DateAdd
DateDiff
DatePart
DateSerialY
DateValueY
HourY
MinuteY
MonthY
MonthName
NowY
SecondY
TimeY
TimerY
TimeSerialY
TimeValueY
WeekdayY
WeekdayName
YearY
+ * + *

Financial

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
FunctionSupported
DDB
FVY
IPmt
IRR
MIRR
NPerY
NPV
PmtY
PPmt
PVY
Rate
SLN
SYD
+ * + *

Inspection

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
FunctionSupported
Environ
GetAllSettings
GetSetting
IsArray
IsDatePartial
IsEmpty
IsError
IsMissing
IsNullY
IsNumeric
IsObject
TypeNameY
VarTypeY
+ * + *

Math

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
FunctionSupported
AbsY
AtnY
CosY
ExpY
IntY
FixY
LogY
RndY
RoundY
SgnY
SinY
SqrY
TanY
+ * + *

Program Flow

+ * + * + * + * + * + * + * + *
FunctionSupported
ChooseY
DoEvents
IIfY
SwitchY
+ * + *

Text

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
FunctionSupported
Format
InStrY
InStrRevY
LCase($)Y
Left($)Y
LenY
LTrim($)Y
RTrim($)Y
Trim($)Y
Mid($)Y
Replace
Right($)Y
Space($)Y
StrCompY
StrConv
String($)Y
StrReverseY
UCase($)Y
+ * + * + * + */ +package com.healthmarketscience.jackcess.expr; diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java index ac5313c..83c6ee8 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java @@ -28,6 +28,7 @@ import com.healthmarketscience.jackcess.expr.Function; import com.healthmarketscience.jackcess.expr.Value; import com.healthmarketscience.jackcess.impl.ColumnImpl; import static com.healthmarketscience.jackcess.impl.expr.DefaultFunctions.*; +import static com.healthmarketscience.jackcess.impl.expr.FunctionSupport.*; /** * diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFinancialFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFinancialFunctions.java index 4fe59ec..df397b0 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFinancialFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFinancialFunctions.java @@ -21,12 +21,13 @@ import com.healthmarketscience.jackcess.expr.EvalContext; import com.healthmarketscience.jackcess.expr.Function; import com.healthmarketscience.jackcess.expr.Value; import static com.healthmarketscience.jackcess.impl.expr.DefaultFunctions.*; +import static com.healthmarketscience.jackcess.impl.expr.FunctionSupport.*; /** * * @author James Ahlborn */ -public class DefaultFinancialFunctions +public class DefaultFinancialFunctions { /** 0 - payment end of month (default) */ private static final int PMT_END_MNTH = 0; @@ -39,14 +40,14 @@ public class DefaultFinancialFunctions static void init() { // dummy method to ensure this class is loaded } - + public static final Function NPER = registerFunc(new FuncVar("NPer", 3, 5) { @Override protected Value evalVar(EvalContext ctx, Value[] params) { - double rate = params[0].getAsDouble(); - double pmt = params[1].getAsDouble(); - double pv = params[2].getAsDouble(); + double rate = params[0].getAsDouble(); + double pmt = params[1].getAsDouble(); + double pv = params[2].getAsDouble(); double fv = 0d; if(params.length > 3) { @@ -71,9 +72,9 @@ public class DefaultFinancialFunctions public static final Function FV = registerFunc(new FuncVar("FV", 3, 5) { @Override protected Value evalVar(EvalContext ctx, Value[] params) { - double rate = params[0].getAsDouble(); - double nper = params[1].getAsDouble(); - double pmt = params[2].getAsDouble(); + double rate = params[0].getAsDouble(); + double nper = params[1].getAsDouble(); + double pmt = params[2].getAsDouble(); double pv = 0d; if(params.length > 3) { @@ -98,9 +99,9 @@ public class DefaultFinancialFunctions public static final Function PV = registerFunc(new FuncVar("PV", 3, 5) { @Override protected Value evalVar(EvalContext ctx, Value[] params) { - double rate = params[0].getAsDouble(); - double nper = params[1].getAsDouble(); - double pmt = params[2].getAsDouble(); + double rate = params[0].getAsDouble(); + double nper = params[1].getAsDouble(); + double pmt = params[2].getAsDouble(); double fv = 0d; if(params.length > 3) { @@ -125,9 +126,9 @@ public class DefaultFinancialFunctions public static final Function PMT = registerFunc(new FuncVar("Pmt", 3, 5) { @Override protected Value evalVar(EvalContext ctx, Value[] params) { - double rate = params[0].getAsDouble(); - double nper = params[1].getAsDouble(); - double pv = params[2].getAsDouble(); + double rate = params[0].getAsDouble(); + double nper = params[1].getAsDouble(); + double pv = params[2].getAsDouble(); double fv = 0d; if(params.length > 3) { @@ -153,10 +154,10 @@ public class DefaultFinancialFunctions // public static final Function IPMT = registerFunc(new FuncVar("IPmt", 4, 6) { // @Override // protected Value evalVar(EvalContext ctx, Value[] params) { - // double rate = params[0].getAsDouble(); - // double per = params[1].getAsDouble(); - // double nper = params[2].getAsDouble(); - // double pv = params[3].getAsDouble(); + // double rate = params[0].getAsDouble(); + // double per = params[1].getAsDouble(); + // double nper = params[2].getAsDouble(); + // double pv = params[3].getAsDouble(); // double fv = 0d; // if(params.length > 4) { @@ -184,10 +185,10 @@ public class DefaultFinancialFunctions // public static final Function PPMT = registerFunc(new FuncVar("PPmt", 4, 6) { // @Override // protected Value evalVar(EvalContext ctx, Value[] params) { - // double rate = params[0].getAsDouble(); - // double per = params[1].getAsDouble(); - // double nper = params[2].getAsDouble(); - // double pv = params[3].getAsDouble(); + // double rate = params[0].getAsDouble(); + // double per = params[1].getAsDouble(); + // double nper = params[2].getAsDouble(); + // double pv = params[3].getAsDouble(); // double fv = 0d; // if(params.length > 4) { @@ -216,10 +217,10 @@ public class DefaultFinancialFunctions // public static final Function DDB = registerFunc(new FuncVar("DDB", 4, 5) { // @Override // protected Value evalVar(EvalContext ctx, Value[] params) { - // double cost = params[0].getAsDouble(); - // double salvage = params[1].getAsDouble(); - // double life = params[2].getAsDouble(); - // double period = params[3].getAsDouble(); + // double cost = params[0].getAsDouble(); + // double salvage = params[1].getAsDouble(); + // double life = params[2].getAsDouble(); + // double period = params[3].getAsDouble(); // double factor = 2d; // if(params.length > 4) { @@ -259,34 +260,34 @@ public class DefaultFinancialFunctions // }); // FIXME, untested - public static final Function SLN = registerFunc(new FuncVar("SLN", 3, 3) { - @Override - protected Value evalVar(EvalContext ctx, Value[] params) { - double cost = params[0].getAsDouble(); - double salvage = params[1].getAsDouble(); - double life = params[2].getAsDouble(); + // public static final Function SLN = registerFunc(new FuncVar("SLN", 3, 3) { + // @Override + // protected Value evalVar(EvalContext ctx, Value[] params) { + // double cost = params[0].getAsDouble(); + // double salvage = params[1].getAsDouble(); + // double life = params[2].getAsDouble(); - double result = calculateStraightLineDepreciation(cost, salvage, life); + // double result = calculateStraightLineDepreciation(cost, salvage, life); - return BuiltinOperators.toValue(result); - } - }); + // return BuiltinOperators.toValue(result); + // } + // }); // FIXME, untested - public static final Function SYD = registerFunc(new FuncVar("SYD", 4, 4) { - @Override - protected Value evalVar(EvalContext ctx, Value[] params) { - double cost = params[0].getAsDouble(); - double salvage = params[1].getAsDouble(); - double life = params[2].getAsDouble(); - double period = params[3].getAsDouble(); + // public static final Function SYD = registerFunc(new FuncVar("SYD", 4, 4) { + // @Override + // protected Value evalVar(EvalContext ctx, Value[] params) { + // double cost = params[0].getAsDouble(); + // double salvage = params[1].getAsDouble(); + // double life = params[2].getAsDouble(); + // double period = params[3].getAsDouble(); - double result = calculateSumOfYearsDepreciation( - cost, salvage, life, period); + // double result = calculateSumOfYearsDepreciation( + // cost, salvage, life, period); - return BuiltinOperators.toValue(result); - } - }); + // return BuiltinOperators.toValue(result); + // } + // }); private static double calculateLoanPaymentPeriods( @@ -300,9 +301,9 @@ public class DefaultFinancialFunctions } double v1 = Math.log(1d + (rate * pv / pmt)); - + double v2 = Math.log(1d + rate); - + double result = -v1 / v2; if(pmtType == PMT_BEG_MNTH) { @@ -326,7 +327,7 @@ public class DefaultFinancialFunctions double v1 = Math.log(1d - (rate * fv / pmt)); double v2 = Math.log(1d + rate); - + double result = v1 / v2; if(pmtType == PMT_BEG_MNTH) { @@ -374,7 +375,7 @@ public class DefaultFinancialFunctions } return result; - } + } private static double calculateAnnuityPayment( double rate, double nper, double fv, int pmtType) { @@ -386,7 +387,7 @@ public class DefaultFinancialFunctions } return result; - } + } private static double calculateInterestPayment( double pmt, double rate, double per, double pv, int pmtType) { @@ -404,7 +405,7 @@ public class DefaultFinancialFunctions fvPer -= 1d; } - double remBalance = (pv * Math.pow((1d + rate), pvPer)) - + double remBalance = (pv * Math.pow((1d + rate), pvPer)) - // FIXME, always use pmtType of 0? calculateFutureValue(rate, fvPer, pmt, PMT_END_MNTH); @@ -437,4 +438,3 @@ public class DefaultFinancialFunctions } } - 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 f77e0a3..bfbf105 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java @@ -17,7 +17,6 @@ limitations under the License. package com.healthmarketscience.jackcess.impl.expr; import java.math.BigDecimal; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -28,6 +27,7 @@ import com.healthmarketscience.jackcess.expr.FunctionLookup; import com.healthmarketscience.jackcess.expr.Value; import com.healthmarketscience.jackcess.impl.DatabaseImpl; import com.healthmarketscience.jackcess.impl.NumberFormatter; +import static com.healthmarketscience.jackcess.impl.expr.FunctionSupport.*; /** * @@ -38,8 +38,6 @@ public class DefaultFunctions private static final Map FUNCS = new HashMap(); - private static final char NON_VAR_SUFFIX = '$'; - static { // load all default functions DefaultTextFunctions.init(); @@ -57,212 +55,6 @@ public class DefaultFunctions private DefaultFunctions() {} - public static abstract class BaseFunction implements Function - { - private final String _name; - private final int _minParams; - private final int _maxParams; - - protected BaseFunction(String name, int minParams, int maxParams) - { - _name = name; - _minParams = minParams; - _maxParams = maxParams; - } - - public String getName() { - return _name; - } - - public boolean isPure() { - // most functions are probably pure, so make this the default - return true; - } - - protected void validateNumParams(Value[] params) { - int num = params.length; - if((num < _minParams) || (num > _maxParams)) { - String range = ((_minParams == _maxParams) ? "" + _minParams : - _minParams + " to " + _maxParams); - throw new EvalException( - "Invalid number of parameters " + - num + " passed, expected " + range); - } - } - - protected EvalException invalidFunctionCall( - Throwable t, Value[] params) - { - String paramStr = Arrays.toString(params); - String msg = "Invalid function call {" + _name + "(" + - paramStr.substring(1, paramStr.length() - 1) + ")}"; - return new EvalException(msg, t); - } - - @Override - public String toString() { - return getName() + "()"; - } - } - - public static abstract class Func0 extends BaseFunction - { - protected Func0(String name) { - super(name, 0, 0); - } - - @Override - public boolean isPure() { - // 0-arg functions are usually not pure - return false; - } - - public final Value eval(EvalContext ctx, Value... params) { - try { - validateNumParams(params); - return eval0(ctx); - } catch(Exception e) { - throw invalidFunctionCall(e, params); - } - } - - protected abstract Value eval0(EvalContext ctx); - } - - public static abstract class Func1 extends BaseFunction - { - protected Func1(String name) { - super(name, 1, 1); - } - - public final Value eval(EvalContext ctx, Value... params) { - try { - validateNumParams(params); - return eval1(ctx, params[0]); - } catch(Exception e) { - throw invalidFunctionCall(e, params); - } - } - - protected abstract Value eval1(EvalContext ctx, Value param); - } - - public static abstract class Func1NullIsNull extends BaseFunction - { - protected Func1NullIsNull(String name) { - super(name, 1, 1); - } - - public final Value eval(EvalContext ctx, Value... params) { - try { - validateNumParams(params); - Value param1 = params[0]; - if(param1.isNull()) { - return param1; - } - return eval1(ctx, param1); - } catch(Exception e) { - throw invalidFunctionCall(e, params); - } - } - - protected abstract Value eval1(EvalContext ctx, Value param); - } - - public static abstract class Func2 extends BaseFunction - { - protected Func2(String name) { - super(name, 2, 2); - } - - public final Value eval(EvalContext ctx, Value... params) { - try { - validateNumParams(params); - return eval2(ctx, params[0], params[1]); - } catch(Exception e) { - throw invalidFunctionCall(e, params); - } - } - - protected abstract Value eval2(EvalContext ctx, Value param1, Value param2); - } - - public static abstract class Func3 extends BaseFunction - { - protected Func3(String name) { - super(name, 3, 3); - } - - public final Value eval(EvalContext ctx, Value... params) { - try { - validateNumParams(params); - return eval3(ctx, params[0], params[1], params[2]); - } catch(Exception e) { - throw invalidFunctionCall(e, params); - } - } - - protected abstract Value eval3(EvalContext ctx, - Value param1, Value param2, Value param3); - } - - public static abstract class FuncVar extends BaseFunction - { - protected FuncVar(String name) { - super(name, 0, Integer.MAX_VALUE); - } - - protected FuncVar(String name, int minParams, int maxParams) { - super(name, minParams, maxParams); - } - - public final Value eval(EvalContext ctx, Value... params) { - try { - validateNumParams(params); - return evalVar(ctx, params); - } catch(Exception e) { - throw invalidFunctionCall(e, params); - } - } - - protected abstract Value evalVar(EvalContext ctx, Value[] params); - } - - public static class StringFuncWrapper implements Function - { - private final String _name; - private final Function _delegate; - - public StringFuncWrapper(Function delegate) { - _delegate = delegate; - _name = _delegate.getName() + NON_VAR_SUFFIX; - } - - public String getName() { - return _name; - } - - public boolean isPure() { - return _delegate.isPure(); - } - - public Value eval(EvalContext ctx, Value... params) { - Value result = _delegate.eval(ctx, params); - if(result.isNull()) { - // non-variant version does not do null-propagation, so force - // exception to be thrown here - result.getAsString(); - } - return result; - } - - @Override - public String toString() { - return getName() + "()"; - } - } - - public static final Function IIF = registerFunc(new Func3("IIf") { @Override protected Value eval3(EvalContext ctx, diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java index d93463f..aeca27d 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java @@ -24,6 +24,7 @@ import com.healthmarketscience.jackcess.expr.Function; import com.healthmarketscience.jackcess.expr.Value; import com.healthmarketscience.jackcess.impl.NumberFormatter; import static com.healthmarketscience.jackcess.impl.expr.DefaultFunctions.*; +import static com.healthmarketscience.jackcess.impl.expr.FunctionSupport.*; /** * @@ -185,7 +186,7 @@ public class DefaultNumberFunctions }); - // public static final Function Val = registerFunc(new Func1("Val") { + // public static final Function VAL = registerFunc(new Func1("Val") { // @Override // protected Value eval1(EvalContext ctx, Value param1) { // // FIXME, maybe leverage ExpressionTokenizer.maybeParseNumberLiteral (note, leading - or + is valid, exponent form is valid) diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java index de1159a..18f7c1f 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java @@ -23,6 +23,7 @@ import com.healthmarketscience.jackcess.expr.EvalException; import com.healthmarketscience.jackcess.expr.Function; import com.healthmarketscience.jackcess.expr.Value; import static com.healthmarketscience.jackcess.impl.expr.DefaultFunctions.*; +import static com.healthmarketscience.jackcess.impl.expr.FunctionSupport.*; /** * diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/FunctionSupport.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/FunctionSupport.java new file mode 100644 index 0000000..bc87f59 --- /dev/null +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/FunctionSupport.java @@ -0,0 +1,241 @@ +/* +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.impl.expr; + +import java.util.Arrays; + +import com.healthmarketscience.jackcess.expr.EvalContext; +import com.healthmarketscience.jackcess.expr.EvalException; +import com.healthmarketscience.jackcess.expr.Function; +import com.healthmarketscience.jackcess.expr.Value; + +/** + * + * @author James Ahlborn + */ +public class FunctionSupport +{ + private static final char NON_VAR_SUFFIX = '$'; + + private FunctionSupport() {} + + public static abstract class BaseFunction implements Function + { + private final String _name; + private final int _minParams; + private final int _maxParams; + + protected BaseFunction(String name, int minParams, int maxParams) + { + _name = name; + _minParams = minParams; + _maxParams = maxParams; + } + + public String getName() { + return _name; + } + + public boolean isPure() { + // most functions are probably pure, so make this the default + return true; + } + + protected void validateNumParams(Value[] params) { + int num = params.length; + if((num < _minParams) || (num > _maxParams)) { + String range = ((_minParams == _maxParams) ? "" + _minParams : + _minParams + " to " + _maxParams); + throw new EvalException( + "Invalid number of parameters " + + num + " passed, expected " + range); + } + } + + protected EvalException invalidFunctionCall( + Throwable t, Value[] params) + { + String paramStr = Arrays.toString(params); + String msg = "Invalid function call {" + _name + "(" + + paramStr.substring(1, paramStr.length() - 1) + ")}"; + return new EvalException(msg, t); + } + + @Override + public String toString() { + return getName() + "()"; + } + } + + public static abstract class Func0 extends BaseFunction + { + protected Func0(String name) { + super(name, 0, 0); + } + + @Override + public boolean isPure() { + // 0-arg functions are usually not pure + return false; + } + + public final Value eval(EvalContext ctx, Value... params) { + try { + validateNumParams(params); + return eval0(ctx); + } catch(Exception e) { + throw invalidFunctionCall(e, params); + } + } + + protected abstract Value eval0(EvalContext ctx); + } + + public static abstract class Func1 extends BaseFunction + { + protected Func1(String name) { + super(name, 1, 1); + } + + public final Value eval(EvalContext ctx, Value... params) { + try { + validateNumParams(params); + return eval1(ctx, params[0]); + } catch(Exception e) { + throw invalidFunctionCall(e, params); + } + } + + protected abstract Value eval1(EvalContext ctx, Value param); + } + + public static abstract class Func1NullIsNull extends BaseFunction + { + protected Func1NullIsNull(String name) { + super(name, 1, 1); + } + + public final Value eval(EvalContext ctx, Value... params) { + try { + validateNumParams(params); + Value param1 = params[0]; + if(param1.isNull()) { + return param1; + } + return eval1(ctx, param1); + } catch(Exception e) { + throw invalidFunctionCall(e, params); + } + } + + protected abstract Value eval1(EvalContext ctx, Value param); + } + + public static abstract class Func2 extends BaseFunction + { + protected Func2(String name) { + super(name, 2, 2); + } + + public final Value eval(EvalContext ctx, Value... params) { + try { + validateNumParams(params); + return eval2(ctx, params[0], params[1]); + } catch(Exception e) { + throw invalidFunctionCall(e, params); + } + } + + protected abstract Value eval2(EvalContext ctx, Value param1, Value param2); + } + + public static abstract class Func3 extends BaseFunction + { + protected Func3(String name) { + super(name, 3, 3); + } + + public final Value eval(EvalContext ctx, Value... params) { + try { + validateNumParams(params); + return eval3(ctx, params[0], params[1], params[2]); + } catch(Exception e) { + throw invalidFunctionCall(e, params); + } + } + + protected abstract Value eval3(EvalContext ctx, + Value param1, Value param2, Value param3); + } + + public static abstract class FuncVar extends BaseFunction + { + protected FuncVar(String name) { + super(name, 0, Integer.MAX_VALUE); + } + + protected FuncVar(String name, int minParams, int maxParams) { + super(name, minParams, maxParams); + } + + public final Value eval(EvalContext ctx, Value... params) { + try { + validateNumParams(params); + return evalVar(ctx, params); + } catch(Exception e) { + throw invalidFunctionCall(e, params); + } + } + + protected abstract Value evalVar(EvalContext ctx, Value[] params); + } + + public static class StringFuncWrapper implements Function + { + private final String _name; + private final Function _delegate; + + public StringFuncWrapper(Function delegate) { + _delegate = delegate; + _name = _delegate.getName() + NON_VAR_SUFFIX; + } + + public String getName() { + return _name; + } + + public boolean isPure() { + return _delegate.isPure(); + } + + public Value eval(EvalContext ctx, Value... params) { + Value result = _delegate.eval(ctx, params); + if(result.isNull()) { + // non-variant version does not do null-propagation, so force + // exception to be thrown here + result.getAsString(); + } + return result; + } + + @Override + public String toString() { + return getName() + "()"; + } + } + +} diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml index 4da6248..c8d7f6b 100644 --- a/src/site/xdoc/index.xml +++ b/src/site/xdoc/index.xml @@ -19,6 +19,15 @@ for more info.

+ +

+ Have you ever wished that Jackcess could handle field "default + values" (or other expressions)? Wish no longer! The 2.2.0 version + of Jackcess has finally landed a beta version of expression + evaluation. See the expr package javadocs for more details. +

+
+

Due to the generosity of Health Market Science and the efforts of diff --git a/src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java b/src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java index 6295477..b73496f 100644 --- a/src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java @@ -28,6 +28,7 @@ import com.healthmarketscience.jackcess.expr.TemporalConfig; import com.healthmarketscience.jackcess.expr.Value; import com.healthmarketscience.jackcess.impl.expr.BuiltinOperators; import com.healthmarketscience.jackcess.impl.expr.DefaultFunctions; +import com.healthmarketscience.jackcess.impl.expr.FunctionSupport; import junit.framework.TestCase; import static com.healthmarketscience.jackcess.Database.*; @@ -347,7 +348,7 @@ public class PropertyExpressionTest extends TestCase props.save(); } - private static final Function FOO = new DefaultFunctions.Func0("FooFunc") { + private static final Function FOO = new FunctionSupport.Func0("FooFunc") { @Override public boolean isPure() { return false; } @Override -- 2.39.5