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:
+ *
+ * - Default Values: When a row is added which has a
+ * {@code null} value for a field which has a default value
+ * expression defined, that expression will be evaluated and the
+ * result will be inserted for that field. Like an auto-generated
+ * id, the generated default value will be returned in the input
+ * row array.
+ * - Calculated Fields: In databases which support calculated
+ * fields, any input value for a calculated field will be ignored.
+ * Instead, the result of evaluating the calculated field
+ * expression will be inserted. Like an auto-generated id, the
+ * calculated value will be returned in the input row array.
+ * - Field Validation: Field validation rules will be
+ * evaluated whenever a field value is updated. If the rule fails,
+ * the update operation will fail. The failure message will
+ * specify which field's validation rule failed and include the
+ * custom validation rule message if defined.
+ * - Record Validation: Similar to field validation rules,
+ * record validation rules will be run for the entire record before
+ * update. Failures are handled in a similar manner.
+ *
+ *
+ * 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
+ *
+ *
+ * Function | Supported |
+ * Asc | Y |
+ * AscW | Y |
+ * Chr | Y |
+ * ChrW | Y |
+ * EuroConvert | |
+ * FormatCurrency | |
+ * FormatDateTime | |
+ * FormatNumber | |
+ * FormatPercent | |
+ * GUIDFromString | |
+ * Hex($) | Y |
+ * Nz | Y |
+ * Oct($) | Y |
+ * Str($) | Y |
+ * StringFromGUID | |
+ * Val | |
+ * CBool | Y |
+ * CByte | Y |
+ * CCur | Y |
+ * CDate | Y |
+ * CVDate | Y |
+ * CDbl | Y |
+ * CDec | Y |
+ * CInt | Y |
+ * CLng | Y |
+ * CSng | Y |
+ * CStr | Y |
+ * CVar | Y |
+ *
+ *
+ * Date/Time
+ *
+ *
+ * Function | Supported |
+ * Day | Y |
+ * Date | Y |
+ * DateAdd | |
+ * DateDiff | |
+ * DatePart | |
+ * DateSerial | Y |
+ * DateValue | Y |
+ * Hour | Y |
+ * Minute | Y |
+ * Month | Y |
+ * MonthName | |
+ * Now | Y |
+ * Second | Y |
+ * Time | Y |
+ * Timer | Y |
+ * TimeSerial | Y |
+ * TimeValue | Y |
+ * Weekday | Y |
+ * WeekdayName | |
+ * Year | Y |
+ *
+ *
+ * Financial
+ *
+ *
+ * Function | Supported |
+ * DDB | |
+ * FV | Y |
+ * IPmt | |
+ * IRR | |
+ * MIRR | |
+ * NPer | Y |
+ * NPV | |
+ * Pmt | Y |
+ * PPmt | |
+ * PV | Y |
+ * Rate | |
+ * SLN | |
+ * SYD | |
+ *
+ *
+ * Inspection
+ *
+ *
+ * Function | Supported |
+ * Environ | |
+ * GetAllSettings | |
+ * GetSetting | |
+ * IsArray | |
+ * IsDate | Partial |
+ * IsEmpty | |
+ * IsError | |
+ * IsMissing | |
+ * IsNull | Y |
+ * IsNumeric | |
+ * IsObject | |
+ * TypeName | Y |
+ * VarType | Y |
+ *
+ *
+ * Math
+ *
+ *
+ * Function | Supported |
+ * Abs | Y |
+ * Atn | Y |
+ * Cos | Y |
+ * Exp | Y |
+ * Int | Y |
+ * Fix | Y |
+ * Log | Y |
+ * Rnd | Y |
+ * Round | Y |
+ * Sgn | Y |
+ * Sin | Y |
+ * Sqr | Y |
+ * Tan | Y |
+ *
+ *
+ * Program Flow
+ *
+ *
+ * Function | Supported |
+ * Choose | Y |
+ * DoEvents | |
+ * IIf | Y |
+ * Switch | Y |
+ *
+ *
+ * Text
+ *
+ *
+ * Function | Supported |
+ * Format | |
+ * InStr | Y |
+ * InStrRev | Y |
+ * LCase($) | Y |
+ * Left($) | Y |
+ * Len | Y |
+ * LTrim($) | Y |
+ * RTrim($) | Y |
+ * Trim($) | Y |
+ * Mid($) | Y |
+ * Replace | |
+ * Right($) | Y |
+ * Space($) | Y |
+ * StrComp | Y |
+ * StrConv | |
+ * String($) | Y |
+ * StrReverse | Y |
+ * 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