git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1190 f203690c-595d-4dc9-a70b-905162fa7fd2tags/jackcess-2.2.0
@@ -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 | |||
*/ |
@@ -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; |
@@ -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.*; | |||
/** | |||
* |
@@ -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 | |||
} | |||
} | |||
@@ -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, |
@@ -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) |
@@ -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.*; | |||
/** | |||
* |
@@ -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() + "()"; | |||
} | |||
} | |||
} |
@@ -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 |
@@ -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 |