Browse Source

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
tags/jackcess-2.2.0
James Ahlborn 5 years ago
parent
commit
5dbcd36169

+ 8
- 0
src/main/java/com/healthmarketscience/jackcess/expr/Expression.java View File

@@ -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
*/

+ 223
- 0
src/main/java/com/healthmarketscience/jackcess/expr/package-info.java View File

@@ -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.
*<p/>
* When enabled, Jackcess supports the following usage:
* <ul>
* <li><b>Default Values:</b> 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.</li>
* <li><b>Calculated Fields:</b> 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.</li>
* <li><b>Field Validation:</b> 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.</li>
* <li><b>Record Validation:</b> Similar to field validation rules,
* record validation rules will be run for the entire record before
* update. Failures are handled in a similar manner.</li>
* </ul>
* <p/>
* <h2>Supporting Classes</h2>
* <p/>
* The classes in this package make up the public api for expression handling
* in Jackcess. They geneerally fall into two categoreies:
* <p/>
* <h3>General Use Classes</h3>
* <p/>
* <ul>
* </ul>
* <p/>
* <h3>Advanced Use Classes</h3>
* <p/>
* <ul>
* </ul>
* <p/>
* <p/>
* <h2>Function Support</h2>
* <p/>
* Jackcess supports many of the standard Access functions. The following
* tables list the (hopefully) current status of support built into Jackcess.
*
* <h3>Conversion</h3>
*
* <table border="1" width="25%" cellpadding="3" cellspacing="0">
* <tr class="TableHeadingColor" align="left"><th>Function</th><th>Supported</th></tr>
* <tr class="TableRowColor"><td>Asc</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>AscW</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Chr</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>ChrW</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>EuroConvert</td><td></td></tr>
* <tr class="TableRowColor"><td>FormatCurrency</td><td></td></tr>
* <tr class="TableRowColor"><td>FormatDateTime</td><td></td></tr>
* <tr class="TableRowColor"><td>FormatNumber</td><td></td></tr>
* <tr class="TableRowColor"><td>FormatPercent</td><td></td></tr>
* <tr class="TableRowColor"><td>GUIDFromString</td><td></td></tr>
* <tr class="TableRowColor"><td>Hex($)</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Nz</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Oct($)</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Str($)</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>StringFromGUID</td><td></td></tr>
* <tr class="TableRowColor"><td>Val</td><td></td></tr>
* <tr class="TableRowColor"><td>CBool</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>CByte</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>CCur</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>CDate</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>CVDate</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>CDbl</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>CDec</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>CInt</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>CLng</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>CSng</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>CStr</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>CVar</td><td>Y</td></tr>
* </table>
*
* <h3>Date/Time</h3>
*
* <table border="1" width="25%" cellpadding="3" cellspacing="0">
* <tr class="TableHeadingColor" align="left"><th>Function</th><th>Supported</th></tr>
* <tr class="TableRowColor"><td>Day</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Date </td><td>Y</td></tr>
* <tr class="TableRowColor"><td>DateAdd</td><td></td></tr>
* <tr class="TableRowColor"><td>DateDiff</td><td></td></tr>
* <tr class="TableRowColor"><td>DatePart</td><td></td></tr>
* <tr class="TableRowColor"><td>DateSerial</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>DateValue</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Hour</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Minute</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Month</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>MonthName</td><td></td></tr>
* <tr class="TableRowColor"><td>Now</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Second</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Time</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Timer</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>TimeSerial</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>TimeValue</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Weekday</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>WeekdayName</td><td></td></tr>
* <tr class="TableRowColor"><td>Year</td><td>Y</td></tr>
* </table>
*
* <h3>Financial</h3>
*
* <table border="1" width="25%" cellpadding="3" cellspacing="0">
* <tr class="TableHeadingColor" align="left"><th>Function</th><th>Supported</th></tr>
* <tr class="TableRowColor"><td>DDB</td><td></td></tr>
* <tr class="TableRowColor"><td>FV</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>IPmt</td><td></td></tr>
* <tr class="TableRowColor"><td>IRR</td><td></td></tr>
* <tr class="TableRowColor"><td>MIRR</td><td></td></tr>
* <tr class="TableRowColor"><td>NPer</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>NPV</td><td></td></tr>
* <tr class="TableRowColor"><td>Pmt</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>PPmt</td><td></td></tr>
* <tr class="TableRowColor"><td>PV</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Rate</td><td></td></tr>
* <tr class="TableRowColor"><td>SLN</td><td></td></tr>
* <tr class="TableRowColor"><td>SYD</td><td></td></tr>
* </table>
*
* <h3>Inspection</h3>
*
* <table border="1" width="25%" cellpadding="3" cellspacing="0">
* <tr class="TableHeadingColor" align="left"><th>Function</th><th>Supported</th></tr>
* <tr class="TableRowColor"><td>Environ</td><td></td></tr>
* <tr class="TableRowColor"><td>GetAllSettings</td><td></td></tr>
* <tr class="TableRowColor"><td>GetSetting</td><td></td></tr>
* <tr class="TableRowColor"><td>IsArray</td><td></td></tr>
* <tr class="TableRowColor"><td>IsDate</td><td>Partial</td></tr>
* <tr class="TableRowColor"><td>IsEmpty</td><td></td></tr>
* <tr class="TableRowColor"><td>IsError</td><td></td></tr>
* <tr class="TableRowColor"><td>IsMissing</td><td></td></tr>
* <tr class="TableRowColor"><td>IsNull</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>IsNumeric</td><td></td></tr>
* <tr class="TableRowColor"><td>IsObject</td><td></td></tr>
* <tr class="TableRowColor"><td>TypeName</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>VarType</td><td>Y</td></tr>
* </table>
*
* <h3>Math</h3>
*
* <table border="1" width="25%" cellpadding="3" cellspacing="0">
* <tr class="TableHeadingColor" align="left"><th>Function</th><th>Supported</th></tr>
* <tr class="TableRowColor"><td>Abs</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Atn</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Cos</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Exp</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Int</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Fix</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Log</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Rnd</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Round</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Sgn</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Sin</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Sqr</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Tan</td><td>Y</td></tr>
* </table>
*
* <h3>Program Flow</h3>
*
* <table border="1" width="25%" cellpadding="3" cellspacing="0">
* <tr class="TableHeadingColor" align="left"><th>Function</th><th>Supported</th></tr>
* <tr class="TableRowColor"><td>Choose</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>DoEvents</td><td></td></tr>
* <tr class="TableRowColor"><td>IIf</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Switch</td><td>Y</td></tr>
* </table>
*
* <h3>Text</h3>
*
* <table border="1" width="25%" cellpadding="3" cellspacing="0">
* <tr class="TableHeadingColor" align="left"><th>Function</th><th>Supported</th></tr>
* <tr class="TableRowColor"><td>Format</td><td></td></tr>
* <tr class="TableRowColor"><td>InStr</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>InStrRev</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>LCase($)</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Left($)</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Len</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>LTrim($)</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>RTrim($)</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Trim($)</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Mid($)</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Replace</td><td></td></tr>
* <tr class="TableRowColor"><td>Right($)</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>Space($)</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>StrComp</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>StrConv</td><td></td></tr>
* <tr class="TableRowColor"><td>String($)</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>StrReverse</td><td>Y</td></tr>
* <tr class="TableRowColor"><td>UCase($)</td><td>Y</td></tr>
* </table>
*
*
*
*/
package com.healthmarketscience.jackcess.expr;

+ 1
- 0
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java View File

@@ -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.*;

/**
*

+ 55
- 55
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFinancialFunctions.java View File

@@ -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
}

}


+ 1
- 209
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java View File

@@ -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<String,Function> FUNCS =
new HashMap<String,Function>();

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,

+ 2
- 1
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java View File

@@ -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)

+ 1
- 0
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java View File

@@ -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.*;

/**
*

+ 241
- 0
src/main/java/com/healthmarketscience/jackcess/impl/expr/FunctionSupport.java View File

@@ -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() + "()";
}
}

}

+ 9
- 0
src/site/xdoc/index.xml View File

@@ -19,6 +19,15 @@
for more info.
</p>

<subsection name="Expression Evaluation">
<p>
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 <a href="apidocs/com/healthmarketscience/jackcess/expr/package-summary.html#package_description">expr package</a> javadocs for more details.
</p>
</subsection>

<subsection name="Brand New License!">
<p>
Due to the generosity of Health Market Science and the efforts of

+ 2
- 1
src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java View File

@@ -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

Loading…
Cancel
Save