aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Ahlborn <jtahlborn@yahoo.com>2016-11-03 20:13:35 +0000
committerJames Ahlborn <jtahlborn@yahoo.com>2016-11-03 20:13:35 +0000
commit874edea4ed1fcfca3a35c98d0706c320fc65c58b (patch)
treeb12f7afdef98d37f068d19dccf1073e41e450f5a
parent46926ceb6fc192f3895c6736be58a93505ec031c (diff)
downloadjackcess-874edea4ed1fcfca3a35c98d0706c320fc65c58b.tar.gz
jackcess-874edea4ed1fcfca3a35c98d0706c320fc65c58b.zip
rework classes, add more interfaces; start implementing builtin functions using Value type
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/exprs@1054 f203690c-595d-4dc9-a70b-905162fa7fd2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java9
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/BuiltinOperators.java437
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/DefaultFunctions.java152
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/Expression.java68
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/ExpressionTokenizer.java78
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/Expressionator.java462
-rw-r--r--src/test/java/com/healthmarketscience/jackcess/util/ExpressionatorTest.java4
7 files changed, 1052 insertions, 158 deletions
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java
index 998e80a..d61bab8 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java
@@ -1524,6 +1524,15 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
return false;
} else if(obj instanceof Boolean) {
return ((Boolean)obj).booleanValue();
+ } else if(obj instanceof Number) {
+ // Access considers 0 as "false"
+ if(obj instanceof BigDecimal) {
+ return (((BigDecimal)obj).compareTo(BigDecimal.ZERO) != 0);
+ }
+ if(obj instanceof BigInteger) {
+ return (((BigInteger)obj).compareTo(BigInteger.ZERO) != 0);
+ }
+ return (((Number)obj).doubleValue() != 0.0);
}
return Boolean.parseBoolean(obj.toString());
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/BuiltinOperators.java b/src/main/java/com/healthmarketscience/jackcess/util/BuiltinOperators.java
new file mode 100644
index 0000000..cb7eb6c
--- /dev/null
+++ b/src/main/java/com/healthmarketscience/jackcess/util/BuiltinOperators.java
@@ -0,0 +1,437 @@
+/*
+Copyright (c) 2016 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.util;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import com.healthmarketscience.jackcess.RuntimeIOException;
+import com.healthmarketscience.jackcess.impl.ColumnImpl;
+import com.healthmarketscience.jackcess.util.Expression.*;
+
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class BuiltinOperators
+{
+
+ public static final Value NULL_VAL =
+ new SimpleValue(ValueType.NULL, null);
+ public static final Value TRUE_VAL =
+ new SimpleValue(ValueType.BOOLEAN, Boolean.TRUE);
+ public static final Value FALSE_VAL =
+ new SimpleValue(ValueType.BOOLEAN, Boolean.FALSE);
+
+ public static class SimpleValue implements Value
+ {
+ private final ValueType _type;
+ private final Object _val;
+
+ public SimpleValue(ValueType type, Object val) {
+ _type = type;
+ _val = val;
+ }
+
+ public ValueType getType() {
+ return _type;
+ }
+
+ public Object get() {
+ return _val;
+ }
+ }
+
+ private BuiltinOperators() {}
+
+ // FIXME, null propagation:
+ // http://www.utteraccess.com/wiki/index.php/Nulls_And_Their_Behavior
+ // https://theaccessbuddy.wordpress.com/2012/10/24/6-logical-operators-in-ms-access-that-you-must-know-operator-types-3-of-5/
+ // - number ops
+ // - comparison ops
+ // - logical ops (some "special")
+ // - And - can be false if one arg is false
+ // - Or - can be true if one arg is true
+ // - between, not, like, in
+ // - *NOT* concal op '&'
+ // FIXME, Imp operator?
+
+ public static Value negate(Value param1) {
+ // FIXME
+ return null;
+ }
+
+ public static Value add(Value param1, Value param2) {
+ // FIXME
+ return null;
+ }
+
+ public static Value subtract(Value param1, Value param2) {
+ // FIXME
+ return null;
+ }
+
+ public static Value multiply(Value param1, Value param2) {
+ // FIXME
+ return null;
+ }
+
+ public static Value divide(Value param1, Value param2) {
+ // FIXME
+ return null;
+ }
+
+ public static Value intDivide(Value param1, Value param2) {
+ // FIXME
+ return null;
+ }
+
+ public static Value exp(Value param1, Value param2) {
+ // FIXME
+ return null;
+ }
+
+ public static Value concat(Value param1, Value param2) {
+ // note, this op converts null to empty string
+
+
+ // FIXME
+ return null;
+ }
+
+ public static Value mod(Value param1, Value param2) {
+ // FIXME
+ return null;
+ }
+
+ public static Value not(Value param1) {
+ if(paramIsNull(param1)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ return toValue(!nonNullValueToBoolean(param1));
+ }
+
+ public static Value lessThan(Value param1, Value param2) {
+ if(anyParamIsNull(param1, param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ return toValue(nonNullCompareTo(param1, param2) < 0);
+ }
+
+ public static Value greaterThan(Value param1, Value param2) {
+ if(anyParamIsNull(param1, param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ return toValue(nonNullCompareTo(param1, param2) > 0);
+ }
+
+ public static Value lessThanEq(Value param1, Value param2) {
+ if(anyParamIsNull(param1, param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ return toValue(nonNullCompareTo(param1, param2) <= 0);
+ }
+
+ public static Value greaterThanEq(Value param1, Value param2) {
+ if(anyParamIsNull(param1, param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ return toValue(nonNullCompareTo(param1, param2) >= 0);
+ }
+
+ public static Value equals(Value param1, Value param2) {
+ if(anyParamIsNull(param1, param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ return toValue(nonNullCompareTo(param1, param2) == 0);
+ }
+
+ public static Value notEquals(Value param1, Value param2) {
+ if(anyParamIsNull(param1, param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ return toValue(nonNullCompareTo(param1, param2) != 0);
+ }
+
+ public static Value and(Value param1, Value param2) {
+
+ // "and" uses short-circuit logic
+
+ if(paramIsNull(param1)) {
+ return NULL_VAL;
+ }
+
+ boolean b1 = nonNullValueToBoolean(param1);
+ if(!b1) {
+ return FALSE_VAL;
+ }
+
+ if(paramIsNull(param2)) {
+ return NULL_VAL;
+ }
+
+ return toValue(nonNullValueToBoolean(param2));
+ }
+
+ public static Value or(Value param1, Value param2) {
+
+ // "or" uses short-circuit logic
+
+ if(paramIsNull(param1)) {
+ return NULL_VAL;
+ }
+
+ boolean b1 = nonNullValueToBoolean(param1);
+ if(b1) {
+ return TRUE_VAL;
+ }
+
+ if(paramIsNull(param2)) {
+ return NULL_VAL;
+ }
+
+ return toValue(nonNullValueToBoolean(param2));
+ }
+
+ public static Value eqv(Value param1, Value param2) {
+ if(anyParamIsNull(param1, param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ boolean b1 = nonNullValueToBoolean(param1);
+ boolean b2 = nonNullValueToBoolean(param2);
+
+ return toValue(b1 == b2);
+ }
+
+ public static Value xor(Value param1, Value param2) {
+ if(anyParamIsNull(param1, param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ boolean b1 = nonNullValueToBoolean(param1);
+ boolean b2 = nonNullValueToBoolean(param2);
+
+ return toValue(b1 ^ b2);
+ }
+
+ public static Value imp(Value param1, Value param2) {
+
+ // "imp" uses short-circuit logic
+
+ if(paramIsNull(param1)) {
+ if(paramIsNull(param2) || !nonNullValueToBoolean(param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ return TRUE_VAL;
+ }
+
+ boolean b1 = nonNullValueToBoolean(param1);
+ if(!b1) {
+ return TRUE_VAL;
+ }
+
+ if(paramIsNull(param2)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ return toValue(nonNullValueToBoolean(param2));
+ }
+
+ public static Value isNull(Value param1) {
+ return toValue(param1.getType() == ValueType.NULL);
+ }
+
+ public static Value isNotNull(Value param1) {
+ return toValue(param1.getType() == ValueType.NULL);
+ }
+
+ public static Value like(Value param1, Pattern pattern) {
+ // FIXME
+ return null;
+ }
+
+ public static Value between(Value param1, Value param2, Value param3) {
+
+ // null propagate any field left to right. uses short circuit eval
+ if(anyParamIsNull(param1, param2, param3)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ // FIXME
+ return null;
+ }
+
+ public static Value notBetween(Value param1, Value param2, Value param3) {
+ return not(between(param1, param2, param3));
+ }
+
+ public static Value in(Value param1, Value[] params) {
+
+ // null propagate any param. uses short circuit eval of params
+ if(paramIsNull(param1)) {
+ // null propagation
+ return NULL_VAL;
+ }
+
+ for(Value val : params) {
+ if(paramIsNull(val)) {
+ continue;
+ }
+
+ // FIXME test
+ }
+
+ // FIXME
+ return null;
+ }
+
+ public static Value notIn(Value param1, Value[] params) {
+ return not(in(param1, params));
+ }
+
+
+ private static boolean anyParamIsNull(Value param1, Value param2) {
+ return (paramIsNull(param1) || paramIsNull(param2));
+ }
+
+ private static boolean anyParamIsNull(Value param1, Value param2,
+ Value param3) {
+ return (paramIsNull(param1) || paramIsNull(param2) || paramIsNull(param3));
+ }
+
+ private static boolean paramIsNull(Value param1) {
+ return (param1.getType() == ValueType.NULL);
+ }
+
+ protected static CharSequence paramToString(Object param) {
+ try {
+ return ColumnImpl.toCharSequence(param);
+ } catch(IOException e) {
+ throw new RuntimeIOException(e);
+ }
+ }
+
+ protected static boolean paramToBoolean(Object param) {
+ // FIXME, null is false...?
+ return ColumnImpl.toBooleanValue(param);
+ }
+
+ protected static Number paramToNumber(Object param) {
+ // FIXME
+ return null;
+ }
+
+ protected static boolean nonNullValueToBoolean(Value val) {
+ switch(val.getType()) {
+ case BOOLEAN:
+ return (Boolean)val.get();
+ case STRING:
+ case DATE:
+ case TIME:
+ case DATE_TIME:
+ // strings and dates are always true
+ return true;
+ case LONG:
+ return (((Number)val.get()).longValue() != 0L);
+ case DOUBLE:
+ return (((Number)val.get()).doubleValue() != 0.0d);
+ case BIG_INT:
+ return (((BigInteger)val.get()).compareTo(BigInteger.ZERO) != 0L);
+ case BIG_DEC:
+ return (((BigDecimal)val.get()).compareTo(BigDecimal.ZERO) != 0L);
+ default:
+ throw new RuntimeException("Unexpected type " + val.getType());
+ }
+ }
+
+ protected static int nonNullCompareTo(
+ Value param1, Value param2)
+ {
+ // FIXME
+ return 0;
+ }
+
+ public static Value toValue(boolean b) {
+ return (b ? TRUE_VAL : FALSE_VAL);
+ }
+
+ public static Value toValue(Object obj) {
+ if(obj == null) {
+ return NULL_VAL;
+ }
+
+ if(obj instanceof Value) {
+ return (Value)obj;
+ }
+
+ if(obj instanceof Boolean) {
+ return (((Boolean)obj) ? TRUE_VAL : FALSE_VAL);
+ }
+
+ if(obj instanceof Date) {
+ // any way to figure out whether it's a date/time/dateTime?
+ return new SimpleValue(ValueType.DATE_TIME, obj);
+ }
+
+ if(obj instanceof Number) {
+ if((obj instanceof Double) || (obj instanceof Float)) {
+ return new SimpleValue(ValueType.DOUBLE, obj);
+ }
+ if(obj instanceof BigDecimal) {
+ return new SimpleValue(ValueType.BIG_DEC, obj);
+ }
+ if(obj instanceof BigInteger) {
+ return new SimpleValue(ValueType.BIG_INT, obj);
+ }
+ return new SimpleValue(ValueType.LONG, obj);
+ }
+
+ try {
+ return new SimpleValue(ValueType.STRING,
+ ColumnImpl.toCharSequence(obj).toString());
+ } catch(IOException e) {
+ throw new RuntimeIOException(e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/DefaultFunctions.java b/src/main/java/com/healthmarketscience/jackcess/util/DefaultFunctions.java
new file mode 100644
index 0000000..3c9c292
--- /dev/null
+++ b/src/main/java/com/healthmarketscience/jackcess/util/DefaultFunctions.java
@@ -0,0 +1,152 @@
+/*
+Copyright (c) 2016 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.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+import com.healthmarketscience.jackcess.util.Expression.*;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class DefaultFunctions
+{
+ private static final Map<String,Function> FUNCS =
+ new HashMap<String,Function>();
+
+ private DefaultFunctions() {}
+
+ public static Function getFunction(String name) {
+ return FUNCS.get(name.toLowerCase());
+ }
+
+ 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;
+ }
+
+ protected void validateNumParams(Value[] params) {
+ int num = params.length;
+ if((num < _minParams) || (num > _maxParams)) {
+ String range = ((_minParams == _maxParams) ? "" + _minParams :
+ _minParams + " to " + _maxParams);
+ throw new IllegalArgumentException(
+ this + ": invalid number of parameters " +
+ num + " passed, expected " + range);
+ }
+ }
+
+ protected static CharSequence paramToString(Object param)
+ {
+ return BuiltinOperators.paramToString(param);
+ }
+
+ protected static boolean paramToBoolean(Object param)
+ {
+ return BuiltinOperators.paramToBoolean(param);
+ }
+
+ protected static Number paramToNumber(Object param)
+ {
+ return BuiltinOperators.paramToNumber(param);
+ }
+
+ @Override
+ public String toString() {
+ return getName() + "()";
+ }
+ }
+
+ public static abstract class Func1 extends BaseFunction
+ {
+ protected Func1(String name) {
+ super(name, 1, 1);
+ }
+
+ public final Value eval(Value... params) {
+ validateNumParams(params);
+ return eval1(params[0]);
+ }
+
+ protected abstract Value eval1(Value param);
+ }
+
+ public static abstract class Func2 extends BaseFunction
+ {
+ protected Func2(String name) {
+ super(name, 2, 2);
+ }
+
+ public final Value eval(Value... params) {
+ validateNumParams(params);
+ return eval2(params[0], params[1]);
+ }
+
+ protected abstract Value eval2(Value param1, Value param2);
+ }
+
+ public static abstract class Func3 extends BaseFunction
+ {
+ protected Func3(String name) {
+ super(name, 3, 3);
+ }
+
+ public final Value eval(Value... params) {
+ validateNumParams(params);
+ return eval3(params[0], params[1], params[2]);
+ }
+
+ protected abstract Value eval3(Value param1, Value param2, Value param3);
+ }
+
+ public static final Function IIF = new Func3("IIf") {
+ @Override
+ protected Value eval3(Value param1, Value param2, Value param3) {
+ // FIXME
+ // return (paramToBoolean(param1) ? param2 : param3);
+ return null;
+ }
+ };
+
+ // https://www.techonthenet.com/access/functions/
+ // https://support.office.com/en-us/article/Access-Functions-by-category-b8b136c3-2716-4d39-94a2-658ce330ed83
+
+ private static void registerFunc(Function func) {
+ if(FUNCS.put(func.getName().toLowerCase(), func) != null) {
+ throw new IllegalStateException("Duplicate function " + func);
+ }
+ }
+
+ static {
+ registerFunc(IIF);
+ }
+}
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/Expression.java b/src/main/java/com/healthmarketscience/jackcess/util/Expression.java
new file mode 100644
index 0000000..f2bb462
--- /dev/null
+++ b/src/main/java/com/healthmarketscience/jackcess/util/Expression.java
@@ -0,0 +1,68 @@
+/*
+Copyright (c) 2016 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.util;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public interface Expression
+{
+ public interface RowContext
+ {
+ public Value getThisColumnValue();
+
+ public Value getRowValue(String collectionName, String objName,
+ String colName);
+ }
+
+ public enum ValueType
+ {
+ NULL, BOOLEAN, STRING, DATE, TIME, DATE_TIME, LONG, DOUBLE, BIG_INT, BIG_DEC;
+
+ public boolean isNumeric() {
+ return inRange(LONG, BIG_DEC);
+ }
+
+ public boolean isTemporal() {
+ return inRange(DATE, DATE_TIME);
+ }
+
+ private boolean inRange(ValueType start, ValueType end) {
+ return ((start.ordinal() <= ordinal()) && (ordinal() <= end.ordinal()));
+ }
+ }
+
+ public interface Value
+ {
+ public ValueType getType();
+ public Object get();
+ }
+
+ public interface Function
+ {
+ public String getName();
+ public Value eval(Value... params);
+ }
+
+
+ public Object evalDefault();
+
+ public Boolean evalCondition(RowContext ctx);
+
+ public String toDebugString();
+}
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/ExpressionTokenizer.java b/src/main/java/com/healthmarketscience/jackcess/util/ExpressionTokenizer.java
index b9a3cd4..192a62d 100644
--- a/src/main/java/com/healthmarketscience/jackcess/util/ExpressionTokenizer.java
+++ b/src/main/java/com/healthmarketscience/jackcess/util/ExpressionTokenizer.java
@@ -27,9 +27,9 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
-import com.healthmarketscience.jackcess.DatabaseBuilder;
-import com.healthmarketscience.jackcess.impl.DatabaseImpl;
import static com.healthmarketscience.jackcess.util.Expressionator.*;
+import com.healthmarketscience.jackcess.util.Expression.ValueType;
+
/**
*
@@ -74,7 +74,8 @@ class ExpressionTokenizer
* Tokenizes an expression string of the given type and (optionally) in the
* context of the relevant database.
*/
- static List<Token> tokenize(Type exprType, String exprStr, DatabaseImpl db) {
+ static List<Token> tokenize(Type exprType, String exprStr,
+ ParseContext context) {
if(exprStr != null) {
exprStr = exprStr.trim();
@@ -99,10 +100,9 @@ class ExpressionTokenizer
case IS_OP_FLAG:
// special case '-' for negative number
- Map.Entry<?,String> numLit = maybeParseNumberLiteral(c, buf);
+ Token numLit = maybeParseNumberLiteral(c, buf);
if(numLit != null) {
- tokens.add(new Token(TokenType.LITERAL, numLit.getKey(),
- numLit.getValue()));
+ tokens.add(numLit);
continue;
}
@@ -152,12 +152,11 @@ class ExpressionTokenizer
switch(c) {
case QUOTED_STR_CHAR:
- tokens.add(new Token(TokenType.LITERAL, parseQuotedString(buf)));
+ tokens.add(new Token(TokenType.LITERAL, null, parseQuotedString(buf),
+ ValueType.STRING));
break;
case DATE_LIT_QUOTE_CHAR:
- Map.Entry<?,String> dateLit = parseDateLiteralString(buf, db);
- tokens.add(new Token(TokenType.LITERAL, dateLit.getKey(),
- dateLit.getValue()));
+ tokens.add(parseDateLiteralString(buf, context));
break;
case OBJ_NAME_START_CHAR:
tokens.add(new Token(TokenType.OBJ_NAME, parseObjNameString(buf)));
@@ -176,10 +175,9 @@ class ExpressionTokenizer
} else {
if(isDigit(c)) {
- Map.Entry<?,String> numLit = maybeParseNumberLiteral(c, buf);
+ Token numLit = maybeParseNumberLiteral(c, buf);
if(numLit != null) {
- tokens.add(new Token(TokenType.LITERAL, numLit.getKey(),
- numLit.getValue()));
+ tokens.add(numLit);
continue;
}
}
@@ -309,8 +307,8 @@ class ExpressionTokenizer
return sb.toString();
}
- private static Map.Entry<?,String> parseDateLiteralString(
- ExprBuf buf, DatabaseImpl db)
+ private static Token parseDateLiteralString(
+ ExprBuf buf, ParseContext context)
{
String dateStr = parseStringUntil(buf, DATE_LIT_QUOTE_CHAR, null);
@@ -318,27 +316,30 @@ class ExpressionTokenizer
boolean hasTime = (dateStr.indexOf(':') >= 0);
SimpleDateFormat sdf = null;
+ ValueType valType = null;
if(hasDate && hasTime) {
- sdf = buf.getDateTimeFormat(db);
+ sdf = buf.getDateTimeFormat(context);
+ valType = ValueType.DATE_TIME;
} else if(hasDate) {
- sdf = buf.getDateFormat(db);
+ sdf = buf.getDateFormat(context);
+ valType = ValueType.DATE;
} else if(hasTime) {
- sdf = buf.getTimeFormat(db);
+ sdf = buf.getTimeFormat(context);
+ valType = ValueType.TIME;
} else {
throw new IllegalArgumentException("Invalid date time literal " + dateStr +
" " + buf);
}
- // FIXME, do we need to know which "type" it was?
try {
- return newEntry(sdf.parse(dateStr), dateStr);
+ return new Token(TokenType.LITERAL, sdf.parse(dateStr), dateStr, valType);
} catch(ParseException pe) {
throw new IllegalArgumentException(
"Invalid date time literal " + dateStr + " " + buf, pe);
}
}
- private static Map.Entry<?,String> maybeParseNumberLiteral(char firstChar, ExprBuf buf) {
+ private static Token maybeParseNumberLiteral(char firstChar, ExprBuf buf) {
StringBuilder sb = buf.getScratchBuffer().append(firstChar);
boolean hasDigit = isDigit(firstChar);
@@ -374,7 +375,7 @@ class ExpressionTokenizer
// what number type to use here?
BigDecimal num = new BigDecimal(numStr);
foundNum = true;
- return newEntry(num, numStr);
+ return new Token(TokenType.LITERAL, num, numStr, ValueType.BIG_DEC);
} catch(NumberFormatException ne) {
throw new IllegalArgumentException(
"Invalid number literal " + numStr + " " + buf, ne);
@@ -458,32 +459,27 @@ class ExpressionTokenizer
return _scratch;
}
- public SimpleDateFormat getDateFormat(DatabaseImpl db) {
+ public SimpleDateFormat getDateFormat(ParseContext context) {
if(_dateFmt == null) {
- _dateFmt = newFormat(DATE_FORMAT, db);
+ _dateFmt = context.createDateFormat(DATE_FORMAT);
}
return _dateFmt;
}
- public SimpleDateFormat getTimeFormat(DatabaseImpl db) {
+ public SimpleDateFormat getTimeFormat(ParseContext context) {
if(_timeFmt == null) {
- _timeFmt = newFormat(TIME_FORMAT, db);
+ _timeFmt = context.createDateFormat(TIME_FORMAT);
}
return _timeFmt;
}
- public SimpleDateFormat getDateTimeFormat(DatabaseImpl db) {
+ public SimpleDateFormat getDateTimeFormat(ParseContext context) {
if(_dateTimeFmt == null) {
- _dateTimeFmt = newFormat(DATE_TIME_FORMAT, db);
+ _dateTimeFmt = context.createDateFormat(DATE_TIME_FORMAT);
}
return _dateTimeFmt;
}
- private static SimpleDateFormat newFormat(String str, DatabaseImpl db) {
- return ((db != null) ? db.createDateFormat(str) :
- DatabaseBuilder.createDateFormat(str));
- }
-
@Override
public String toString() {
return "[char " + _pos + "] '" + _str + "'";
@@ -496,15 +492,21 @@ class ExpressionTokenizer
private final TokenType _type;
private final Object _val;
private final String _valStr;
+ private final ValueType _valType;
private Token(TokenType type, String val) {
this(type, val, val);
}
private Token(TokenType type, Object val, String valStr) {
+ this(type, val, valStr, null);
+ }
+
+ private Token(TokenType type, Object val, String valStr, ValueType valType) {
_type = type;
- _val = val;
+ _val = ((val != null) ? val : valStr);
_valStr = valStr;
+ _valType = valType;
}
public TokenType getType() {
@@ -519,14 +521,18 @@ class ExpressionTokenizer
return _valStr;
}
+ public ValueType getValueType() {
+ return _valType;
+ }
+
@Override
public String toString() {
if(_type == TokenType.SPACE) {
return "' '";
}
String str = "[" + _type + "] '" + _val + "'";
- if(_type == TokenType.LITERAL) {
- str += " (" + _val.getClass() + ")";
+ if(_valType != null) {
+ str += " (" + _valType + ")";
}
return str;
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/Expressionator.java b/src/main/java/com/healthmarketscience/jackcess/util/Expressionator.java
index 6b5d3fd..286ab8b 100644
--- a/src/main/java/com/healthmarketscience/jackcess/util/Expressionator.java
+++ b/src/main/java/com/healthmarketscience/jackcess/util/Expressionator.java
@@ -16,10 +16,10 @@ limitations under the License.
package com.healthmarketscience.jackcess.util;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.Date;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
@@ -30,10 +30,10 @@ import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
-import com.healthmarketscience.jackcess.Database;
-import com.healthmarketscience.jackcess.impl.DatabaseImpl;
+import com.healthmarketscience.jackcess.DatabaseBuilder;
import static com.healthmarketscience.jackcess.util.ExpressionTokenizer.Token;
import static com.healthmarketscience.jackcess.util.ExpressionTokenizer.TokenType;
+import com.healthmarketscience.jackcess.util.Expression.*;
/**
*
@@ -54,6 +54,20 @@ public class Expressionator
DEFAULT_VALUE, FIELD_VALIDATOR, RECORD_VALIDATOR;
}
+ public interface ParseContext {
+ public SimpleDateFormat createDateFormat(String formatStr);
+ public Function getExpressionFunction(String name);
+ }
+
+ public static final ParseContext DEFAULT_PARSE_CONTEXT = new ParseContext() {
+ public SimpleDateFormat createDateFormat(String formatStr) {
+ return DatabaseBuilder.createDateFormat(formatStr);
+ }
+ public Function getExpressionFunction(String name) {
+ return DefaultFunctions.getFunction(name);
+ }
+ };
+
private enum WordType {
OP, COMP, LOG_OP, CONST, SPEC_OP_PREFIX, DELIM;
}
@@ -64,12 +78,13 @@ public class Expressionator
private static final String CLOSE_PAREN = ")";
private static final String FUNC_PARAM_SEP = ",";
- private static final Map<String,WordType> WORD_TYPES = new HashMap<String,WordType>();
+ private static final Map<String,WordType> WORD_TYPES =
+ new HashMap<String,WordType>();
static {
setWordType(WordType.OP, "+", "-", "*", "/", "\\", "^", "&", "mod");
setWordType(WordType.COMP, "<", "<=", ">", ">=", "=", "<>");
- setWordType(WordType.LOG_OP, "and", "or", "eqv", "xor");
+ setWordType(WordType.LOG_OP, "and", "or", "eqv", "xor", "imp");
setWordType(WordType.CONST, "true", "false", "null");
setWordType(WordType.SPEC_OP_PREFIX, "is", "like", "between", "in", "not");
// "X is null", "X is not null", "X like P", "X between A and B",
@@ -81,7 +96,16 @@ public class Expressionator
private interface OpType {}
private enum UnaryOp implements OpType {
- NEG("-", false), NOT("Not", true);
+ NEG("-", false) {
+ @Override public Value eval(Value param1) {
+ return BuiltinOperators.negate(param1);
+ }
+ },
+ NOT("Not", true) {
+ @Override public Value eval(Value param1) {
+ return BuiltinOperators.not(param1);
+ }
+ };
private final String _str;
private final boolean _needSpace;
@@ -99,11 +123,51 @@ public class Expressionator
public String toString() {
return _str;
}
+
+ public abstract Value eval(Value param1);
}
private enum BinaryOp implements OpType {
- PLUS("+"), MINUS("-"), MULT("*"), DIV("/"), INT_DIV("\\"), EXP("^"),
- CONCAT("&"), MOD("Mod");
+ PLUS("+") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.add(param1, param2);
+ }
+ },
+ MINUS("-") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.subtract(param1, param2);
+ }
+ },
+ MULT("*") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.multiply(param1, param2);
+ }
+ },
+ DIV("/") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.divide(param1, param2);
+ }
+ },
+ INT_DIV("\\") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.intDivide(param1, param2);
+ }
+ },
+ EXP("^") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.exp(param1, param2);
+ }
+ },
+ CONCAT("&") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.concat(param1, param2);
+ }
+ },
+ MOD("Mod") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.mod(param1, param2);
+ }
+ };
private final String _str;
@@ -115,10 +179,41 @@ public class Expressionator
public String toString() {
return _str;
}
+
+ public abstract Value eval(Value param1, Value param2);
}
private enum CompOp implements OpType {
- LT("<"), LTE("<="), GT(">"), GTE(">="), EQ("="), NE("<>");
+ LT("<") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.lessThan(param1, param2);
+ }
+ },
+ LTE("<=") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.lessThanEq(param1, param2);
+ }
+ },
+ GT(">") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.greaterThan(param1, param2);
+ }
+ },
+ GTE(">=") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.greaterThanEq(param1, param2);
+ }
+ },
+ EQ("=") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.equals(param1, param2);
+ }
+ },
+ NE("<>") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.notEquals(param1, param2);
+ }
+ };
private final String _str;
@@ -130,10 +225,36 @@ public class Expressionator
public String toString() {
return _str;
}
+
+ public abstract Value eval(Value param1, Value param2);
}
private enum LogOp implements OpType {
- AND("And"), OR("Or"), EQV("Eqv"), XOR("Xor");
+ AND("And") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.and(param1, param2);
+ }
+ },
+ OR("Or") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.or(param1, param2);
+ }
+ },
+ EQV("Eqv") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.eqv(param1, param2);
+ }
+ },
+ XOR("Xor") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.xor(param1, param2);
+ }
+ },
+ IMP("Imp") {
+ @Override public Value eval(Value param1, Value param2) {
+ return BuiltinOperators.imp(param1, param2);
+ }
+ };
private final String _str;
@@ -145,14 +266,53 @@ public class Expressionator
public String toString() {
return _str;
}
+
+ public abstract Value eval(Value param1, Value param2);
}
private enum SpecOp implements OpType {
// note, "NOT" is not actually used as a special operation, always
// replaced with UnaryOp.NOT
- NOT("Not"), IS_NULL("Is Null"), IS_NOT_NULL("Is Not Null"), LIKE("Like"),
- BETWEEN("Between"), NOT_BETWEEN("Not Between"), IN("In"),
- NOT_IN("Not In");
+ NOT("Not") {
+ @Override public Value eval(Value param1, Object param2, Object param3) {
+ throw new UnsupportedOperationException();
+ }
+ },
+ IS_NULL("Is Null") {
+ @Override public Value eval(Value param1, Object param2, Object param3) {
+ return BuiltinOperators.isNull(param1);
+ }
+ },
+ IS_NOT_NULL("Is Not Null") {
+ @Override public Value eval(Value param1, Object param2, Object param3) {
+ return BuiltinOperators.isNotNull(param1);
+ }
+ },
+ LIKE("Like") {
+ @Override public Value eval(Value param1, Object param2, Object param3) {
+ return BuiltinOperators.like(param1, (Pattern)param2);
+ }
+ },
+ BETWEEN("Between") {
+ @Override public Value eval(Value param1, Object param2, Object param3) {
+ return BuiltinOperators.between(param1, (Value)param2, (Value)param3);
+ }
+ },
+ NOT_BETWEEN("Not Between") {
+ @Override public Value eval(Value param1, Object param2, Object param3) {
+ return BuiltinOperators.notBetween(param1, (Value)param2, (Value)param3);
+ }
+ },
+ IN("In") {
+ @Override public Value eval(Value param1, Object param2, Object param3) {
+ return BuiltinOperators.in(param1, (Value[])param2);
+ }
+ },
+ NOT_IN("Not In") {
+ @Override public Value eval(Value param1, Object param2, Object param3) {
+ return BuiltinOperators.notIn(param1, (Value[])param2);
+ }
+ };
private final String _str;
@@ -164,6 +324,8 @@ public class Expressionator
public String toString() {
return _str;
}
+
+ public abstract Value eval(Value param1, Object param2, Object param3);
}
private static final Map<OpType, Integer> PRECENDENCE =
@@ -182,6 +344,7 @@ public class Expressionator
new OpType[]{LogOp.OR},
new OpType[]{LogOp.XOR},
new OpType[]{LogOp.EQV},
+ new OpType[]{LogOp.IMP},
new OpType[]{SpecOp.IN, SpecOp.NOT_IN, SpecOp.BETWEEN,
SpecOp.NOT_BETWEEN});
@@ -190,7 +353,7 @@ public class Expressionator
private static final Expr THIS_COL_VALUE = new Expr() {
- @Override protected Object eval(RowContext ctx) {
+ @Override protected Value eval(RowContext ctx) {
return ctx.getThisColumnValue();
}
@Override protected void toExprString(StringBuilder sb, boolean isDebug) {
@@ -198,18 +361,25 @@ public class Expressionator
}
};
- private static final Expr NULL_VALUE = new EConstValue(null, "Null");
- private static final Expr TRUE_VALUE = new EConstValue(Boolean.TRUE, "True");
- private static final Expr FALSE_VALUE = new EConstValue(Boolean.FALSE, "False");
+ private static final Expr NULL_VALUE = new EConstValue(
+ BuiltinOperators.NULL_VAL, "Null");
+ private static final Expr TRUE_VALUE = new EConstValue(
+ BuiltinOperators.TRUE_VAL, "True");
+ private static final Expr FALSE_VALUE = new EConstValue(
+ BuiltinOperators.FALSE_VAL, "False");
private Expressionator()
{
}
- static String testTokenize(Type exprType, String exprStr, Database db) {
+ static String testTokenize(Type exprType, String exprStr,
+ ParseContext context) {
+ if(context == null) {
+ context = DEFAULT_PARSE_CONTEXT;
+ }
List<Token> tokens = trimSpaces(
- ExpressionTokenizer.tokenize(exprType, exprStr, (DatabaseImpl)db));
+ ExpressionTokenizer.tokenize(exprType, exprStr, context));
if(tokens == null) {
// FIXME, NULL_EXPR?
@@ -219,7 +389,12 @@ public class Expressionator
return tokens.toString();
}
- public static Expr parse(Type exprType, String exprStr, Database db) {
+ public static Expression parse(Type exprType, String exprStr,
+ ParseContext context) {
+
+ if(context == null) {
+ context = DEFAULT_PARSE_CONTEXT;
+ }
// FIXME,restrictions:
// - default value only accepts simple exprs, otherwise becomes literal text
@@ -228,14 +403,14 @@ public class Expressionator
// - record validation cannot refer to outside columns
List<Token> tokens = trimSpaces(
- ExpressionTokenizer.tokenize(exprType, exprStr, (DatabaseImpl)db));
+ ExpressionTokenizer.tokenize(exprType, exprStr, context));
if(tokens == null) {
// FIXME, NULL_EXPR?
return null;
}
- return parseExpression(new TokBuf(exprType, tokens), false);
+ return parseExpression(new TokBuf(exprType, tokens, context), false);
}
private static List<Token> trimSpaces(List<Token> tokens) {
@@ -274,7 +449,7 @@ public class Expressionator
case LITERAL:
- buf.setPendingExpr(new ELiteralValue(t.getValue()));
+ buf.setPendingExpr(new ELiteralValue(t.getValueType(), t.getValue()));
break;
case OP:
@@ -456,8 +631,13 @@ public class Expressionator
buf.next();
List<Expr> params = findParenExprs(buf, true);
- buf.setPendingExpr(
- new EFunc(firstTok.getValueStr(), params));
+ String funcName = firstTok.getValueStr();
+ Function func = buf.getFunction(funcName);
+ if(func == null) {
+ throw new IllegalArgumentException("Could not find function '" +
+ funcName + "' " + buf);
+ }
+ buf.setPendingExpr(new EFunc(func, params));
foundFunc = true;
return true;
@@ -602,7 +782,6 @@ public class Expressionator
Expr expr = buf.takePendingExpr();
- // FIXME
Expr specOpExpr = null;
switch(specOp) {
case IS_NULL:
@@ -774,24 +953,27 @@ public class Expressionator
private final List<Token> _tokens;
private final TokBuf _parent;
private final int _parentOff;
+ private final ParseContext _context;
private int _pos;
private Expr _pendingExpr;
private final boolean _simpleExpr;
- private TokBuf(Type exprType, List<Token> tokens) {
- this(exprType, false, tokens, null, 0);
+ private TokBuf(Type exprType, List<Token> tokens, ParseContext context) {
+ this(exprType, false, tokens, null, 0, context);
}
private TokBuf(List<Token> tokens, TokBuf parent, int parentOff) {
- this(parent._exprType, parent._simpleExpr, tokens, parent, parentOff);
+ this(parent._exprType, parent._simpleExpr, tokens, parent, parentOff,
+ parent._context);
}
private TokBuf(Type exprType, boolean simpleExpr, List<Token> tokens,
- TokBuf parent, int parentOff) {
+ TokBuf parent, int parentOff, ParseContext context) {
_exprType = exprType;
_tokens = tokens;
_parent = parent;
_parentOff = parentOff;
+ _context = context;
if(parent == null) {
// "top-level" expression, determine if it is a simple expression or not
simpleExpr = isSimpleExpression();
@@ -897,6 +1079,10 @@ public class Expressionator
return ExpressionTokenizer.newEntry(pos, toks);
}
+ public Function getFunction(String funcName) {
+ return _context.getExpressionFunction(funcName);
+ }
+
@Override
public String toString() {
@@ -958,6 +1144,30 @@ public class Expressionator
}
}
+ private static Value[] exprListToValues(
+ List<Expr> exprs, RowContext ctx) {
+ Value[] paramVals = new Value[exprs.size()];
+ for(int i = 0; i < exprs.size(); ++i) {
+ paramVals[i] = exprs.get(i).eval(ctx);
+ }
+ return paramVals;
+ }
+
+ private static Value[] exprListToDelayedValues(
+ List<Expr> exprs, RowContext ctx) {
+ Value[] paramVals = new Value[exprs.size()];
+ for(int i = 0; i < exprs.size(); ++i) {
+ paramVals[i] = new DelayedValue(exprs.get(i), ctx);
+ }
+ return paramVals;
+ }
+
+ private static void literalStrToString(String str, StringBuilder sb) {
+ sb.append("\"")
+ .append(str.replace("\"", "\"\""))
+ .append("\"");
+ }
+
private static Pattern likePatternToRegex(String pattern, Object location) {
StringBuilder sb = new StringBuilder(pattern.length());
@@ -1017,6 +1227,7 @@ public class Expressionator
Pattern.UNICODE_CASE);
}
+
private interface LeftAssocExpr {
public OpType getOp();
public Expr getLeft();
@@ -1029,22 +1240,63 @@ public class Expressionator
public void setRight(Expr right);
}
- public static abstract class Expr
+ private static final class DelayedValue implements Value
+ {
+ private Value _val;
+ private final Expr _expr;
+ private final RowContext _ctx;
+
+ private DelayedValue(Expr expr, RowContext ctx) {
+ _expr = expr;
+ _ctx = ctx;
+ }
+
+ private Value getDelegate() {
+ if(_val == null) {
+ _val = _expr.eval(_ctx);
+ }
+ return _val;
+ }
+
+ public ValueType getType() {
+ return getDelegate().getType();
+ }
+
+ public Object get() {
+ return getDelegate().get();
+ }
+ }
+
+
+ private static abstract class Expr implements Expression
{
public Object evalDefault() {
- return eval(null);
+ Value val = eval(null);
+
+ if(val.getType() == ValueType.NULL) {
+ return null;
+ }
+
+ // FIXME, booleans seem to go to -1 (true),0 (false) ...?
+
+ return val.get();
}
- public boolean evalCondition(RowContext ctx) {
- Object val = eval(ctx);
+ public Boolean evalCondition(RowContext ctx) {
+ Value val = eval(ctx);
+
+ if(val.getType() == ValueType.NULL) {
+ return null;
+ }
- if(val instanceof Boolean) {
- return (Boolean)val;
+ if(val.getType() != ValueType.BOOLEAN) {
+ // a single value as a conditional expression seems to act like an
+ // implicit "="
+ // FIXME, what about row validators?
+ val = BuiltinOperators.equals(val, ctx.getThisColumnValue());
}
- // a single value as a conditional expression seems to act like an
- // implicit "="
- return val.equals(ctx.getThisColumnValue());
+ return (Boolean)val.get();
}
@Override
@@ -1126,31 +1378,23 @@ public class Expressionator
return outerExpr;
}
- protected abstract Object eval(RowContext ctx);
+ protected abstract Value eval(RowContext ctx);
protected abstract void toExprString(StringBuilder sb, boolean isDebug);
}
- public interface RowContext
- {
- public Object getThisColumnValue();
-
- public Object getRowValue(String collectionName, String objName,
- String colName);
- }
-
private static final class EConstValue extends Expr
{
- private final Object _val;
+ private final Value _val;
private final String _str;
- private EConstValue(Object val, String str) {
+ private EConstValue(Value val, String str) {
_val = val;
_str = str;
}
@Override
- protected Object eval(RowContext ctx) {
+ protected Value eval(RowContext ctx) {
return _val;
}
@@ -1162,28 +1406,28 @@ public class Expressionator
private static final class ELiteralValue extends Expr
{
- private final Object _value;
+ private final Value _val;
- private ELiteralValue(Object value) {
- _value = value;
+ private ELiteralValue(ValueType valType, Object value) {
+ _val = new BuiltinOperators.SimpleValue(valType, value);
}
@Override
- public Object eval(RowContext ctx) {
- return _value;
+ public Value eval(RowContext ctx) {
+ return _val;
}
@Override
protected void toExprString(StringBuilder sb, boolean isDebug) {
- // FIXME, stronger typing?
- if(_value instanceof String) {
- sb.append("\"").append(_value).append("\"");
- } else if(_value instanceof Date) {
- // FIXME Date,Time,DateTime formatting?
- sb.append("#").append(_value).append("#");
+ if(_val.getType() == ValueType.STRING) {
+ literalStrToString((String)_val.get(), sb);
+ } else if(_val.getType().isTemporal()) {
+ // // FIXME Date,Time,DateTime formatting?
+ // sb.append("#").append(_value).append("#");
+ throw new UnsupportedOperationException();
} else {
- sb.append(_value);
- }
+ sb.append(_val.get());
+ }
}
}
@@ -1201,7 +1445,7 @@ public class Expressionator
}
@Override
- public Object eval(RowContext ctx) {
+ public Value eval(RowContext ctx) {
return ctx.getRowValue(_collectionName, _objName, _fieldName);
}
@@ -1217,16 +1461,6 @@ public class Expressionator
}
}
- private static abstract class EOp
- {
-
- }
-
- private static abstract class ECond
- {
-
- }
-
private static class EParen extends Expr
{
private final Expr _expr;
@@ -1236,7 +1470,7 @@ public class Expressionator
}
@Override
- protected Object eval(RowContext ctx) {
+ protected Value eval(RowContext ctx) {
return _expr.eval(ctx);
}
@@ -1250,24 +1484,22 @@ public class Expressionator
private static class EFunc extends Expr
{
- private final String _name;
+ private final Function _func;
private final List<Expr> _params;
- private EFunc(String name, List<Expr> params) {
- _name = name;
+ private EFunc(Function func, List<Expr> params) {
+ _func = func;
_params = params;
}
@Override
- protected Object eval(RowContext ctx) {
- // FIXME how do func results act for conditional values? (literals become = tests)
-
- return false;
+ protected Value eval(RowContext ctx) {
+ return _func.eval(exprListToValues(_params, ctx));
}
@Override
protected void toExprString(StringBuilder sb, boolean isDebug) {
- sb.append(_name).append("(");
+ sb.append(_func.getName()).append("(");
if(!_params.isEmpty()) {
exprListToString(_params, ",", sb, isDebug);
@@ -1320,16 +1552,13 @@ public class Expressionator
private static class EBinaryOp extends EBaseBinaryOp
{
-
private EBinaryOp(BinaryOp op, Expr left, Expr right) {
super(op, left, right);
}
@Override
- protected Object eval(RowContext ctx) {
- // FIXME
-
- return null;
+ protected Value eval(RowContext ctx) {
+ return ((BinaryOp)_op).eval(_left.eval(ctx), _right.eval(ctx));
}
}
@@ -1357,10 +1586,8 @@ public class Expressionator
}
@Override
- protected Object eval(RowContext ctx) {
- // FIXME
-
- return null;
+ protected Value eval(RowContext ctx) {
+ return ((UnaryOp)_op).eval(_expr.eval(ctx));
}
@Override
@@ -1380,10 +1607,8 @@ public class Expressionator
}
@Override
- protected Object eval(RowContext ctx) {
- // FIXME
-
- return null;
+ protected Value eval(RowContext ctx) {
+ return ((CompOp)_op).eval(_left.eval(ctx), _right.eval(ctx));
}
}
@@ -1394,10 +1619,12 @@ public class Expressionator
}
@Override
- protected Object eval(RowContext ctx) {
- // FIXME
-
- return null;
+ protected Value eval(final RowContext ctx) {
+
+ // logical operations do short circuit evaluation, so we need to delay
+ // computing results until necessary
+ return ((LogOp)_op).eval(new DelayedValue(_left, ctx),
+ new DelayedValue(_right, ctx));
}
}
@@ -1432,10 +1659,8 @@ public class Expressionator
}
@Override
- protected Object eval(RowContext ctx) {
- // FIXME
-
- return null;
+ protected Value eval(RowContext ctx) {
+ return _op.eval(_expr.eval(ctx), null, null);
}
@Override
@@ -1447,6 +1672,7 @@ public class Expressionator
private static class ELikeOp extends ESpecOp
{
+ // FIXME, compile Pattern on first use?
private final Pattern _pattern;
private final String _patternStr;
@@ -1457,18 +1683,15 @@ public class Expressionator
}
@Override
- protected Object eval(RowContext ctx) {
- // FIXME
-
- return null;
+ protected Value eval(RowContext ctx) {
+ return _op.eval(_expr.eval(ctx), _pattern, null);
}
@Override
protected void toExprString(StringBuilder sb, boolean isDebug) {
_expr.toString(sb, isDebug);
- sb.append(" ").append(_op).append(" \"")
- .append(_patternStr.replace("\"", "\"\""))
- .append("\"");
+ sb.append(" ").append(_op).append(" ");
+ literalStrToString(_patternStr, sb);
if(isDebug) {
sb.append("(").append(_pattern).append(")");
}
@@ -1485,10 +1708,9 @@ public class Expressionator
}
@Override
- protected Object eval(RowContext ctx) {
- // FIXME
-
- return null;
+ protected Value eval(RowContext ctx) {
+ return _op.eval(_expr.eval(ctx),
+ exprListToDelayedValues(_exprs, ctx), null);
}
@Override
@@ -1522,10 +1744,10 @@ public class Expressionator
}
@Override
- protected Object eval(RowContext ctx) {
- // FIXME
-
- return null;
+ protected Value eval(RowContext ctx) {
+ return _op.eval(_expr.eval(ctx),
+ new DelayedValue(_startRangeExpr, ctx),
+ new DelayedValue(_endRangeExpr, ctx));
}
@Override
diff --git a/src/test/java/com/healthmarketscience/jackcess/util/ExpressionatorTest.java b/src/test/java/com/healthmarketscience/jackcess/util/ExpressionatorTest.java
index 039fb8a..8da921f 100644
--- a/src/test/java/com/healthmarketscience/jackcess/util/ExpressionatorTest.java
+++ b/src/test/java/com/healthmarketscience/jackcess/util/ExpressionatorTest.java
@@ -40,7 +40,7 @@ public class ExpressionatorTest extends TestCase
doTestSimpleBinOp("EBinaryOp", "+", "-", "*", "/", "\\", "^", "&", "Mod");
doTestSimpleBinOp("ECompOp", "<", "<=", ">", ">=", "=", "<>");
- doTestSimpleBinOp("ELogicalOp", "And", "Or", "Eqv", "Xor");
+ doTestSimpleBinOp("ELogicalOp", "And", "Or", "Eqv", "Xor", "Imp");
for(String constStr : new String[]{"True", "False", "Null"}) {
validateExpr(constStr, "<EConstValue>{" + constStr + "}");
@@ -120,7 +120,7 @@ public class ExpressionatorTest extends TestCase
private static void validateExpr(String exprStr, String debugStr,
String cleanStr) {
- Expressionator.Expr expr = Expressionator.parse(
+ Expression expr = Expressionator.parse(
Expressionator.Type.FIELD_VALIDATOR, exprStr, null);
assertEquals(debugStr, expr.toDebugString());
assertEquals(cleanStr, expr.toString());