Browse Source

rework public expression api with better locale handling; support parsing of number strings with grouping separators

git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1203 f203690c-595d-4dc9-a70b-905162fa7fd2
tags/jackcess-2.2.1
James Ahlborn 5 years ago
parent
commit
443c9fef2b
31 changed files with 730 additions and 735 deletions
  1. 12
    0
      src/main/java/com/healthmarketscience/jackcess/expr/EvalConfig.java
  2. 10
    4
      src/main/java/com/healthmarketscience/jackcess/expr/Expression.java
  3. 12
    0
      src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java
  4. 45
    0
      src/main/java/com/healthmarketscience/jackcess/expr/NumericConfig.java
  5. 6
    6
      src/main/java/com/healthmarketscience/jackcess/expr/Value.java
  6. 2
    0
      src/main/java/com/healthmarketscience/jackcess/expr/package-info.java
  7. 19
    4
      src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java
  8. 19
    0
      src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java
  9. 7
    7
      src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java
  10. 0
    83
      src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java
  11. 12
    12
      src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java
  12. 6
    9
      src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java
  13. 9
    9
      src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java
  14. 4
    3
      src/main/java/com/healthmarketscience/jackcess/impl/expr/BigDecimalValue.java
  15. 115
    107
      src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java
  16. 53
    6
      src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java
  17. 0
    36
      src/main/java/com/healthmarketscience/jackcess/impl/expr/DateValue.java
  18. 43
    58
      src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java
  19. 44
    44
      src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFinancialFunctions.java
  20. 17
    17
      src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java
  21. 19
    19
      src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java
  22. 36
    35
      src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java
  23. 5
    4
      src/main/java/com/healthmarketscience/jackcess/impl/expr/DoubleValue.java
  24. 30
    47
      src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java
  25. 153
    128
      src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java
  26. 1
    1
      src/main/java/com/healthmarketscience/jackcess/impl/expr/FunctionSupport.java
  27. 6
    4
      src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java
  28. 18
    11
      src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java
  29. 0
    37
      src/main/java/com/healthmarketscience/jackcess/impl/expr/TimeValue.java
  30. 7
    36
      src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java
  31. 20
    8
      src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java

+ 12
- 0
src/main/java/com/healthmarketscience/jackcess/expr/EvalConfig.java View File

@@ -41,6 +41,18 @@ public interface EvalConfig
*/
public void setTemporalConfig(TemporalConfig temporal);

/**
* @return the currently configured NumericConfig
*/
public NumericConfig getNumericConfig();

/**
* Sets the NumericConfig for use when evaluating expressions. The default
* date/time formatting is US based, so this may need to be modified when
* interacting with {@link Database} instances from other locales.
*/
public void setNumericConfig(NumericConfig numeric);

/**
* @return the currently configured FunctionLookup
*/

+ 10
- 4
src/main/java/com/healthmarketscience/jackcess/expr/Expression.java View File

@@ -44,12 +44,18 @@ public interface Expression
* @return a detailed string which indicates how the expression was
* interpreted by the expression evaluation engine.
*/
public String toDebugString();
public String toDebugString(LocaleContext ctx);

/**
* @return the original, unparsed expression string. By contrast, the {@link
* Object#toString} result may return a value which has been cleaned
* up with respect to the original expression.
* @return a parsed and re-formated version of the expression. This may
* look slightly different than the original, raw string, although
* it should be an equivalent expression.
*/
public String toCleanString(LocaleContext ctx);

/**
* @return the original, unparsed expression string. This is the same as
* the value which will be returned by {@link Object#toString}.
*/
public String toRawString();


+ 12
- 0
src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java View File

@@ -17,6 +17,7 @@ limitations under the License.
package com.healthmarketscience.jackcess.expr;

import java.text.SimpleDateFormat;
import java.util.Calendar;

/**
* LocaleContext encapsulates all shared localization state for expression
@@ -38,4 +39,15 @@ public interface LocaleContext
*/
public SimpleDateFormat createDateFormat(String formatStr);

/**
* @return an appropriately configured (i.e. TimeZone and other date/time
* flags) Calendar.
*/
public Calendar getCalendar();

/**
* @return the currently configured NumericConfig (from the
* {@link EvalConfig})
*/
public NumericConfig getNumericConfig();
}

+ 45
- 0
src/main/java/com/healthmarketscience/jackcess/expr/NumericConfig.java View File

@@ -0,0 +1,45 @@
/*
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.expr;

import java.text.DecimalFormatSymbols;
import java.util.Locale;

/**
* A NumericConfig encapsulates number formatting options for expression
* evaluation. The default {@link #US_NUMERIC_CONFIG} instance provides US
* specific locale configuration. Databases which have been built for other
* locales can utilize custom implementations of NumericConfig in order to
* evaluate expressions correctly.
*
* @author James Ahlborn
*/
public class NumericConfig
{
public static final NumericConfig US_NUMERIC_CONFIG = new NumericConfig(
Locale.US);

private final DecimalFormatSymbols _symbols;

public NumericConfig(Locale locale) {
_symbols = DecimalFormatSymbols.getInstance(locale);
}

public DecimalFormatSymbols getDecimalFormatSymbols() {
return _symbols;
}
}

+ 6
- 6
src/main/java/com/healthmarketscience/jackcess/expr/Value.java View File

@@ -85,30 +85,30 @@ public interface Value
/**
* @return this primitive value converted to a boolean
*/
public boolean getAsBoolean();
public boolean getAsBoolean(LocaleContext ctx);

/**
* @return this primitive value converted to a String
*/
public String getAsString();
public String getAsString(LocaleContext ctx);

/**
* @return this primitive value converted to a Date
*/
public Date getAsDateTime(EvalContext ctx);
public Date getAsDateTime(LocaleContext ctx);

/**
* @return this primitive value converted (rounded) to an int
*/
public Integer getAsLongInt();
public Integer getAsLongInt(LocaleContext ctx);

/**
* @return this primitive value converted (rounded) to a double
*/
public Double getAsDouble();
public Double getAsDouble(LocaleContext ctx);

/**
* @return this primitive value converted to a BigDecimal
*/
public BigDecimal getAsBigDecimal();
public BigDecimal getAsBigDecimal(LocaleContext ctx);
}

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

@@ -63,6 +63,8 @@ limitations under the License.
* evaluation context for a given {@link com.healthmarketscience.jackcess.Database} instance.</li>
* <li>{@link com.healthmarketscience.jackcess.expr.TemporalConfig} encapsulates date/time formatting options for
* expression evaluation.</li>
* <li>{@link com.healthmarketscience.jackcess.expr.NumericConfig} encapsulates number formatting options for
* expression evaluation.</li>
* <li>{@link com.healthmarketscience.jackcess.expr.FunctionLookup} provides a source for {@link com.healthmarketscience.jackcess.expr.Function} instances
* used during expression evaluation.</li>
* <li>{@link com.healthmarketscience.jackcess.expr.EvalException} wrapper exception thrown for failures which occur

+ 19
- 4
src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java View File

@@ -19,6 +19,7 @@ package com.healthmarketscience.jackcess.impl;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.EnumMap;
@@ -31,6 +32,8 @@ import com.healthmarketscience.jackcess.expr.EvalContext;
import com.healthmarketscience.jackcess.expr.EvalException;
import com.healthmarketscience.jackcess.expr.Expression;
import com.healthmarketscience.jackcess.expr.Identifier;
import com.healthmarketscience.jackcess.expr.LocaleContext;
import com.healthmarketscience.jackcess.expr.NumericConfig;
import com.healthmarketscience.jackcess.expr.TemporalConfig;
import com.healthmarketscience.jackcess.expr.Value;
import com.healthmarketscience.jackcess.impl.expr.Expressionator;
@@ -82,6 +85,14 @@ public abstract class BaseEvalContext implements EvalContext
return _dbCtx.createDateFormat(formatStr);
}

public Calendar getCalendar() {
return _dbCtx.getCalendar();
}

public NumericConfig getNumericConfig() {
return _dbCtx.getNumericConfig();
}

public float getRandom(Integer seed) {
return _dbCtx.getRandom(seed);
}
@@ -142,7 +153,7 @@ public abstract class BaseEvalContext implements EvalContext
case DATE:
case TIME:
case DATE_TIME:
return ValueSupport.toValue(this, vType, (Date)val);
return ValueSupport.toValue(vType, (Date)val);
case LONG:
Integer i = ((val instanceof Integer) ? (Integer)val :
((Number)val).intValue());
@@ -191,14 +202,18 @@ public abstract class BaseEvalContext implements EvalContext
return getExpr().eval(ctx);
}

public String toDebugString() {
return "<raw>{" + _exprStr + "}";
public String toDebugString(LocaleContext ctx) {
return getExpr().toDebugString(ctx);
}

public String toRawString() {
return _exprStr;
}

public String toCleanString(LocaleContext ctx) {
return getExpr().toCleanString(ctx);
}

public boolean isConstant() {
return getExpr().isConstant();
}
@@ -209,7 +224,7 @@ public abstract class BaseEvalContext implements EvalContext

@Override
public String toString() {
return _exprStr;
return toRawString();
}
}
}

+ 19
- 0
src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java View File

@@ -17,6 +17,7 @@ limitations under the License.
package com.healthmarketscience.jackcess.impl;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Map;
import javax.script.Bindings;
import javax.script.SimpleBindings;
@@ -24,6 +25,7 @@ import javax.script.SimpleBindings;
import com.healthmarketscience.jackcess.expr.EvalConfig;
import com.healthmarketscience.jackcess.expr.Function;
import com.healthmarketscience.jackcess.expr.FunctionLookup;
import com.healthmarketscience.jackcess.expr.NumericConfig;
import com.healthmarketscience.jackcess.expr.TemporalConfig;
import com.healthmarketscience.jackcess.impl.expr.DefaultFunctions;
import com.healthmarketscience.jackcess.impl.expr.Expressionator;
@@ -41,6 +43,7 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig
private FunctionLookup _funcs = DefaultFunctions.LOOKUP;
private Map<String,SimpleDateFormat> _sdfs;
private TemporalConfig _temporal;
private NumericConfig _numeric;
private final RandomContext _rndCtx = new RandomContext();
private Bindings _bindings = new SimpleBindings();

@@ -61,6 +64,18 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig
_temporal = temporal;
}

public Calendar getCalendar() {
return _db.getCalendar();
}

public NumericConfig getNumericConfig() {
return _numeric;
}

public void setNumericConfig(NumericConfig numeric) {
_numeric = numeric;
}

public FunctionLookup getFunctionLookup() {
return _funcs;
}
@@ -92,4 +107,8 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig
public float getRandom(Integer seed) {
return _rndCtx.getRandom(seed);
}

void resetDateTimeConfig() {
_sdfs = null;
}
}

+ 7
- 7
src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java View File

@@ -73,6 +73,7 @@ import com.healthmarketscience.jackcess.util.LinkResolver;
import com.healthmarketscience.jackcess.util.ReadOnlyFileChannel;
import com.healthmarketscience.jackcess.util.SimpleColumnValidatorFactory;
import com.healthmarketscience.jackcess.util.TableIterableBuilder;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -643,8 +644,11 @@ public class DatabaseImpl implements Database
newTimeZone = getDefaultTimeZone();
}
_timeZone = newTimeZone;
// clear cached calendar when timezone is changed
// clear cached calendar(s) when timezone is changed
_calendar = null;
if(_evalCtx != null) {
_evalCtx.resetDateTimeConfig();
}
}

public Charset getCharset()
@@ -1823,7 +1827,7 @@ public class DatabaseImpl implements Database
* space, {@code false} otherwise.
*/
public static boolean isBlank(String name) {
return((name == null) || (name.trim().length() == 0));
return StringUtils.isBlank(name);
}

/**
@@ -1831,11 +1835,7 @@ public class DatabaseImpl implements Database
* null} or empty.
*/
public static String trimToNull(String str) {
if(str == null) {
return null;
}
str = str.trim();
return((str.length() > 0) ? str : null);
return StringUtils.trimToNull(str);
}

@Override

+ 0
- 83
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java View File

@@ -1,83 +0,0 @@
/*
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.impl.expr;

import java.math.BigDecimal;
import java.text.DateFormat;
import java.util.Date;

import com.healthmarketscience.jackcess.impl.ColumnImpl;
import com.healthmarketscience.jackcess.expr.EvalContext;

/**
*
* @author James Ahlborn
*/
public abstract class BaseDateValue extends BaseValue
{
private final Date _val;
private final DateFormat _fmt;

public BaseDateValue(Date val, DateFormat fmt)
{
_val = val;
_fmt = fmt;
}

public Object get() {
return _val;
}

protected DateFormat getFormat() {
return _fmt;
}

protected Double getNumber() {
return ColumnImpl.toDateDouble(_val, _fmt.getCalendar());
}

@Override
public boolean getAsBoolean() {
// ms access seems to treat dates/times as "true"
return true;
}

@Override
public String getAsString() {
return _fmt.format(_val);
}

@Override
public Date getAsDateTime(EvalContext ctx) {
return _val;
}

@Override
public Integer getAsLongInt() {
return roundToLongInt();
}

@Override
public Double getAsDouble() {
return getNumber();
}

@Override
public BigDecimal getAsBigDecimal() {
return BigDecimal.valueOf(getNumber());
}
}

+ 12
- 12
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java View File

@@ -19,7 +19,7 @@ package com.healthmarketscience.jackcess.impl.expr;
import java.math.BigDecimal;
import java.util.Date;

import com.healthmarketscience.jackcess.expr.EvalContext;
import com.healthmarketscience.jackcess.expr.LocaleContext;
import com.healthmarketscience.jackcess.expr.Value;

/**
@@ -52,28 +52,28 @@ public abstract class BaseDelayedValue implements Value
return getDelegate().get();
}

public boolean getAsBoolean() {
return getDelegate().getAsBoolean();
public boolean getAsBoolean(LocaleContext ctx) {
return getDelegate().getAsBoolean(ctx);
}

public String getAsString() {
return getDelegate().getAsString();
public String getAsString(LocaleContext ctx) {
return getDelegate().getAsString(ctx);
}

public Date getAsDateTime(EvalContext ctx) {
public Date getAsDateTime(LocaleContext ctx) {
return getDelegate().getAsDateTime(ctx);
}

public Integer getAsLongInt() {
return getDelegate().getAsLongInt();
public Integer getAsLongInt(LocaleContext ctx) {
return getDelegate().getAsLongInt(ctx);
}

public Double getAsDouble() {
return getDelegate().getAsDouble();
public Double getAsDouble(LocaleContext ctx) {
return getDelegate().getAsDouble(ctx);
}

public BigDecimal getAsBigDecimal() {
return getDelegate().getAsBigDecimal();
public BigDecimal getAsBigDecimal(LocaleContext ctx) {
return getDelegate().getAsBigDecimal(ctx);
}

protected abstract Value eval();

+ 6
- 9
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java View File

@@ -19,7 +19,7 @@ package com.healthmarketscience.jackcess.impl.expr;
import java.text.SimpleDateFormat;
import java.util.Date;

import com.healthmarketscience.jackcess.expr.EvalContext;
import com.healthmarketscience.jackcess.expr.LocaleContext;
import com.healthmarketscience.jackcess.impl.ColumnImpl;

/**
@@ -34,22 +34,19 @@ public abstract class BaseNumericValue extends BaseValue
}

@Override
public Integer getAsLongInt() {
return roundToLongInt();
public Integer getAsLongInt(LocaleContext ctx) {
return roundToLongInt(ctx);
}

@Override
public Double getAsDouble() {
public Double getAsDouble(LocaleContext ctx) {
return getNumber().doubleValue();
}

@Override
public Date getAsDateTime(EvalContext ctx) {
public Date getAsDateTime(LocaleContext ctx) {
double d = getNumber().doubleValue();

SimpleDateFormat sdf = ctx.createDateFormat(
ctx.getTemporalConfig().getDefaultDateTimeFormat());
return new Date(ColumnImpl.fromDateDouble(d, sdf.getCalendar()));
return new Date(ColumnImpl.fromDateDouble(d, ctx.getCalendar()));
}

protected abstract Number getNumber();

+ 9
- 9
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java View File

@@ -20,8 +20,8 @@ import java.math.BigDecimal;
import java.util.Date;

import com.healthmarketscience.jackcess.expr.Value;
import com.healthmarketscience.jackcess.expr.EvalContext;
import com.healthmarketscience.jackcess.expr.EvalException;
import com.healthmarketscience.jackcess.expr.LocaleContext;
import com.healthmarketscience.jackcess.impl.NumberFormatter;

/**
@@ -34,27 +34,27 @@ public abstract class BaseValue implements Value
return(getType() == Type.NULL);
}

public boolean getAsBoolean() {
public boolean getAsBoolean(LocaleContext ctx) {
throw invalidConversion(Value.Type.LONG);
}

public String getAsString() {
public String getAsString(LocaleContext ctx) {
throw invalidConversion(Value.Type.STRING);
}

public Date getAsDateTime(EvalContext ctx) {
public Date getAsDateTime(LocaleContext ctx) {
throw invalidConversion(Value.Type.DATE_TIME);
}

public Integer getAsLongInt() {
public Integer getAsLongInt(LocaleContext ctx) {
throw invalidConversion(Value.Type.LONG);
}

public Double getAsDouble() {
public Double getAsDouble(LocaleContext ctx) {
throw invalidConversion(Value.Type.DOUBLE);
}

public BigDecimal getAsBigDecimal() {
public BigDecimal getAsBigDecimal(LocaleContext ctx) {
throw invalidConversion(Value.Type.BIG_DEC);
}

@@ -63,8 +63,8 @@ public abstract class BaseValue implements Value
getType() + " value cannot be converted to " + newType);
}

protected Integer roundToLongInt() {
return getAsBigDecimal().setScale(0, NumberFormatter.ROUND_MODE)
protected Integer roundToLongInt(LocaleContext ctx) {
return getAsBigDecimal(ctx).setScale(0, NumberFormatter.ROUND_MODE)
.intValueExact();
}


+ 4
- 3
src/main/java/com/healthmarketscience/jackcess/impl/expr/BigDecimalValue.java View File

@@ -18,6 +18,7 @@ package com.healthmarketscience.jackcess.impl.expr;

import java.math.BigDecimal;

import com.healthmarketscience.jackcess.expr.LocaleContext;
import com.healthmarketscience.jackcess.impl.NumberFormatter;

/**
@@ -47,17 +48,17 @@ public class BigDecimalValue extends BaseNumericValue
}

@Override
public boolean getAsBoolean() {
public boolean getAsBoolean(LocaleContext ctx) {
return (_val.compareTo(BigDecimal.ZERO) != 0L);
}

@Override
public String getAsString() {
public String getAsString(LocaleContext ctx) {
return NumberFormatter.format(_val);
}

@Override
public BigDecimal getAsBigDecimal() {
public BigDecimal getAsBigDecimal(LocaleContext ctx) {
return _val;
}
}

+ 115
- 107
src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java View File

@@ -19,8 +19,8 @@ package com.healthmarketscience.jackcess.impl.expr;
import java.math.BigDecimal;
import java.util.regex.Pattern;

import com.healthmarketscience.jackcess.expr.EvalContext;
import com.healthmarketscience.jackcess.expr.EvalException;
import com.healthmarketscience.jackcess.expr.LocaleContext;
import com.healthmarketscience.jackcess.expr.Value;
import com.healthmarketscience.jackcess.impl.NumberFormatter;
import static com.healthmarketscience.jackcess.impl.expr.ValueSupport.*;
@@ -63,7 +63,7 @@ public class BuiltinOperators
// - between, not, like, in
// - *NOT* concal op '&'

public static Value negate(EvalContext ctx, Value param1) {
public static Value negate(LocaleContext ctx, Value param1) {
if(param1.isNull()) {
// null propagation
return NULL_VAL;
@@ -76,60 +76,60 @@ public class BuiltinOperators
case TIME:
case DATE_TIME:
// dates/times get converted to date doubles for arithmetic
double result = -param1.getAsDouble();
return toDateValue(ctx, mathType, result, param1, null);
double result = -param1.getAsDouble(ctx);
return toDateValue(ctx, mathType, result);
case LONG:
return toValue(-param1.getAsLongInt());
return toValue(-param1.getAsLongInt(ctx));
case DOUBLE:
return toValue(-param1.getAsDouble());
return toValue(-param1.getAsDouble(ctx));
case STRING:
case BIG_DEC:
return toValue(param1.getAsBigDecimal().negate(
return toValue(param1.getAsBigDecimal(ctx).negate(
NumberFormatter.DEC_MATH_CONTEXT));
default:
throw new EvalException("Unexpected type " + mathType);
}
}

public static Value add(EvalContext ctx, Value param1, Value param2) {
public static Value add(LocaleContext ctx, Value param1, Value param2) {
if(anyParamIsNull(param1, param2)) {
// null propagation
return NULL_VAL;
}

Value.Type mathType = getMathTypePrecedence(param1, param2,
Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
CoercionType.SIMPLE);

switch(mathType) {
case STRING:
// string '+' is a null-propagation (handled above) concat
return nonNullConcat(param1, param2);
return nonNullConcat(ctx, param1, param2);
case DATE:
case TIME:
case DATE_TIME:
// dates/times get converted to date doubles for arithmetic
double result = param1.getAsDouble() + param2.getAsDouble();
return toDateValue(ctx, mathType, result, param1, param2);
double result = param1.getAsDouble(ctx) + param2.getAsDouble(ctx);
return toDateValue(ctx, mathType, result);
case LONG:
return toValue(param1.getAsLongInt() + param2.getAsLongInt());
return toValue(param1.getAsLongInt(ctx) + param2.getAsLongInt(ctx));
case DOUBLE:
return toValue(param1.getAsDouble() + param2.getAsDouble());
return toValue(param1.getAsDouble(ctx) + param2.getAsDouble(ctx));
case BIG_DEC:
return toValue(param1.getAsBigDecimal().add(
param2.getAsBigDecimal(),
return toValue(param1.getAsBigDecimal(ctx).add(
param2.getAsBigDecimal(ctx),
NumberFormatter.DEC_MATH_CONTEXT));
default:
throw new EvalException("Unexpected type " + mathType);
}
}

public static Value subtract(EvalContext ctx, Value param1, Value param2) {
public static Value subtract(LocaleContext ctx, Value param1, Value param2) {
if(anyParamIsNull(param1, param2)) {
// null propagation
return NULL_VAL;
}

Value.Type mathType = getMathTypePrecedence(param1, param2,
Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
CoercionType.SIMPLE);

switch(mathType) {
@@ -138,28 +138,28 @@ public class BuiltinOperators
case TIME:
case DATE_TIME:
// dates/times get converted to date doubles for arithmetic
double result = param1.getAsDouble() - param2.getAsDouble();
return toDateValue(ctx, mathType, result, param1, param2);
double result = param1.getAsDouble(ctx) - param2.getAsDouble(ctx);
return toDateValue(ctx, mathType, result);
case LONG:
return toValue(param1.getAsLongInt() - param2.getAsLongInt());
return toValue(param1.getAsLongInt(ctx) - param2.getAsLongInt(ctx));
case DOUBLE:
return toValue(param1.getAsDouble() - param2.getAsDouble());
return toValue(param1.getAsDouble(ctx) - param2.getAsDouble(ctx));
case BIG_DEC:
return toValue(param1.getAsBigDecimal().subtract(
param2.getAsBigDecimal(),
return toValue(param1.getAsBigDecimal(ctx).subtract(
param2.getAsBigDecimal(ctx),
NumberFormatter.DEC_MATH_CONTEXT));
default:
throw new EvalException("Unexpected type " + mathType);
}
}

public static Value multiply(Value param1, Value param2) {
public static Value multiply(LocaleContext ctx, Value param1, Value param2) {
if(anyParamIsNull(param1, param2)) {
// null propagation
return NULL_VAL;
}

Value.Type mathType = getMathTypePrecedence(param1, param2,
Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
CoercionType.GENERAL);

switch(mathType) {
@@ -168,25 +168,25 @@ public class BuiltinOperators
// case TIME: break; promoted to double
// case DATE_TIME: break; promoted to double
case LONG:
return toValue(param1.getAsLongInt() * param2.getAsLongInt());
return toValue(param1.getAsLongInt(ctx) * param2.getAsLongInt(ctx));
case DOUBLE:
return toValue(param1.getAsDouble() * param2.getAsDouble());
return toValue(param1.getAsDouble(ctx) * param2.getAsDouble(ctx));
case BIG_DEC:
return toValue(param1.getAsBigDecimal().multiply(
param2.getAsBigDecimal(),
return toValue(param1.getAsBigDecimal(ctx).multiply(
param2.getAsBigDecimal(ctx),
NumberFormatter.DEC_MATH_CONTEXT));
default:
throw new EvalException("Unexpected type " + mathType);
}
}

public static Value divide(Value param1, Value param2) {
public static Value divide(LocaleContext ctx, Value param1, Value param2) {
if(anyParamIsNull(param1, param2)) {
// null propagation
return NULL_VAL;
}

Value.Type mathType = getMathTypePrecedence(param1, param2,
Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
CoercionType.GENERAL);

switch(mathType) {
@@ -195,54 +195,54 @@ public class BuiltinOperators
// case TIME: break; promoted to double
// case DATE_TIME: break; promoted to double
case LONG:
int lp1 = param1.getAsLongInt();
int lp2 = param2.getAsLongInt();
int lp1 = param1.getAsLongInt(ctx);
int lp2 = param2.getAsLongInt(ctx);
if((lp1 % lp2) == 0) {
return toValue(lp1 / lp2);
}
return toValue((double)lp1 / (double)lp2);
case DOUBLE:
double d2 = param2.getAsDouble();
double d2 = param2.getAsDouble(ctx);
if(d2 == 0.0d) {
throw new ArithmeticException(DIV_BY_ZERO);
}
return toValue(param1.getAsDouble() / d2);
return toValue(param1.getAsDouble(ctx) / d2);
case BIG_DEC:
return toValue(divide(param1.getAsBigDecimal(), param2.getAsBigDecimal()));
return toValue(divide(param1.getAsBigDecimal(ctx), param2.getAsBigDecimal(ctx)));
default:
throw new EvalException("Unexpected type " + mathType);
}
}

public static Value intDivide(Value param1, Value param2) {
public static Value intDivide(LocaleContext ctx, Value param1, Value param2) {
if(anyParamIsNull(param1, param2)) {
// null propagation
return NULL_VAL;
}

Value.Type mathType = getMathTypePrecedence(param1, param2,
Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
CoercionType.GENERAL);
if(mathType == Value.Type.STRING) {
throw new EvalException("Unexpected type " + mathType);
}
return toValue(param1.getAsLongInt() / param2.getAsLongInt());
return toValue(param1.getAsLongInt(ctx) / param2.getAsLongInt(ctx));
}

public static Value exp(Value param1, Value param2) {
public static Value exp(LocaleContext ctx, Value param1, Value param2) {
if(anyParamIsNull(param1, param2)) {
// null propagation
return NULL_VAL;
}

Value.Type mathType = getMathTypePrecedence(param1, param2,
Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
CoercionType.GENERAL);

if(mathType == Value.Type.BIG_DEC) {
// see if we can handle the limited options supported for BigDecimal
// (must be a positive int exponent)
try {
BigDecimal result = param1.getAsBigDecimal().pow(
param2.getAsBigDecimal().intValueExact(),
BigDecimal result = param1.getAsBigDecimal(ctx).pow(
param2.getAsBigDecimal(ctx).intValueExact(),
NumberFormatter.DEC_MATH_CONTEXT);
return toValue(result);
} catch(ArithmeticException ae) {
@@ -251,7 +251,7 @@ public class BuiltinOperators
}

// jdk only supports general pow() as doubles, let's go with that
double result = Math.pow(param1.getAsDouble(), param2.getAsDouble());
double result = Math.pow(param1.getAsDouble(ctx), param2.getAsDouble(ctx));

// attempt to convert integral types back to integrals if possible
if((mathType == Value.Type.LONG) && isIntegral(result)) {
@@ -261,22 +261,22 @@ public class BuiltinOperators
return toValue(result);
}

public static Value mod(Value param1, Value param2) {
public static Value mod(LocaleContext ctx, Value param1, Value param2) {
if(anyParamIsNull(param1, param2)) {
// null propagation
return NULL_VAL;
}

Value.Type mathType = getMathTypePrecedence(param1, param2,
Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
CoercionType.GENERAL);

if(mathType == Value.Type.STRING) {
throw new EvalException("Unexpected type " + mathType);
}
return toValue(param1.getAsLongInt() % param2.getAsLongInt());
return toValue(param1.getAsLongInt(ctx) % param2.getAsLongInt(ctx));
}

public static Value concat(Value param1, Value param2) {
public static Value concat(LocaleContext ctx, Value param1, Value param2) {

// note, this op converts null to empty string
if(param1.isNull()) {
@@ -287,77 +287,81 @@ public class BuiltinOperators
param2 = EMPTY_STR_VAL;
}

return nonNullConcat(param1, param2);
return nonNullConcat(ctx, param1, param2);
}

private static Value nonNullConcat(Value param1, Value param2) {
return toValue(param1.getAsString().concat(param2.getAsString()));
private static Value nonNullConcat(
LocaleContext ctx, Value param1, Value param2) {
return toValue(param1.getAsString(ctx).concat(param2.getAsString(ctx)));
}

public static Value not(Value param1) {
public static Value not(LocaleContext ctx, Value param1) {
if(param1.isNull()) {
// null propagation
return NULL_VAL;
}

return toValue(!param1.getAsBoolean());
return toValue(!param1.getAsBoolean(ctx));
}

public static Value lessThan(Value param1, Value param2) {
public static Value lessThan(LocaleContext ctx, Value param1, Value param2) {
if(anyParamIsNull(param1, param2)) {
// null propagation
return NULL_VAL;
}

return toValue(nonNullCompareTo(param1, param2) < 0);
return toValue(nonNullCompareTo(ctx, param1, param2) < 0);
}

public static Value greaterThan(Value param1, Value param2) {
public static Value greaterThan(
LocaleContext ctx, Value param1, Value param2) {
if(anyParamIsNull(param1, param2)) {
// null propagation
return NULL_VAL;
}

return toValue(nonNullCompareTo(param1, param2) > 0);
return toValue(nonNullCompareTo(ctx, param1, param2) > 0);
}

public static Value lessThanEq(Value param1, Value param2) {
public static Value lessThanEq(
LocaleContext ctx, Value param1, Value param2) {
if(anyParamIsNull(param1, param2)) {
// null propagation
return NULL_VAL;
}

return toValue(nonNullCompareTo(param1, param2) <= 0);
return toValue(nonNullCompareTo(ctx, param1, param2) <= 0);
}

public static Value greaterThanEq(Value param1, Value param2) {
public static Value greaterThanEq(
LocaleContext ctx, Value param1, Value param2) {
if(anyParamIsNull(param1, param2)) {
// null propagation
return NULL_VAL;
}

return toValue(nonNullCompareTo(param1, param2) >= 0);
return toValue(nonNullCompareTo(ctx, param1, param2) >= 0);
}

public static Value equals(Value param1, Value param2) {
public static Value equals(LocaleContext ctx, Value param1, Value param2) {
if(anyParamIsNull(param1, param2)) {
// null propagation
return NULL_VAL;
}

return toValue(nonNullCompareTo(param1, param2) == 0);
return toValue(nonNullCompareTo(ctx, param1, param2) == 0);
}

public static Value notEquals(Value param1, Value param2) {
public static Value notEquals(LocaleContext ctx, Value param1, Value param2) {
if(anyParamIsNull(param1, param2)) {
// null propagation
return NULL_VAL;
}

return toValue(nonNullCompareTo(param1, param2) != 0);
return toValue(nonNullCompareTo(ctx, param1, param2) != 0);
}

public static Value and(Value param1, Value param2) {
public static Value and(LocaleContext ctx, Value param1, Value param2) {

// "and" uses short-circuit logic

@@ -365,7 +369,7 @@ public class BuiltinOperators
return NULL_VAL;
}

boolean b1 = param1.getAsBoolean();
boolean b1 = param1.getAsBoolean(ctx);
if(!b1) {
return FALSE_VAL;
}
@@ -374,10 +378,10 @@ public class BuiltinOperators
return NULL_VAL;
}

return toValue(param2.getAsBoolean());
return toValue(param2.getAsBoolean(ctx));
}

public static Value or(Value param1, Value param2) {
public static Value or(LocaleContext ctx, Value param1, Value param2) {

// "or" uses short-circuit logic

@@ -385,7 +389,7 @@ public class BuiltinOperators
return NULL_VAL;
}

boolean b1 = param1.getAsBoolean();
boolean b1 = param1.getAsBoolean(ctx);
if(b1) {
return TRUE_VAL;
}
@@ -394,39 +398,39 @@ public class BuiltinOperators
return NULL_VAL;
}

return toValue(param2.getAsBoolean());
return toValue(param2.getAsBoolean(ctx));
}

public static Value eqv(Value param1, Value param2) {
public static Value eqv(LocaleContext ctx, Value param1, Value param2) {
if(anyParamIsNull(param1, param2)) {
// null propagation
return NULL_VAL;
}

boolean b1 = param1.getAsBoolean();
boolean b2 = param2.getAsBoolean();
boolean b1 = param1.getAsBoolean(ctx);
boolean b2 = param2.getAsBoolean(ctx);

return toValue(b1 == b2);
}

public static Value xor(Value param1, Value param2) {
public static Value xor(LocaleContext ctx, Value param1, Value param2) {
if(anyParamIsNull(param1, param2)) {
// null propagation
return NULL_VAL;
}

boolean b1 = param1.getAsBoolean();
boolean b2 = param2.getAsBoolean();
boolean b1 = param1.getAsBoolean(ctx);
boolean b2 = param2.getAsBoolean(ctx);

return toValue(b1 ^ b2);
}

public static Value imp(Value param1, Value param2) {
public static Value imp(LocaleContext ctx, Value param1, Value param2) {

// "imp" uses short-circuit logic

if(param1.isNull()) {
if(param2.isNull() || !param2.getAsBoolean()) {
if(param2.isNull() || !param2.getAsBoolean(ctx)) {
// null propagation
return NULL_VAL;
}
@@ -434,7 +438,7 @@ public class BuiltinOperators
return TRUE_VAL;
}

boolean b1 = param1.getAsBoolean();
boolean b1 = param1.getAsBoolean(ctx);
if(!b1) {
return TRUE_VAL;
}
@@ -444,7 +448,7 @@ public class BuiltinOperators
return NULL_VAL;
}

return toValue(param2.getAsBoolean());
return toValue(param2.getAsBoolean(ctx));
}

public static Value isNull(Value param1) {
@@ -455,20 +459,22 @@ public class BuiltinOperators
return toValue(!param1.isNull());
}

public static Value like(Value param1, Pattern pattern) {
public static Value like(LocaleContext ctx, Value param1, Pattern pattern) {
if(param1.isNull()) {
// null propagation
return NULL_VAL;
}

return toValue(pattern.matcher(param1.getAsString()).matches());
return toValue(pattern.matcher(param1.getAsString(ctx)).matches());
}

public static Value notLike(Value param1, Pattern pattern) {
return not(like(param1, pattern));
public static Value notLike(
LocaleContext ctx, Value param1, Pattern pattern) {
return not(ctx, like(ctx, param1, pattern));
}

public static Value between(Value param1, Value param2, Value param3) {
public static Value between(
LocaleContext ctx, Value param1, Value param2, Value param3) {
// null propagate any param. uses short circuit eval of params
if(anyParamIsNull(param1, param2, param3)) {
// null propagation
@@ -478,20 +484,21 @@ public class BuiltinOperators
// the between values can be in either order!?!
Value min = param2;
Value max = param3;
Value gt = greaterThan(min, max);
if(gt.getAsBoolean()) {
Value gt = greaterThan(ctx, min, max);
if(gt.getAsBoolean(ctx)) {
min = param3;
max = param2;
}

return and(greaterThanEq(param1, min), lessThanEq(param1, max));
return and(ctx, greaterThanEq(ctx, param1, min), lessThanEq(ctx, param1, max));
}

public static Value notBetween(Value param1, Value param2, Value param3) {
return not(between(param1, param2, param3));
public static Value notBetween(
LocaleContext ctx, Value param1, Value param2, Value param3) {
return not(ctx, between(ctx, param1, param2, param3));
}

public static Value in(Value param1, Value[] params) {
public static Value in(LocaleContext ctx, Value param1, Value[] params) {

// null propagate any param. uses short circuit eval of params
if(param1.isNull()) {
@@ -504,8 +511,8 @@ public class BuiltinOperators
continue;
}

Value eq = equals(param1, val);
if(eq.getAsBoolean()) {
Value eq = equals(ctx, param1, val);
if(eq.getAsBoolean(ctx)) {
return TRUE_VAL;
}
}
@@ -513,8 +520,8 @@ public class BuiltinOperators
return FALSE_VAL;
}

public static Value notIn(Value param1, Value[] params) {
return not(in(param1, params));
public static Value notIn(LocaleContext ctx, Value param1, Value[] params) {
return not(ctx, in(ctx, param1, params));
}


@@ -528,10 +535,10 @@ public class BuiltinOperators
}

protected static int nonNullCompareTo(
Value param1, Value param2)
LocaleContext ctx, Value param1, Value param2)
{
// note that comparison does not do string to num coercion
Value.Type compareType = getMathTypePrecedence(param1, param2,
Value.Type compareType = getMathTypePrecedence(ctx, param1, param2,
CoercionType.COMPARE);

switch(compareType) {
@@ -540,23 +547,23 @@ public class BuiltinOperators
if(param1.getType() != param2.getType()) {
throw new EvalException("Unexpected type " + compareType);
}
return param1.getAsString().compareToIgnoreCase(param2.getAsString());
return param1.getAsString(ctx).compareToIgnoreCase(param2.getAsString(ctx));
// case DATE: break; promoted to double
// case TIME: break; promoted to double
// case DATE_TIME: break; promoted to double
case LONG:
return param1.getAsLongInt().compareTo(param2.getAsLongInt());
return param1.getAsLongInt(ctx).compareTo(param2.getAsLongInt(ctx));
case DOUBLE:
return param1.getAsDouble().compareTo(param2.getAsDouble());
return param1.getAsDouble(ctx).compareTo(param2.getAsDouble(ctx));
case BIG_DEC:
return param1.getAsBigDecimal().compareTo(param2.getAsBigDecimal());
return param1.getAsBigDecimal(ctx).compareTo(param2.getAsBigDecimal(ctx));
default:
throw new EvalException("Unexpected type " + compareType);
}
}

private static Value.Type getMathTypePrecedence(
Value param1, Value param2, CoercionType cType)
LocaleContext ctx, Value param1, Value param2, CoercionType cType)
{
Value.Type t1 = param1.getType();
Value.Type t2 = param2.getType();
@@ -577,7 +584,8 @@ public class BuiltinOperators
if(cType._allowCoerceStringToNum) {
// see if this is mixed string/numeric and the string can be coerced
// to a number
Value.Type numericType = coerceStringToNumeric(param1, param2, cType);
Value.Type numericType = coerceStringToNumeric(
ctx, param1, param2, cType);
if(numericType != null) {
// string can be coerced to number
return numericType;
@@ -614,7 +622,7 @@ public class BuiltinOperators
}

private static Value.Type coerceStringToNumeric(
Value param1, Value param2, CoercionType cType) {
LocaleContext ctx, Value param1, Value param2, CoercionType cType) {
Value.Type t1 = param1.getType();
Value.Type t2 = param2.getType();

@@ -639,7 +647,7 @@ public class BuiltinOperators

try {
// see if string can be coerced to a number
strParam.getAsBigDecimal();
strParam.getAsBigDecimal(ctx);
if(prefType.isNumeric()) {
// seems like when strings are coerced to numbers, they are usually
// doubles, unless the current context is decimal

+ 53
- 6
src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java View File

@@ -16,22 +16,69 @@ limitations under the License.

package com.healthmarketscience.jackcess.impl.expr;

import java.text.DateFormat;
import java.math.BigDecimal;
import java.util.Date;

import com.healthmarketscience.jackcess.impl.ColumnImpl;
import com.healthmarketscience.jackcess.expr.LocaleContext;

/**
*
* @author James Ahlborn
*/
public class DateTimeValue extends BaseDateValue
public class DateTimeValue extends BaseValue
{
private final Type _type;
private final Date _val;

public DateTimeValue(Date val, DateFormat fmt)
{
super(val, fmt);
public DateTimeValue(Type type, Date val) {
if(!type.isTemporal()) {
throw new IllegalArgumentException("invalid date/time type");
}
_type = type;
_val = val;
}

public Type getType() {
return Type.DATE_TIME;
return _type;
}

public Object get() {
return _val;
}

protected Double getNumber(LocaleContext ctx) {
return ColumnImpl.toDateDouble(_val, ctx.getCalendar());
}

@Override
public boolean getAsBoolean(LocaleContext ctx) {
// ms access seems to treat dates/times as "true"
return true;
}

@Override
public String getAsString(LocaleContext ctx) {
return ValueSupport.getDateFormatForType(ctx, getType()).format(_val);
}

@Override
public Date getAsDateTime(LocaleContext ctx) {
return _val;
}

@Override
public Integer getAsLongInt(LocaleContext ctx) {
return roundToLongInt(ctx);
}

@Override
public Double getAsDouble(LocaleContext ctx) {
return getNumber(ctx);
}

@Override
public BigDecimal getAsBigDecimal(LocaleContext ctx) {
return BigDecimal.valueOf(getNumber(ctx));
}
}

+ 0
- 36
src/main/java/com/healthmarketscience/jackcess/impl/expr/DateValue.java View File

@@ -1,36 +0,0 @@
/*
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.impl.expr;

import java.text.DateFormat;
import java.util.Date;

/**
*
* @author James Ahlborn
*/
public class DateValue extends BaseDateValue
{
public DateValue(Date val, DateFormat fmt)
{
super(val, fmt);
}

public Type getType() {
return Type.DATE;
}
}

+ 43
- 58
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java View File

@@ -26,6 +26,7 @@ import java.util.Date;
import com.healthmarketscience.jackcess.expr.EvalContext;
import com.healthmarketscience.jackcess.expr.EvalException;
import com.healthmarketscience.jackcess.expr.Function;
import com.healthmarketscience.jackcess.expr.LocaleContext;
import com.healthmarketscience.jackcess.expr.TemporalConfig;
import com.healthmarketscience.jackcess.expr.Value;
import com.healthmarketscience.jackcess.impl.ColumnImpl;
@@ -58,9 +59,8 @@ public class DefaultDateFunctions
public static final Function DATE = registerFunc(new Func0("Date") {
@Override
protected Value eval0(EvalContext ctx) {
DateFormat fmt = ValueSupport.getDateFormatForType(ctx, Value.Type.DATE);
double dd = dateOnly(currentTimeDouble(fmt));
return ValueSupport.toValue(Value.Type.DATE, dd, fmt);
double dd = dateOnly(currentTimeDouble(ctx));
return ValueSupport.toDateValue(ctx, Value.Type.DATE, dd);
}
});

@@ -71,26 +71,24 @@ public class DefaultDateFunctions
if(dv.getType() == Value.Type.DATE) {
return dv;
}
double dd = dateOnly(dv.getAsDouble());
DateFormat fmt = ValueSupport.getDateFormatForType(ctx, Value.Type.DATE);
return ValueSupport.toValue(Value.Type.DATE, dd, fmt);
double dd = dateOnly(dv.getAsDouble(ctx));
return ValueSupport.toDateValue(ctx, Value.Type.DATE, dd);
}
});

public static final Function DATESERIAL = registerFunc(new Func3("DateSerial") {
@Override
protected Value eval3(EvalContext ctx, Value param1, Value param2, Value param3) {
int year = param1.getAsLongInt();
int month = param2.getAsLongInt();
int day = param3.getAsLongInt();
int year = param1.getAsLongInt(ctx);
int month = param2.getAsLongInt(ctx);
int day = param3.getAsLongInt(ctx);

// "default" two digit year handling
if(year < 100) {
year += ((year <= 29) ? 2000 : 1900);
}

DateFormat fmt = ValueSupport.getDateFormatForType(ctx, Value.Type.DATE);
Calendar cal = fmt.getCalendar();
Calendar cal = ctx.getCalendar();
cal.clear();

cal.set(Calendar.YEAR, year);
@@ -98,24 +96,22 @@ public class DefaultDateFunctions
cal.set(Calendar.MONTH, month - 1);
cal.set(Calendar.DAY_OF_MONTH, day);

return ValueSupport.toValue(Value.Type.DATE, cal.getTime(), fmt);
return ValueSupport.toValue(Value.Type.DATE, cal.getTime());
}
});

public static final Function NOW = registerFunc(new Func0("Now") {
@Override
protected Value eval0(EvalContext ctx) {
DateFormat fmt = ValueSupport.getDateFormatForType(ctx, Value.Type.DATE_TIME);
return ValueSupport.toValue(Value.Type.DATE_TIME, new Date(), fmt);
return ValueSupport.toValue(Value.Type.DATE_TIME, new Date());
}
});

public static final Function TIME = registerFunc(new Func0("Time") {
@Override
protected Value eval0(EvalContext ctx) {
DateFormat fmt = ValueSupport.getDateFormatForType(ctx, Value.Type.TIME);
double dd = timeOnly(currentTimeDouble(fmt));
return ValueSupport.toValue(Value.Type.TIME, dd, fmt);
double dd = timeOnly(currentTimeDouble(ctx));
return ValueSupport.toDateValue(ctx, Value.Type.TIME, dd);
}
});

@@ -126,17 +122,15 @@ public class DefaultDateFunctions
if(dv.getType() == Value.Type.TIME) {
return dv;
}
double dd = timeOnly(dv.getAsDouble());
DateFormat fmt = ValueSupport.getDateFormatForType(ctx, Value.Type.TIME);
return ValueSupport.toValue(Value.Type.TIME, dd, fmt);
double dd = timeOnly(dv.getAsDouble(ctx));
return ValueSupport.toDateValue(ctx, Value.Type.TIME, dd);
}
});

public static final Function TIMER = registerFunc(new Func0("Timer") {
@Override
protected Value eval0(EvalContext ctx) {
DateFormat fmt = ValueSupport.getDateFormatForType(ctx, Value.Type.TIME);
double dd = timeOnly(currentTimeDouble(fmt)) * DSECONDS_PER_DAY;
double dd = timeOnly(currentTimeDouble(ctx)) * DSECONDS_PER_DAY;
return ValueSupport.toValue(dd);
}
});
@@ -144,9 +138,9 @@ public class DefaultDateFunctions
public static final Function TIMESERIAL = registerFunc(new Func3("TimeSerial") {
@Override
protected Value eval3(EvalContext ctx, Value param1, Value param2, Value param3) {
int hours = param1.getAsLongInt();
int minutes = param2.getAsLongInt();
int seconds = param3.getAsLongInt();
int hours = param1.getAsLongInt(ctx);
int minutes = param2.getAsLongInt(ctx);
int seconds = param3.getAsLongInt(ctx);

long totalSeconds = (hours * SECONDS_PER_HOUR) +
(minutes * SECONDS_PER_MINUTE) + seconds;
@@ -158,9 +152,8 @@ public class DefaultDateFunctions
totalSeconds %= SECONDS_PER_DAY;
}

DateFormat fmt = ValueSupport.getDateFormatForType(ctx, Value.Type.TIME);
double dd = totalSeconds / DSECONDS_PER_DAY;
return ValueSupport.toValue(Value.Type.TIME, dd, fmt);
return ValueSupport.toDateValue(ctx, Value.Type.TIME, dd);
}
});

@@ -213,12 +206,11 @@ public class DefaultDateFunctions
return null;
}
// convert from 1 based to 0 based value
int month = param1.getAsLongInt() - 1;
int month = param1.getAsLongInt(ctx) - 1;

boolean abbreviate = getOptionalBooleanParam(params, 1);
boolean abbreviate = getOptionalBooleanParam(ctx, params, 1);

DateFormatSymbols syms = ctx.createDateFormat(
ctx.getTemporalConfig().getDateFormat()).getDateFormatSymbols();
DateFormatSymbols syms = ctx.getTemporalConfig().getDateFormatSymbols();
String[] monthNames = (abbreviate ?
syms.getShortMonths() : syms.getMonths());
// note, the array is 1 based
@@ -243,7 +235,7 @@ public class DefaultDateFunctions
}
int dayOfWeek = nonNullToCalendarField(ctx, param1, Calendar.DAY_OF_WEEK);

int firstDay = getFirstDayParam(params, 1);
int firstDay = getFirstDayParam(ctx, params, 1);

return ValueSupport.toValue(dayOfWeekToWeekDay(dayOfWeek, firstDay));
}
@@ -256,16 +248,15 @@ public class DefaultDateFunctions
if(param1 == null) {
return null;
}
int weekday = param1.getAsLongInt();
int weekday = param1.getAsLongInt(ctx);

boolean abbreviate = getOptionalBooleanParam(params, 1);
boolean abbreviate = getOptionalBooleanParam(ctx, params, 1);

int firstDay = getFirstDayParam(params, 2);
int firstDay = getFirstDayParam(ctx, params, 2);

int dayOfWeek = weekDayToDayOfWeek(weekday, firstDay);

DateFormatSymbols syms = ctx.createDateFormat(
ctx.getTemporalConfig().getDateFormat()).getDateFormatSymbols();
DateFormatSymbols syms = ctx.getTemporalConfig().getDateFormatSymbols();
String[] weekdayNames = (abbreviate ?
syms.getShortWeekdays() : syms.getWeekdays());
// note, the array is 1 based
@@ -287,7 +278,7 @@ public class DefaultDateFunctions
origParam + "'");
}

Calendar cal = getDateValueFormat(ctx, param).getCalendar();
Calendar cal = ctx.getCalendar();
cal.setTime(param.getAsDateTime(ctx));
return cal;
}
@@ -301,18 +292,17 @@ public class DefaultDateFunctions
if(type == Value.Type.STRING) {

// see if we can coerce to date/time or double
String valStr = param.getAsString();
String valStr = param.getAsString(ctx);
TemporalConfig.Type valTempType = ExpressionTokenizer.determineDateType(
valStr, ctx);

if(valTempType != null) {

try {
DateFormat parseDf = ExpressionTokenizer.createParseDateFormat(
DateFormat parseDf = ExpressionTokenizer.createParseDateTimeFormat(
valTempType, ctx);
Date dateVal = ExpressionTokenizer.parseComplete(parseDf, valStr);
return ValueSupport.toValue(ctx, valTempType.getValueType(),
dateVal);
return ValueSupport.toValue(valTempType.getValueType(), dateVal);
} catch(java.text.ParseException pe) {
// not a valid date string, not a date/time
return null;
@@ -321,7 +311,7 @@ public class DefaultDateFunctions

// see if string can be coerced to number
try {
return numberToDateValue(ctx, param.getAsDouble());
return numberToDateValue(ctx, param.getAsDouble(ctx));
} catch(NumberFormatException ignored) {
// not a number, not a date/time
return null;
@@ -329,7 +319,7 @@ public class DefaultDateFunctions
}

// must be a number
return numberToDateValue(ctx, param.getAsDouble());
return numberToDateValue(ctx, param.getAsDouble(ctx));
}

private static Value numberToDateValue(EvalContext ctx, double dd) {
@@ -344,14 +334,7 @@ public class DefaultDateFunctions
Value.Type type = (hasDate ? (hasTime ? Value.Type.DATE_TIME :
Value.Type.DATE) :
Value.Type.TIME);
DateFormat fmt = ValueSupport.getDateFormatForType(ctx, type);
return ValueSupport.toValue(type, dd, fmt);
}

private static DateFormat getDateValueFormat(EvalContext ctx, Value param) {
return ((param instanceof BaseDateValue) ?
((BaseDateValue)param).getFormat() :
ValueSupport.getDateFormatForType(ctx, param.getType()));
return ValueSupport.toDateValue(ctx, type, dd);
}

private static double dateOnly(double dd) {
@@ -366,8 +349,8 @@ public class DefaultDateFunctions
return new BigDecimal(dd).remainder(BigDecimal.ONE).doubleValue();
}

private static double currentTimeDouble(DateFormat fmt) {
return ColumnImpl.toDateDouble(System.currentTimeMillis(), fmt.getCalendar());
private static double currentTimeDouble(LocaleContext ctx) {
return ColumnImpl.toDateDouble(System.currentTimeMillis(), ctx.getCalendar());
}

private static int dayOfWeekToWeekDay(int day, int firstDay) {
@@ -382,11 +365,12 @@ public class DefaultDateFunctions
return (((firstDay - 1) + (weekday - 1)) % 7) + 1;
}

private static int getFirstDayParam(Value[] params, int idx) {
private static int getFirstDayParam(
LocaleContext ctx, Value[] params, int idx) {
// vbSunday (default)
int firstDay = 1;
if(params.length > idx) {
firstDay = params[idx].getAsLongInt();
firstDay = params[idx].getAsLongInt(ctx);
if(firstDay == 0) {
// 0 == vbUseSystem, so we will use the default "sunday"
firstDay = 1;
@@ -395,9 +379,10 @@ public class DefaultDateFunctions
return firstDay;
}

private static boolean getOptionalBooleanParam(Value[] params, int idx) {
private static boolean getOptionalBooleanParam(
LocaleContext ctx, Value[] params, int idx) {
if(params.length > idx) {
return params[idx].getAsBoolean();
return params[idx].getAsBoolean(ctx);
}
return false;
}

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

@@ -45,18 +45,18 @@ public class DefaultFinancialFunctions
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(ctx);
double pmt = params[1].getAsDouble(ctx);
double pv = params[2].getAsDouble(ctx);

double fv = 0d;
if(params.length > 3) {
fv = params[3].getAsDouble();
fv = params[3].getAsDouble(ctx);
}

int pmtType = PMT_END_MNTH;
if(params.length > 4) {
pmtType = params[4].getAsLongInt();
pmtType = params[4].getAsLongInt(ctx);
}

double result = calculateLoanPaymentPeriods(rate, pmt, pv, pmtType);
@@ -72,18 +72,18 @@ 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(ctx);
double nper = params[1].getAsDouble(ctx);
double pmt = params[2].getAsDouble(ctx);

double pv = 0d;
if(params.length > 3) {
pv = params[3].getAsDouble();
pv = params[3].getAsDouble(ctx);
}

int pmtType = PMT_END_MNTH;
if(params.length > 4) {
pmtType = params[4].getAsLongInt();
pmtType = params[4].getAsLongInt(ctx);
}

if(pv != 0d) {
@@ -99,18 +99,18 @@ 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(ctx);
double nper = params[1].getAsDouble(ctx);
double pmt = params[2].getAsDouble(ctx);

double fv = 0d;
if(params.length > 3) {
fv = params[3].getAsDouble();
fv = params[3].getAsDouble(ctx);
}

int pmtType = PMT_END_MNTH;
if(params.length > 4) {
pmtType = params[4].getAsLongInt();
pmtType = params[4].getAsLongInt(ctx);
}

if(fv != 0d) {
@@ -126,18 +126,18 @@ 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(ctx);
double nper = params[1].getAsDouble(ctx);
double pv = params[2].getAsDouble(ctx);

double fv = 0d;
if(params.length > 3) {
fv = params[3].getAsDouble();
fv = params[3].getAsDouble(ctx);
}

int pmtType = PMT_END_MNTH;
if(params.length > 4) {
pmtType = params[4].getAsLongInt();
pmtType = params[4].getAsLongInt(ctx);
}

double result = calculateLoanPayment(rate, nper, pv, pmtType);
@@ -154,19 +154,19 @@ 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(ctx);
// double per = params[1].getAsDouble(ctx);
// double nper = params[2].getAsDouble(ctx);
// double pv = params[3].getAsDouble(ctx);

// double fv = 0d;
// if(params.length > 4) {
// fv = params[4].getAsDouble();
// fv = params[4].getAsDouble(ctx);
// }

// int pmtType = PMT_END_MNTH;
// if(params.length > 5) {
// pmtType = params[5].getAsLongInt();
// pmtType = params[5].getAsLongInt(ctx);
// }

// double pmt = calculateLoanPayment(rate, nper, pv, pmtType);
@@ -185,19 +185,19 @@ 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(ctx);
// double per = params[1].getAsDouble(ctx);
// double nper = params[2].getAsDouble(ctx);
// double pv = params[3].getAsDouble(ctx);

// double fv = 0d;
// if(params.length > 4) {
// fv = params[4].getAsDouble();
// fv = params[4].getAsDouble(ctx);
// }

// int pmtType = PMT_END_MNTH;
// if(params.length > 5) {
// pmtType = params[5].getAsLongInt();
// pmtType = params[5].getAsLongInt(ctx);
// }

// double pmt = calculateLoanPayment(rate, nper, pv, pmtType);
@@ -217,14 +217,14 @@ 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(ctx);
// double salvage = params[1].getAsDouble(ctx);
// double life = params[2].getAsDouble(ctx);
// double period = params[3].getAsDouble(ctx);

// double factor = 2d;
// if(params.length > 4) {
// factor = params[4].getAsDouble();
// factor = params[4].getAsDouble(ctx);
// }

// double result = 0d;
@@ -263,9 +263,9 @@ public class DefaultFinancialFunctions
// 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 cost = params[0].getAsDouble(ctx);
// double salvage = params[1].getAsDouble(ctx);
// double life = params[2].getAsDouble(ctx);

// double result = calculateStraightLineDepreciation(cost, salvage, life);

@@ -277,10 +277,10 @@ public class DefaultFinancialFunctions
// 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 cost = params[0].getAsDouble(ctx);
// double salvage = params[1].getAsDouble(ctx);
// double life = params[2].getAsDouble(ctx);
// double period = params[3].getAsDouble(ctx);

// double result = calculateSumOfYearsDepreciation(
// cost, salvage, life, period);

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

@@ -60,7 +60,7 @@ public class DefaultFunctions
protected Value eval3(EvalContext ctx,
Value param1, Value param2, Value param3) {
// null is false
return ((!param1.isNull() && param1.getAsBoolean()) ? param2 : param3);
return ((!param1.isNull() && param1.getAsBoolean(ctx)) ? param2 : param3);
}
});

@@ -68,10 +68,10 @@ public class DefaultFunctions
@Override
protected Value eval1(EvalContext ctx, Value param1) {
if((param1.getType() == Value.Type.STRING) &&
(param1.getAsString().length() == 0)) {
(param1.getAsString(ctx).length() == 0)) {
return ValueSupport.ZERO_VAL;
}
int lv = param1.getAsLongInt();
int lv = param1.getAsLongInt(ctx);
return ValueSupport.toValue(Integer.toHexString(lv).toUpperCase());
}
});
@@ -97,7 +97,7 @@ public class DefaultFunctions
@Override
protected Value evalVar(EvalContext ctx, Value[] params) {
Value param1 = params[0];
int idx = param1.getAsLongInt();
int idx = param1.getAsLongInt(ctx);
if((idx < 1) || (idx >= params.length)) {
return ValueSupport.NULL_VAL;
}
@@ -112,7 +112,7 @@ public class DefaultFunctions
throw new EvalException("Odd number of parameters");
}
for(int i = 0; i < params.length; i+=2) {
if(params[i].getAsBoolean()) {
if(params[i].getAsBoolean(ctx)) {
return params[i + 1];
}
}
@@ -124,10 +124,10 @@ public class DefaultFunctions
@Override
protected Value eval1(EvalContext ctx, Value param1) {
if((param1.getType() == Value.Type.STRING) &&
(param1.getAsString().length() == 0)) {
(param1.getAsString(ctx).length() == 0)) {
return ValueSupport.ZERO_VAL;
}
int lv = param1.getAsLongInt();
int lv = param1.getAsLongInt(ctx);
return ValueSupport.toValue(Integer.toOctalString(lv));
}
});
@@ -135,7 +135,7 @@ public class DefaultFunctions
public static final Function CBOOL = registerFunc(new Func1("CBool") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
boolean b = param1.getAsBoolean();
boolean b = param1.getAsBoolean(ctx);
return ValueSupport.toValue(b);
}
});
@@ -143,7 +143,7 @@ public class DefaultFunctions
public static final Function CBYTE = registerFunc(new Func1("CByte") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
int lv = param1.getAsLongInt();
int lv = param1.getAsLongInt(ctx);
if((lv < 0) || (lv > 255)) {
throw new EvalException("Byte code '" + lv + "' out of range ");
}
@@ -154,7 +154,7 @@ public class DefaultFunctions
public static final Function CCUR = registerFunc(new Func1("CCur") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
BigDecimal bd = param1.getAsBigDecimal();
BigDecimal bd = param1.getAsBigDecimal(ctx);
bd = bd.setScale(4, NumberFormatter.ROUND_MODE);
return ValueSupport.toValue(bd);
}
@@ -173,7 +173,7 @@ public class DefaultFunctions
public static final Function CDBL = registerFunc(new Func1("CDbl") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
Double dv = param1.getAsDouble();
Double dv = param1.getAsDouble(ctx);
return ValueSupport.toValue(dv);
}
});
@@ -181,7 +181,7 @@ public class DefaultFunctions
public static final Function CDEC = registerFunc(new Func1("CDec") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
BigDecimal bd = param1.getAsBigDecimal();
BigDecimal bd = param1.getAsBigDecimal(ctx);
return ValueSupport.toValue(bd);
}
});
@@ -189,7 +189,7 @@ public class DefaultFunctions
public static final Function CINT = registerFunc(new Func1("CInt") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
int lv = param1.getAsLongInt();
int lv = param1.getAsLongInt(ctx);
if((lv < Short.MIN_VALUE) || (lv > Short.MAX_VALUE)) {
throw new EvalException("Int value '" + lv + "' out of range ");
}
@@ -200,7 +200,7 @@ public class DefaultFunctions
public static final Function CLNG = registerFunc(new Func1("CLng") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
int lv = param1.getAsLongInt();
int lv = param1.getAsLongInt(ctx);
return ValueSupport.toValue(lv);
}
});
@@ -208,7 +208,7 @@ public class DefaultFunctions
public static final Function CSNG = registerFunc(new Func1("CSng") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
Double dv = param1.getAsDouble();
Double dv = param1.getAsDouble(ctx);
if((dv < Float.MIN_VALUE) || (dv > Float.MAX_VALUE)) {
throw new EvalException("Single value '" + dv + "' out of range ");
}
@@ -219,7 +219,7 @@ public class DefaultFunctions
public static final Function CSTR = registerFunc(new Func1("CStr") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
return ValueSupport.toValue(param1.getAsString());
return ValueSupport.toValue(param1.getAsString(ctx));
}
});

@@ -255,7 +255,7 @@ public class DefaultFunctions

if(param1.getType() == Value.Type.STRING) {
try {
param1.getAsBigDecimal();
param1.getAsBigDecimal(ctx);
return ValueSupport.TRUE_VAL;
} catch(NumberFormatException ignored) {
// fall through to FALSE_VAL

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

@@ -49,15 +49,15 @@ public class DefaultNumberFunctions
case TIME:
case DATE_TIME:
// dates/times get converted to date doubles for arithmetic
double result = Math.abs(param1.getAsDouble());
return ValueSupport.toDateValue(ctx, mathType, result, param1, null);
double result = Math.abs(param1.getAsDouble(ctx));
return ValueSupport.toDateValue(ctx, mathType, result);
case LONG:
return ValueSupport.toValue(Math.abs(param1.getAsLongInt()));
return ValueSupport.toValue(Math.abs(param1.getAsLongInt(ctx)));
case DOUBLE:
return ValueSupport.toValue(Math.abs(param1.getAsDouble()));
return ValueSupport.toValue(Math.abs(param1.getAsDouble(ctx)));
case STRING:
case BIG_DEC:
return ValueSupport.toValue(param1.getAsBigDecimal().abs(
return ValueSupport.toValue(param1.getAsBigDecimal(ctx).abs(
NumberFormatter.DEC_MATH_CONTEXT));
default:
throw new EvalException("Unexpected type " + mathType);
@@ -68,21 +68,21 @@ public class DefaultNumberFunctions
public static final Function ATAN = registerFunc(new Func1("Atan") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
return ValueSupport.toValue(Math.atan(param1.getAsDouble()));
return ValueSupport.toValue(Math.atan(param1.getAsDouble(ctx)));
}
});

public static final Function COS = registerFunc(new Func1("Cos") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
return ValueSupport.toValue(Math.cos(param1.getAsDouble()));
return ValueSupport.toValue(Math.cos(param1.getAsDouble(ctx)));
}
});

public static final Function EXP = registerFunc(new Func1("Exp") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
return ValueSupport.toValue(Math.exp(param1.getAsDouble()));
return ValueSupport.toValue(Math.exp(param1.getAsDouble(ctx)));
}
});

@@ -92,7 +92,7 @@ public class DefaultNumberFunctions
if(param1.getType().isIntegral()) {
return param1;
}
return ValueSupport.toValue(param1.getAsDouble().intValue());
return ValueSupport.toValue(param1.getAsDouble(ctx).intValue());
}
});

@@ -102,14 +102,14 @@ public class DefaultNumberFunctions
if(param1.getType().isIntegral()) {
return param1;
}
return ValueSupport.toValue((int)Math.floor(param1.getAsDouble()));
return ValueSupport.toValue((int)Math.floor(param1.getAsDouble(ctx)));
}
});

public static final Function LOG = registerFunc(new Func1("Log") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
return ValueSupport.toValue(Math.log(param1.getAsDouble()));
return ValueSupport.toValue(Math.log(param1.getAsDouble(ctx)));
}
});

@@ -120,7 +120,7 @@ public class DefaultNumberFunctions
}
@Override
protected Value evalVar(EvalContext ctx, Value[] params) {
Integer seed = ((params.length > 0) ? params[0].getAsLongInt() : null);
Integer seed = ((params.length > 0) ? params[0].getAsLongInt(ctx) : null);
return ValueSupport.toValue(ctx.getRandom(seed));
}
});
@@ -137,9 +137,9 @@ public class DefaultNumberFunctions
}
int scale = 0;
if(params.length > 1) {
scale = params[1].getAsLongInt();
scale = params[1].getAsLongInt(ctx);
}
BigDecimal bd = param1.getAsBigDecimal()
BigDecimal bd = param1.getAsBigDecimal(ctx)
.setScale(scale, NumberFormatter.ROUND_MODE);
return ValueSupport.toValue(bd);
}
@@ -150,9 +150,9 @@ public class DefaultNumberFunctions
protected Value eval1(EvalContext ctx, Value param1) {
int val = 0;
if(param1.getType().isIntegral()) {
val = param1.getAsLongInt();
val = param1.getAsLongInt(ctx);
} else {
val = param1.getAsBigDecimal().signum();
val = param1.getAsBigDecimal(ctx).signum();
}
return ((val > 0) ? ValueSupport.ONE_VAL :
((val < 0) ? ValueSupport.NEG_ONE_VAL :
@@ -163,7 +163,7 @@ public class DefaultNumberFunctions
public static final Function SQR = registerFunc(new Func1("Sqr") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
double dv = param1.getAsDouble();
double dv = param1.getAsDouble(ctx);
if(dv < 0.0d) {
throw new EvalException("Invalid value '" + dv + "'");
}
@@ -174,14 +174,14 @@ public class DefaultNumberFunctions
public static final Function SIN = registerFunc(new Func1("Sin") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
return ValueSupport.toValue(Math.sin(param1.getAsDouble()));
return ValueSupport.toValue(Math.sin(param1.getAsDouble(ctx)));
}
});

public static final Function TAN = registerFunc(new Func1("Tan") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
return ValueSupport.toValue(Math.tan(param1.getAsDouble()));
return ValueSupport.toValue(Math.tan(param1.getAsDouble(ctx)));
}
});


+ 36
- 35
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java View File

@@ -21,6 +21,7 @@ import java.math.BigDecimal;
import com.healthmarketscience.jackcess.expr.EvalContext;
import com.healthmarketscience.jackcess.expr.EvalException;
import com.healthmarketscience.jackcess.expr.Function;
import com.healthmarketscience.jackcess.expr.LocaleContext;
import com.healthmarketscience.jackcess.expr.Value;
import static com.healthmarketscience.jackcess.impl.expr.DefaultFunctions.*;
import static com.healthmarketscience.jackcess.impl.expr.FunctionSupport.*;
@@ -41,7 +42,7 @@ public class DefaultTextFunctions
public static final Function ASC = registerFunc(new Func1("Asc") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
String str = param1.getAsString();
String str = param1.getAsString(ctx);
int len = str.length();
if(len == 0) {
throw new EvalException("No characters in string");
@@ -58,7 +59,7 @@ public class DefaultTextFunctions
public static final Function ASCW = registerFunc(new Func1("AscW") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
String str = param1.getAsString();
String str = param1.getAsString(ctx);
int len = str.length();
if(len == 0) {
throw new EvalException("No characters in string");
@@ -71,7 +72,7 @@ public class DefaultTextFunctions
public static final Function CHR = registerStringFunc(new Func1NullIsNull("Chr") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
int lv = param1.getAsLongInt();
int lv = param1.getAsLongInt(ctx);
if((lv < 0) || (lv > 255)) {
throw new EvalException("Character code '" + lv +
"' out of range ");
@@ -84,7 +85,7 @@ public class DefaultTextFunctions
public static final Function CHRW = registerStringFunc(new Func1NullIsNull("ChrW") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
int lv = param1.getAsLongInt();
int lv = param1.getAsLongInt(ctx);
char[] cs = Character.toChars(lv);
return ValueSupport.toValue(new String(cs));
}
@@ -93,7 +94,7 @@ public class DefaultTextFunctions
public static final Function STR = registerStringFunc(new Func1NullIsNull("Str") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
BigDecimal bd = param1.getAsBigDecimal();
BigDecimal bd = param1.getAsBigDecimal(ctx);
String str = bd.toPlainString();
if(bd.compareTo(BigDecimal.ZERO) >= 0) {
str = " " + str;
@@ -109,14 +110,14 @@ public class DefaultTextFunctions
int start = 0;
if(params.length > 2) {
// 1 based offsets
start = params[0].getAsLongInt() - 1;
start = params[0].getAsLongInt(ctx) - 1;
++idx;
}
Value param1 = params[idx++];
if(param1.isNull()) {
return param1;
}
String s1 = param1.getAsString();
String s1 = param1.getAsString(ctx);
int s1Len = s1.length();
if(s1Len == 0) {
return ValueSupport.ZERO_VAL;
@@ -125,7 +126,7 @@ public class DefaultTextFunctions
if(param2.isNull()) {
return param2;
}
String s2 = param2.getAsString();
String s2 = param2.getAsString(ctx);
int s2Len = s2.length();
if(s2Len == 0) {
// 1 based offsets
@@ -133,7 +134,7 @@ public class DefaultTextFunctions
}
boolean ignoreCase = true;
if(params.length > 3) {
ignoreCase = doIgnoreCase(params[3]);
ignoreCase = doIgnoreCase(ctx, params[3]);
}
int end = s1Len - s2Len;
while(start < end) {
@@ -154,7 +155,7 @@ public class DefaultTextFunctions
if(param1.isNull()) {
return param1;
}
String s1 = param1.getAsString();
String s1 = param1.getAsString(ctx);
int s1Len = s1.length();
if(s1Len == 0) {
return ValueSupport.ZERO_VAL;
@@ -163,7 +164,7 @@ public class DefaultTextFunctions
if(param2.isNull()) {
return param2;
}
String s2 = param2.getAsString();
String s2 = param2.getAsString(ctx);
int s2Len = s2.length();
int start = s1Len - 1;
if(s2Len == 0) {
@@ -171,7 +172,7 @@ public class DefaultTextFunctions
return ValueSupport.toValue(start + 1);
}
if(params.length > 2) {
start = params[2].getAsLongInt();
start = params[2].getAsLongInt(ctx);
if(start == -1) {
start = s1Len;
}
@@ -180,7 +181,7 @@ public class DefaultTextFunctions
}
boolean ignoreCase = true;
if(params.length > 3) {
ignoreCase = doIgnoreCase(params[3]);
ignoreCase = doIgnoreCase(ctx, params[3]);
}
start = Math.min(s1Len - s2Len, start - s2Len + 1);
while(start >= 0) {
@@ -197,7 +198,7 @@ public class DefaultTextFunctions
public static final Function LCASE = registerStringFunc(new Func1NullIsNull("LCase") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
String str = param1.getAsString();
String str = param1.getAsString(ctx);
return ValueSupport.toValue(str.toLowerCase());
}
});
@@ -205,7 +206,7 @@ public class DefaultTextFunctions
public static final Function UCASE = registerStringFunc(new Func1NullIsNull("UCase") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
String str = param1.getAsString();
String str = param1.getAsString(ctx);
return ValueSupport.toValue(str.toUpperCase());
}
});
@@ -216,8 +217,8 @@ public class DefaultTextFunctions
if(param1.isNull()) {
return param1;
}
String str = param1.getAsString();
int len = Math.min(str.length(), param2.getAsLongInt());
String str = param1.getAsString(ctx);
int len = Math.min(str.length(), param2.getAsLongInt(ctx));
return ValueSupport.toValue(str.substring(0, len));
}
});
@@ -228,9 +229,9 @@ public class DefaultTextFunctions
if(param1.isNull()) {
return param1;
}
String str = param1.getAsString();
String str = param1.getAsString(ctx);
int strLen = str.length();
int len = Math.min(strLen, param2.getAsLongInt());
int len = Math.min(strLen, param2.getAsLongInt(ctx));
return ValueSupport.toValue(str.substring(strLen - len, strLen));
}
});
@@ -242,12 +243,12 @@ public class DefaultTextFunctions
if(param1.isNull()) {
return param1;
}
String str = param1.getAsString();
String str = param1.getAsString(ctx);
int strLen = str.length();
// 1 based offsets
int start = Math.min(strLen, params[1].getAsLongInt() - 1);
int start = Math.min(strLen, params[1].getAsLongInt(ctx) - 1);
int len = Math.min(
((params.length > 2) ? params[2].getAsLongInt() : strLen),
((params.length > 2) ? params[2].getAsLongInt(ctx) : strLen),
(strLen - start));
return ValueSupport.toValue(str.substring(start, start + len));
}
@@ -256,7 +257,7 @@ public class DefaultTextFunctions
public static final Function LEN = registerFunc(new Func1NullIsNull("Len") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
String str = param1.getAsString();
String str = param1.getAsString(ctx);
return ValueSupport.toValue(str.length());
}
});
@@ -264,7 +265,7 @@ public class DefaultTextFunctions
public static final Function LTRIM = registerStringFunc(new Func1NullIsNull("LTrim") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
String str = param1.getAsString();
String str = param1.getAsString(ctx);
return ValueSupport.toValue(trim(str, true, false));
}
});
@@ -272,7 +273,7 @@ public class DefaultTextFunctions
public static final Function RTRIM = registerStringFunc(new Func1NullIsNull("RTrim") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
String str = param1.getAsString();
String str = param1.getAsString(ctx);
return ValueSupport.toValue(trim(str, false, true));
}
});
@@ -280,7 +281,7 @@ public class DefaultTextFunctions
public static final Function TRIM = registerStringFunc(new Func1NullIsNull("Trim") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
String str = param1.getAsString();
String str = param1.getAsString(ctx);
return ValueSupport.toValue(trim(str, true, true));
}
});
@@ -288,7 +289,7 @@ public class DefaultTextFunctions
public static final Function SPACE = registerStringFunc(new Func1("Space") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
int lv = param1.getAsLongInt();
int lv = param1.getAsLongInt(ctx);
return ValueSupport.toValue(nchars(lv, ' '));
}
});
@@ -301,11 +302,11 @@ public class DefaultTextFunctions
if(param1.isNull() || param2.isNull()) {
return ValueSupport.NULL_VAL;
}
String s1 = param1.getAsString();
String s2 = param2.getAsString();
String s1 = param1.getAsString(ctx);
String s2 = param2.getAsString(ctx);
boolean ignoreCase = true;
if(params.length > 2) {
ignoreCase = doIgnoreCase(params[2]);
ignoreCase = doIgnoreCase(ctx, params[2]);
}
int cmp = (ignoreCase ?
s1.compareToIgnoreCase(s2) : s1.compareTo(s2));
@@ -322,8 +323,8 @@ public class DefaultTextFunctions
if(param1.isNull() || param2.isNull()) {
return ValueSupport.NULL_VAL;
}
int lv = param1.getAsLongInt();
char c = (char)(param2.getAsString().charAt(0) % 256);
int lv = param1.getAsLongInt(ctx);
char c = (char)(param2.getAsString(ctx).charAt(0) % 256);
return ValueSupport.toValue(nchars(lv, c));
}
});
@@ -331,7 +332,7 @@ public class DefaultTextFunctions
public static final Function STRREVERSE = registerFunc(new Func1("StrReverse") {
@Override
protected Value eval1(EvalContext ctx, Value param1) {
String str = param1.getAsString();
String str = param1.getAsString(ctx);
return ValueSupport.toValue(
new StringBuilder(str).reverse().toString());
}
@@ -363,8 +364,8 @@ public class DefaultTextFunctions
return str.substring(start, end);
}

private static boolean doIgnoreCase(Value paramCmp) {
int cmpType = paramCmp.getAsLongInt();
private static boolean doIgnoreCase(LocaleContext ctx, Value paramCmp) {
int cmpType = paramCmp.getAsLongInt(ctx);
switch(cmpType) {
case -1:
// vbUseCompareOption -> default is binary

+ 5
- 4
src/main/java/com/healthmarketscience/jackcess/impl/expr/DoubleValue.java View File

@@ -18,6 +18,7 @@ package com.healthmarketscience.jackcess.impl.expr;

import java.math.BigDecimal;

import com.healthmarketscience.jackcess.expr.LocaleContext;
import com.healthmarketscience.jackcess.impl.NumberFormatter;

/**
@@ -47,22 +48,22 @@ public class DoubleValue extends BaseNumericValue
}

@Override
public boolean getAsBoolean() {
public boolean getAsBoolean(LocaleContext ctx) {
return (_val.doubleValue() != 0.0d);
}

@Override
public Double getAsDouble() {
public Double getAsDouble(LocaleContext ctx) {
return _val;
}

@Override
public BigDecimal getAsBigDecimal() {
public BigDecimal getAsBigDecimal(LocaleContext ctx) {
return BigDecimal.valueOf(_val);
}

@Override
public String getAsString() {
public String getAsString(LocaleContext ctx) {
return NumberFormatter.format(_val);
}
}

+ 30
- 47
src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java View File

@@ -57,7 +57,7 @@ class ExpressionTokenizer
private static final String AM_SUFFIX = " am";
private static final String PM_SUFFIX = " pm";
// access times are based on this date (not the UTC base)
private static final String BASE_DATE = "12/30/1899 ";
private static final String BASE_DATE = "12/30/1899";
private static final String BASE_DATE_FMT = "M/d/yyyy";

private static final byte IS_OP_FLAG = 0x01;
@@ -303,12 +303,11 @@ class ExpressionTokenizer

// note that although we may parse in the time "24" format, we will
// display as the default time format
DateFormat parseDf = buf.getDateTimeFormat(type);
DateFormat df = buf.getDateTimeFormat(type.getDefaultType());
DateFormat parseDf = buf.getParseDateTimeFormat(type);

try {
return new Token(TokenType.LITERAL, parseComplete(parseDf, dateStr),
dateStr, type.getValueType(), df);
dateStr, type.getValueType());
} catch(java.text.ParseException pe) {
throw new ParseException(
"Invalid date/time literal " + dateStr + " " + buf, pe);
@@ -345,36 +344,34 @@ class ExpressionTokenizer
return null;
}

static DateFormat createParseDateFormat(TemporalConfig.Type type,
LocaleContext ctx)
static DateFormat createParseDateTimeFormat(TemporalConfig.Type type,
LocaleContext ctx)
{
TemporalConfig cfg = ctx.getTemporalConfig();
DateFormat df = ctx.createDateFormat(cfg.getDateTimeFormat(type));

TemporalConfig.Type parseType = null;
switch(type) {
case TIME:
parseType = TemporalConfig.Type.DATE_TIME;
break;
return createParseTimeFormat(TemporalConfig.Type.DATE_TIME, ctx);
case TIME_12:
parseType = TemporalConfig.Type.DATE_TIME_12;
break;
return createParseTimeFormat(TemporalConfig.Type.DATE_TIME_12, ctx);
case TIME_24:
parseType = TemporalConfig.Type.DATE_TIME_24;
break;
return createParseTimeFormat(TemporalConfig.Type.DATE_TIME_24, ctx);
default:
// use normal formatter
}

if(parseType != null) {
// we need to use a special DateFormat impl which handles parsing
// separately from formatting
String baseDate = getBaseDatePrefix(ctx);
DateFormat parseDf = ctx.createDateFormat(
cfg.getDateTimeFormat(parseType));
df = new TimeFormat(parseDf, df, baseDate);
}
TemporalConfig cfg = ctx.getTemporalConfig();
return ctx.createDateFormat(cfg.getDateTimeFormat(type));
}

return df;
private static DateFormat createParseTimeFormat(TemporalConfig.Type parseType,
LocaleContext ctx)
{
TemporalConfig cfg = ctx.getTemporalConfig();
// we need to use a special DateFormat impl which manipulates the parsed
// time-only value so it becomes the right Date value
String baseDate = getBaseDatePrefix(ctx);
DateFormat parseDf = ctx.createDateFormat(
cfg.getDateTimeFormat(parseType));
return new ParseTimeFormat(parseDf, baseDate);
}

private static String getBaseDatePrefix(LocaleContext ctx) {
@@ -562,10 +559,10 @@ class ExpressionTokenizer
return _ctx;
}

public DateFormat getDateTimeFormat(TemporalConfig.Type type) {
public DateFormat getParseDateTimeFormat(TemporalConfig.Type type) {
DateFormat df = _dateTimeFmts.get(type);
if(df == null) {
df = createParseDateFormat(type, _ctx);
df = createParseDateTimeFormat(type, _ctx);
_dateTimeFmts.put(type, df);
}
return df;
@@ -584,27 +581,20 @@ class ExpressionTokenizer
private final Object _val;
private final String _valStr;
private final Value.Type _valType;
private final DateFormat _sdf;

private Token(TokenType type, String val) {
this(type, val, val);
}

private Token(TokenType type, Object val, String valStr) {
this(type, val, valStr, null, null);
this(type, val, valStr, null);
}

private Token(TokenType type, Object val, String valStr, Value.Type valType) {
this(type, val, valStr, valType, null);
}

private Token(TokenType type, Object val, String valStr, Value.Type valType,
DateFormat sdf) {
_type = type;
_val = ((val != null) ? val : valStr);
_valStr = valStr;
_valType = valType;
_sdf = sdf;
}

public TokenType getType() {
@@ -623,10 +613,6 @@ class ExpressionTokenizer
return _valType;
}

public DateFormat getDateFormat() {
return _sdf;
}

@Override
public String toString() {
if(_type == TokenType.SPACE) {
@@ -644,25 +630,22 @@ class ExpressionTokenizer
* Special date/time format which will parse time-only strings "correctly"
* according to how access handles time-only values.
*/
private static final class TimeFormat extends DateFormat
private static final class ParseTimeFormat extends DateFormat
{
private static final long serialVersionUID = 0L;

private final DateFormat _parseDelegate;
private final DateFormat _fmtDelegate;
private final String _baseDate;

private TimeFormat(DateFormat parseDelegate, DateFormat fmtDelegate,
String baseDate)
private ParseTimeFormat(DateFormat parseDelegate, String baseDate)
{
_parseDelegate = parseDelegate;
_fmtDelegate = fmtDelegate;
_baseDate = baseDate;
}

@Override
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
return _fmtDelegate.format(date, toAppendTo, fieldPosition);
throw new UnsupportedOperationException();
}

@Override
@@ -674,12 +657,12 @@ class ExpressionTokenizer

@Override
public Calendar getCalendar() {
return _fmtDelegate.getCalendar();
return _parseDelegate.getCalendar();
}

@Override
public TimeZone getTimeZone() {
return _fmtDelegate.getTimeZone();
return _parseDelegate.getTimeZone();
}
}


+ 153
- 128
src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java View File

@@ -47,6 +47,7 @@ import com.healthmarketscience.jackcess.expr.TemporalConfig;
import com.healthmarketscience.jackcess.expr.Value;
import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.Token;
import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.TokenType;
import org.apache.commons.lang.StringUtils;


/**
@@ -125,7 +126,7 @@ public class Expressionator
},
NOT("Not", true) {
@Override public Value eval(EvalContext ctx, Value param1) {
return BuiltinOperators.not(param1);
return BuiltinOperators.not(ctx, param1);
}
},
// when a '-' immediately precedes a number, it needs "highest" precedence
@@ -179,32 +180,32 @@ public class Expressionator
},
MULT("*") {
@Override public Value eval(EvalContext ctx, Value param1, Value param2) {
return BuiltinOperators.multiply(param1, param2);
return BuiltinOperators.multiply(ctx, param1, param2);
}
},
DIV("/") {
@Override public Value eval(EvalContext ctx, Value param1, Value param2) {
return BuiltinOperators.divide(param1, param2);
return BuiltinOperators.divide(ctx, param1, param2);
}
},
INT_DIV("\\") {
@Override public Value eval(EvalContext ctx, Value param1, Value param2) {
return BuiltinOperators.intDivide(param1, param2);
return BuiltinOperators.intDivide(ctx, param1, param2);
}
},
EXP("^") {
@Override public Value eval(EvalContext ctx, Value param1, Value param2) {
return BuiltinOperators.exp(param1, param2);
return BuiltinOperators.exp(ctx, param1, param2);
}
},
CONCAT("&") {
@Override public Value eval(EvalContext ctx, Value param1, Value param2) {
return BuiltinOperators.concat(param1, param2);
return BuiltinOperators.concat(ctx, param1, param2);
}
},
MOD("Mod") {
@Override public Value eval(EvalContext ctx, Value param1, Value param2) {
return BuiltinOperators.mod(param1, param2);
return BuiltinOperators.mod(ctx, param1, param2);
}
};

@@ -224,33 +225,33 @@ public class Expressionator

private enum CompOp implements OpType {
LT("<") {
@Override public Value eval(Value param1, Value param2) {
return BuiltinOperators.lessThan(param1, param2);
@Override public Value eval(EvalContext ctx, Value param1, Value param2) {
return BuiltinOperators.lessThan(ctx, param1, param2);
}
},
LTE("<=") {
@Override public Value eval(Value param1, Value param2) {
return BuiltinOperators.lessThanEq(param1, param2);
@Override public Value eval(EvalContext ctx, Value param1, Value param2) {
return BuiltinOperators.lessThanEq(ctx, param1, param2);
}
},
GT(">") {
@Override public Value eval(Value param1, Value param2) {
return BuiltinOperators.greaterThan(param1, param2);
@Override public Value eval(EvalContext ctx, Value param1, Value param2) {
return BuiltinOperators.greaterThan(ctx, param1, param2);
}
},
GTE(">=") {
@Override public Value eval(Value param1, Value param2) {
return BuiltinOperators.greaterThanEq(param1, param2);
@Override public Value eval(EvalContext ctx, Value param1, Value param2) {
return BuiltinOperators.greaterThanEq(ctx, param1, param2);
}
},
EQ("=") {
@Override public Value eval(Value param1, Value param2) {
return BuiltinOperators.equals(param1, param2);
@Override public Value eval(EvalContext ctx, Value param1, Value param2) {
return BuiltinOperators.equals(ctx, param1, param2);
}
},
NE("<>") {
@Override public Value eval(Value param1, Value param2) {
return BuiltinOperators.notEquals(param1, param2);
@Override public Value eval(EvalContext ctx, Value param1, Value param2) {
return BuiltinOperators.notEquals(ctx, param1, param2);
}
};

@@ -265,33 +266,33 @@ public class Expressionator
return _str;
}

public abstract Value eval(Value param1, Value param2);
public abstract Value eval(EvalContext ctx, Value param1, Value param2);
}

private enum LogOp implements OpType {
AND("And") {
@Override public Value eval(Value param1, Value param2) {
return BuiltinOperators.and(param1, param2);
@Override public Value eval(EvalContext ctx, Value param1, Value param2) {
return BuiltinOperators.and(ctx, param1, param2);
}
},
OR("Or") {
@Override public Value eval(Value param1, Value param2) {
return BuiltinOperators.or(param1, param2);
@Override public Value eval(EvalContext ctx, Value param1, Value param2) {
return BuiltinOperators.or(ctx, param1, param2);
}
},
EQV("Eqv") {
@Override public Value eval(Value param1, Value param2) {
return BuiltinOperators.eqv(param1, param2);
@Override public Value eval(EvalContext ctx, Value param1, Value param2) {
return BuiltinOperators.eqv(ctx, param1, param2);
}
},
XOR("Xor") {
@Override public Value eval(Value param1, Value param2) {
return BuiltinOperators.xor(param1, param2);
@Override public Value eval(EvalContext ctx, Value param1, Value param2) {
return BuiltinOperators.xor(ctx, param1, param2);
}
},
IMP("Imp") {
@Override public Value eval(Value param1, Value param2) {
return BuiltinOperators.imp(param1, param2);
@Override public Value eval(EvalContext ctx, Value param1, Value param2) {
return BuiltinOperators.imp(ctx, param1, param2);
}
};

@@ -306,55 +307,55 @@ public class Expressionator
return _str;
}

public abstract Value eval(Value param1, Value param2);
public abstract Value eval(EvalContext ctx, 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") {
@Override public Value eval(Value param1, Object param2, Object param3) {
@Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
throw new UnsupportedOperationException();
}
},
IS_NULL("Is Null") {
@Override public Value eval(Value param1, Object param2, Object param3) {
@Override public Value eval(EvalContext ctx, 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) {
@Override public Value eval(EvalContext ctx, 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);
@Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
return BuiltinOperators.like(ctx, param1, (Pattern)param2);
}
},
NOT_LIKE("Not Like") {
@Override public Value eval(Value param1, Object param2, Object param3) {
return BuiltinOperators.notLike(param1, (Pattern)param2);
@Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
return BuiltinOperators.notLike(ctx, param1, (Pattern)param2);
}
},
BETWEEN("Between") {
@Override public Value eval(Value param1, Object param2, Object param3) {
return BuiltinOperators.between(param1, (Value)param2, (Value)param3);
@Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
return BuiltinOperators.between(ctx, 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);
@Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
return BuiltinOperators.notBetween(ctx, param1, (Value)param2, (Value)param3);
}
},
IN("In") {
@Override public Value eval(Value param1, Object param2, Object param3) {
return BuiltinOperators.in(param1, (Value[])param2);
@Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
return BuiltinOperators.in(ctx, param1, (Value[])param2);
}
},
NOT_IN("Not In") {
@Override public Value eval(Value param1, Object param2, Object param3) {
return BuiltinOperators.notIn(param1, (Value[])param2);
@Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
return BuiltinOperators.notIn(ctx, param1, (Value[])param2);
}
};

@@ -369,7 +370,7 @@ public class Expressionator
return _str;
}

public abstract Value eval(Value param1, Object param2, Object param3);
public abstract Value eval(EvalContext ctx, Value param1, Object param2, Object param3);
}

private static final Map<OpType, Integer> PRECENDENCE =
@@ -429,7 +430,7 @@ public class Expressionator
// this is handled as a literal string value, not an expression. no
// need to memo-ize cause it's a simple literal value
return new ExprWrapper(exprStr,
new ELiteralValue(Value.Type.STRING, exprStr, null), resultType);
new ELiteralValue(Value.Type.STRING, exprStr), resultType);
}

// normal expression handling
@@ -498,8 +499,7 @@ public class Expressionator

case LITERAL:

buf.setPendingExpr(new ELiteralValue(t.getValueType(), t.getValue(),
t.getDateFormat()));
buf.setPendingExpr(new ELiteralValue(t.getValueType(), t.getValue()));
break;

case OP:
@@ -1027,13 +1027,25 @@ public class Expressionator
throw new ParseException("Unexpected op string " + t.getValueStr());
}

private static StringBuilder appendLeadingExpr(
Expr expr, LocaleContext ctx, StringBuilder sb, boolean isDebug)
{
int len = sb.length();
expr.toString(ctx, sb, isDebug);
if(sb.length() > len) {
// only add space if the leading expr added some text
sb.append(" ");
}
return sb;
}

private static final class TokBuf
{
private final Type _exprType;
private final List<Token> _tokens;
private final TokBuf _parent;
private final int _parentOff;
private final ParseContext _context;
private final ParseContext _ctx;
private int _pos;
private Expr _pendingExpr;

@@ -1042,7 +1054,7 @@ public class Expressionator
}

private TokBuf(List<Token> tokens, TokBuf parent, int parentOff) {
this(parent._exprType, tokens, parent, parentOff, parent._context);
this(parent._exprType, tokens, parent, parentOff, parent._ctx);
}

private TokBuf(Type exprType, List<Token> tokens, TokBuf parent,
@@ -1051,7 +1063,7 @@ public class Expressionator
_tokens = tokens;
_parent = parent;
_parentOff = parentOff;
_context = context;
_ctx = context;
}

public Type getExprType() {
@@ -1129,7 +1141,7 @@ public class Expressionator
}

public Function getFunction(String funcName) {
return _context.getFunctionLookup().getFunction(funcName);
return _ctx.getFunctionLookup().getFunction(funcName);
}

@Override
@@ -1152,7 +1164,7 @@ public class Expressionator
sb.append(")");

if(_pendingExpr != null) {
sb.append(" [pending '").append(_pendingExpr.toDebugString())
sb.append(" [pending '").append(_pendingExpr.toDebugString(_ctx))
.append("']");
}

@@ -1184,12 +1196,13 @@ public class Expressionator
}

private static void exprListToString(
List<Expr> exprs, String sep, StringBuilder sb, boolean isDebug) {
List<Expr> exprs, String sep, LocaleContext ctx, StringBuilder sb,
boolean isDebug) {
Iterator<Expr> iter = exprs.iterator();
iter.next().toString(sb, isDebug);
iter.next().toString(ctx, sb, isDebug);
while(iter.hasNext()) {
sb.append(sep);
iter.next().toString(sb, isDebug);
iter.next().toString(ctx, sb, isDebug);
}
}

@@ -1231,7 +1244,7 @@ public class Expressionator

private static void literalStrToString(String str, StringBuilder sb) {
sb.append("\"")
.append(str.replace("\"", "\"\""))
.append(StringUtils.replace(str, "\"", "\"\""))
.append("\"");
}

@@ -1298,18 +1311,14 @@ public class Expressionator
}
}

private static Value toLiteralValue(Value.Type valType, Object value,
DateFormat sdf)
{
private static Value toLiteralValue(Value.Type valType, Object value) {
switch(valType) {
case STRING:
return ValueSupport.toValue((String)value);
case DATE:
return new DateValue((Date)value, sdf);
case TIME:
return new TimeValue((Date)value, sdf);
case DATE_TIME:
return new DateTimeValue((Date)value, sdf);
return ValueSupport.toValue(valType, (Date)value);
case LONG:
return ValueSupport.toValue((Integer)value);
case DOUBLE:
@@ -1375,31 +1384,28 @@ public class Expressionator

private static abstract class Expr
{
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
toString(sb, false);
return sb.toString();
public String toCleanString(LocaleContext ctx) {
return toString(ctx, new StringBuilder(), false).toString();
}

public String toDebugString() {
StringBuilder sb = new StringBuilder();
toString(sb, true);
return sb.toString();
public String toDebugString(LocaleContext ctx) {
return toString(ctx, new StringBuilder(), true).toString();
}

protected boolean isValidationExpr() {
return false;
}

protected void toString(StringBuilder sb, boolean isDebug) {
protected StringBuilder toString(
LocaleContext ctx, StringBuilder sb, boolean isDebug) {
if(isDebug) {
sb.append("<").append(getClass().getSimpleName()).append(">{");
}
toExprString(sb, isDebug);
toExprString(ctx, sb, isDebug);
if(isDebug) {
sb.append("}");
}
return sb;
}

protected Expr resolveOrderOfOperations() {
@@ -1464,7 +1470,8 @@ public class Expressionator

public abstract void collectIdentifiers(Collection<Identifier> identifiers);

protected abstract void toExprString(StringBuilder sb, boolean isDebug);
protected abstract void toExprString(
LocaleContext ctx, StringBuilder sb, boolean isDebug);
}

private static final class EConstValue extends Expr
@@ -1493,7 +1500,8 @@ public class Expressionator
}

@Override
protected void toExprString(StringBuilder sb, boolean isDebug) {
protected void toExprString(
LocaleContext ctx, StringBuilder sb, boolean isDebug) {
sb.append(_str);
}
}
@@ -1513,8 +1521,11 @@ public class Expressionator
// none
}
@Override
protected void toExprString(StringBuilder sb, boolean isDebug) {
sb.append("<THIS_COL>");
protected void toExprString(
LocaleContext ctx, StringBuilder sb, boolean isDebug) {
if(isDebug) {
sb.append("<THIS_COL>");
}
}
}

@@ -1522,9 +1533,8 @@ public class Expressionator
{
private final Value _val;

private ELiteralValue(Value.Type valType, Object value,
DateFormat sdf) {
_val = toLiteralValue(valType, value, sdf);
private ELiteralValue(Value.Type valType, Object value) {
_val = toLiteralValue(valType, value);
}

@Override
@@ -1543,11 +1553,12 @@ public class Expressionator
}

@Override
protected void toExprString(StringBuilder sb, boolean isDebug) {
protected void toExprString(
LocaleContext ctx, StringBuilder sb, boolean isDebug) {
if(_val.getType() == Value.Type.STRING) {
literalStrToString((String)_val.get(), sb);
} else if(_val.getType().isTemporal()) {
sb.append("#").append(_val.getAsString()).append("#");
sb.append("#").append(_val.getAsString(ctx)).append("#");
} else {
sb.append(_val.get());
}
@@ -1578,7 +1589,8 @@ public class Expressionator
}

@Override
protected void toExprString(StringBuilder sb, boolean isDebug) {
protected void toExprString(
LocaleContext ctx, StringBuilder sb, boolean isDebug) {
sb.append(_identifier);
}
}
@@ -1612,9 +1624,10 @@ public class Expressionator
}

@Override
protected void toExprString(StringBuilder sb, boolean isDebug) {
protected void toExprString(
LocaleContext ctx, StringBuilder sb, boolean isDebug) {
sb.append("(");
_expr.toString(sb, isDebug);
_expr.toString(ctx, sb, isDebug);
sb.append(")");
}
}
@@ -1647,11 +1660,12 @@ public class Expressionator
}

@Override
protected void toExprString(StringBuilder sb, boolean isDebug) {
protected void toExprString(
LocaleContext ctx, StringBuilder sb, boolean isDebug) {
sb.append(_func.getName()).append("(");

if(!_params.isEmpty()) {
exprListToString(_params, ",", sb, isDebug);
exprListToString(_params, ",", ctx, sb, isDebug);
}

sb.append(")");
@@ -1703,10 +1717,11 @@ public class Expressionator
}

@Override
protected void toExprString(StringBuilder sb, boolean isDebug) {
_left.toString(sb, isDebug);
sb.append(" ").append(_op).append(" ");
_right.toString(sb, isDebug);
protected void toExprString(
LocaleContext ctx, StringBuilder sb, boolean isDebug) {
appendLeadingExpr(_left, ctx, sb, isDebug)
.append(_op).append(" ");
_right.toString(ctx, sb, isDebug);
}
}

@@ -1761,12 +1776,13 @@ public class Expressionator
}

@Override
protected void toExprString(StringBuilder sb, boolean isDebug) {
protected void toExprString(
LocaleContext ctx, StringBuilder sb, boolean isDebug) {
sb.append(_op);
if(isDebug || ((UnaryOp)_op).needsSpace()) {
sb.append(" ");
}
_expr.toString(sb, isDebug);
_expr.toString(ctx, sb, isDebug);
}
}

@@ -1783,7 +1799,7 @@ public class Expressionator

@Override
public Value eval(EvalContext ctx) {
return ((CompOp)_op).eval(_left.eval(ctx), _right.eval(ctx));
return ((CompOp)_op).eval(ctx, _left.eval(ctx), _right.eval(ctx));
}
}

@@ -1794,13 +1810,14 @@ public class Expressionator
}

@Override
protected void toExprString(StringBuilder sb, boolean isDebug) {
protected void toExprString(
LocaleContext ctx, StringBuilder sb, boolean isDebug) {
// only output the full "implicit" comparison in debug mode
if(isDebug) {
super.toExprString(sb, isDebug);
super.toExprString(ctx, sb, isDebug);
} else {
// just output the explicit part of the expression
_right.toString(sb, isDebug);
_right.toString(ctx, sb, isDebug);
}
}
}
@@ -1821,7 +1838,7 @@ public class Expressionator

// logical operations do short circuit evaluation, so we need to delay
// computing results until necessary
return ((LogOp)_op).eval(new DelayedValue(_left, ctx),
return ((LogOp)_op).eval(ctx, new DelayedValue(_left, ctx),
new DelayedValue(_right, ctx));
}
}
@@ -1873,13 +1890,14 @@ public class Expressionator

@Override
public Value eval(EvalContext ctx) {
return _op.eval(_expr.eval(ctx), null, null);
return _op.eval(ctx, _expr.eval(ctx), null, null);
}

@Override
protected void toExprString(StringBuilder sb, boolean isDebug) {
_expr.toString(sb, isDebug);
sb.append(" ").append(_op);
protected void toExprString(
LocaleContext ctx, StringBuilder sb, boolean isDebug) {
appendLeadingExpr(_expr, ctx, sb, isDebug)
.append(_op);
}
}

@@ -1903,13 +1921,14 @@ public class Expressionator

@Override
public Value eval(EvalContext ctx) {
return _op.eval(_expr.eval(ctx), getPattern(), null);
return _op.eval(ctx, _expr.eval(ctx), getPattern(), null);
}

@Override
protected void toExprString(StringBuilder sb, boolean isDebug) {
_expr.toString(sb, isDebug);
sb.append(" ").append(_op).append(" ");
protected void toExprString(
LocaleContext ctx, StringBuilder sb, boolean isDebug) {
appendLeadingExpr(_expr, ctx, sb, isDebug)
.append(_op).append(" ");
literalStrToString(_patternStr, sb);
if(isDebug) {
sb.append("(").append(getPattern()).append(")");
@@ -1933,7 +1952,7 @@ public class Expressionator

@Override
public Value eval(EvalContext ctx) {
return _op.eval(_expr.eval(ctx),
return _op.eval(ctx, _expr.eval(ctx),
exprListToDelayedValues(_exprs, ctx), null);
}

@@ -1945,10 +1964,11 @@ public class Expressionator
}

@Override
protected void toExprString(StringBuilder sb, boolean isDebug) {
_expr.toString(sb, isDebug);
sb.append(" ").append(_op).append(" (");
exprListToString(_exprs, ",", sb, isDebug);
protected void toExprString(
LocaleContext ctx, StringBuilder sb, boolean isDebug) {
appendLeadingExpr(_expr, ctx, sb, isDebug)
.append(_op).append(" (");
exprListToString(_exprs, ",", ctx, sb, isDebug);
sb.append(")");
}
}
@@ -1981,7 +2001,7 @@ public class Expressionator

@Override
public Value eval(EvalContext ctx) {
return _op.eval(_expr.eval(ctx),
return _op.eval(ctx, _expr.eval(ctx),
new DelayedValue(_startRangeExpr, ctx),
new DelayedValue(_endRangeExpr, ctx));
}
@@ -1994,12 +2014,13 @@ public class Expressionator
}

@Override
protected void toExprString(StringBuilder sb, boolean isDebug) {
_expr.toString(sb, isDebug);
sb.append(" ").append(_op).append(" ");
_startRangeExpr.toString(sb, isDebug);
protected void toExprString(
LocaleContext ctx, StringBuilder sb, boolean isDebug) {
appendLeadingExpr(_expr, ctx, sb, isDebug)
.append(_op).append(" ");
_startRangeExpr.toString(ctx, sb, isDebug);
sb.append(" And ");
_endRangeExpr.toString(sb, isDebug);
_endRangeExpr.toString(ctx, sb, isDebug);
}
}

@@ -2016,14 +2037,18 @@ public class Expressionator
_expr = expr;
}

public String toDebugString() {
return _expr.toDebugString();
public String toDebugString(LocaleContext ctx) {
return _expr.toDebugString(ctx);
}

public String toRawString() {
return _rawExprStr;
}

public String toCleanString(LocaleContext ctx) {
return _expr.toCleanString(ctx);
}

public boolean isConstant() {
return _expr.isConstant();
}
@@ -2034,7 +2059,7 @@ public class Expressionator

@Override
public String toString() {
return _expr.toString();
return toRawString();
}

protected Object evalValue(Value.Type resultType, EvalContext ctx) {
@@ -2052,17 +2077,17 @@ public class Expressionator
// FIXME possibly do some type coercion. are there conversions here which don't work elsewhere? (string -> date, string -> number)?
switch(resultType) {
case STRING:
return val.getAsString();
return val.getAsString(ctx);
case DATE:
case TIME:
case DATE_TIME:
return val.getAsDateTime(ctx);
case LONG:
return val.getAsLongInt();
return val.getAsLongInt(ctx);
case DOUBLE:
return val.getAsDouble();
return val.getAsDouble(ctx);
case BIG_DEC:
return val.getAsBigDecimal();
return val.getAsBigDecimal(ctx);
default:
throw new IllegalStateException("unexpected result type " + resultType);
}
@@ -2076,7 +2101,7 @@ public class Expressionator
throw new EvalException("Condition evaluated to Null");
}

return val.getAsBoolean();
return val.getAsBoolean(ctx);
}
}


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

@@ -227,7 +227,7 @@ public class FunctionSupport
if(result.isNull()) {
// non-variant version does not do null-propagation, so force
// exception to be thrown here
result.getAsString();
result.getAsString(ctx);
}
return result;
}

+ 6
- 4
src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java View File

@@ -18,6 +18,8 @@ package com.healthmarketscience.jackcess.impl.expr;

import java.math.BigDecimal;

import com.healthmarketscience.jackcess.expr.LocaleContext;

/**
*
* @author James Ahlborn
@@ -45,22 +47,22 @@ public class LongValue extends BaseNumericValue
}

@Override
public boolean getAsBoolean() {
public boolean getAsBoolean(LocaleContext ctx) {
return (_val.longValue() != 0L);
}

@Override
public Integer getAsLongInt() {
public Integer getAsLongInt(LocaleContext ctx) {
return _val;
}

@Override
public BigDecimal getAsBigDecimal() {
public BigDecimal getAsBigDecimal(LocaleContext ctx) {
return BigDecimal.valueOf(_val);
}

@Override
public String getAsString() {
public String getAsString(LocaleContext ctx) {
return _val.toString();
}
}

+ 18
- 11
src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java View File

@@ -20,6 +20,10 @@ import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.regex.Pattern;

import com.healthmarketscience.jackcess.expr.LocaleContext;
import org.apache.commons.lang.CharUtils;
import org.apache.commons.lang.StringUtils;

/**
*
* @author James Ahlborn
@@ -51,32 +55,32 @@ public class StringValue extends BaseValue
}

@Override
public boolean getAsBoolean() {
public boolean getAsBoolean(LocaleContext ctx) {
// ms access seems to treat strings as "true"
return true;
}

@Override
public String getAsString() {
public String getAsString(LocaleContext ctx) {
return _val;
}

@Override
public Integer getAsLongInt() {
return roundToLongInt();
public Integer getAsLongInt(LocaleContext ctx) {
return roundToLongInt(ctx);
}

@Override
public Double getAsDouble() {
return getNumber().doubleValue();
public Double getAsDouble(LocaleContext ctx) {
return getNumber(ctx).doubleValue();
}

@Override
public BigDecimal getAsBigDecimal() {
return getNumber();
public BigDecimal getAsBigDecimal(LocaleContext ctx) {
return getNumber(ctx);
}

protected BigDecimal getNumber() {
protected BigDecimal getNumber(LocaleContext ctx) {
if(_num instanceof BigDecimal) {
return (BigDecimal)_num;
}
@@ -89,8 +93,11 @@ public class StringValue extends BaseValue
if(tmpVal.length() > 0) {

if(tmpVal.charAt(0) != NUMBER_BASE_PREFIX) {
// parse using standard numeric support
// FIXME, this should handle grouping separator, but needs ctx
// parse using standard numeric support, after discarding any
// grouping separators
char groupSepChar = ctx.getNumericConfig().getDecimalFormatSymbols()
.getGroupingSeparator();
tmpVal = StringUtils.remove(tmpVal, groupSepChar);
_num = ValueSupport.normalize(new BigDecimal(tmpVal));
return (BigDecimal)_num;
}

+ 0
- 37
src/main/java/com/healthmarketscience/jackcess/impl/expr/TimeValue.java View File

@@ -1,37 +0,0 @@
/*
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.impl.expr;

import java.text.DateFormat;
import java.util.Date;

/**
*
* @author James Ahlborn
*/
public class TimeValue extends BaseDateValue
{

public TimeValue(Date val, DateFormat fmt)
{
super(val, fmt);
}

public Type getType() {
return Type.TIME;
}
}

+ 7
- 36
src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java View File

@@ -85,46 +85,17 @@ public class ValueSupport
return new BigDecimalValue(normalize(s));
}

public static Value toValue(Value.Type type, double dd, DateFormat fmt) {
return toValue(type, new Date(ColumnImpl.fromDateDouble(
dd, fmt.getCalendar())), fmt);
}

public static Value toValue(LocaleContext ctx, Value.Type type, Date d) {
return toValue(type, d, getDateFormatForType(ctx, type));
}

public static Value toValue(Value.Type type, Date d, DateFormat fmt) {
switch(type) {
case DATE:
return new DateValue(d, fmt);
case TIME:
return new TimeValue(d, fmt);
case DATE_TIME:
return new DateTimeValue(d, fmt);
default:
throw new EvalException("Unexpected date/time type " + type);
}
}

static Value toDateValue(LocaleContext ctx, Value.Type type, double v,
Value param1, Value param2)
public static Value toDateValue(LocaleContext ctx, Value.Type type, double dd)
{
DateFormat fmt = null;
if((param1 instanceof BaseDateValue) && (param1.getType() == type)) {
fmt = ((BaseDateValue)param1).getFormat();
} else if((param2 instanceof BaseDateValue) && (param2.getType() == type)) {
fmt = ((BaseDateValue)param2).getFormat();
} else {
fmt = getDateFormatForType(ctx, type);
}

Date d = new Date(ColumnImpl.fromDateDouble(v, fmt.getCalendar()));
return toValue(type, new Date(
ColumnImpl.fromDateDouble(dd, ctx.getCalendar())));
}

return toValue(type, d, fmt);
public static Value toValue(Value.Type type, Date d) {
return new DateTimeValue(type, d);
}

static DateFormat getDateFormatForType(LocaleContext ctx, Value.Type type) {
public static DateFormat getDateFormatForType(LocaleContext ctx, Value.Type type) {
String fmtStr = null;
switch(type) {
case DATE:

+ 20
- 8
src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java View File

@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.FileReader;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import javax.script.Bindings;
import javax.script.SimpleBindings;
@@ -31,6 +32,7 @@ import com.healthmarketscience.jackcess.expr.EvalContext;
import com.healthmarketscience.jackcess.expr.Expression;
import com.healthmarketscience.jackcess.expr.FunctionLookup;
import com.healthmarketscience.jackcess.expr.Identifier;
import com.healthmarketscience.jackcess.expr.NumericConfig;
import com.healthmarketscience.jackcess.expr.ParseException;
import com.healthmarketscience.jackcess.expr.TemporalConfig;
import com.healthmarketscience.jackcess.expr.Value;
@@ -98,7 +100,7 @@ public class ExpressionatorTest extends TestCase
validateExpr("' \"A\" '", "<ELiteralValue>{\" \"\"A\"\" \"}",
"\" \"\"A\"\" \"");

validateExpr("<=1 And >=0", "<ELogicalOp>{<ECompOp>{<EThisValue>{<THIS_COL>} <= <ELiteralValue>{1}} And <ECompOp>{<EThisValue>{<THIS_COL>} >= <ELiteralValue>{0}}}", "<THIS_COL> <= 1 And <THIS_COL> >= 0");
validateExpr("<=1 And >=0", "<ELogicalOp>{<ECompOp>{<EThisValue>{<THIS_COL>} <= <ELiteralValue>{1}} And <ECompOp>{<EThisValue>{<THIS_COL>} >= <ELiteralValue>{0}}}", "<= 1 And >= 0");
}

private static void doTestSimpleBinOp(String opName, String... ops) throws Exception
@@ -387,6 +389,7 @@ public class ExpressionatorTest extends TestCase
assertEquals(37d, eval("=\" &h1A \" + 11"));
assertEquals(37d, eval("=\" &h1a \" + 11"));
assertEquals(37d, eval("=\" &O32 \" + 11"));
assertEquals(1037d, eval("=\"1,025\" + 12"));

evalFail(("=12 - \"foo\""), RuntimeException.class);
evalFail(("=\"foo\" - 12"), RuntimeException.class);
@@ -403,12 +406,12 @@ public class ExpressionatorTest extends TestCase
public void testLikeExpression() throws Exception
{
validateExpr("Like \"[abc]*\"", "<ELikeOp>{<EThisValue>{<THIS_COL>} Like \"[abc]*\"([abc].*)}",
"<THIS_COL> Like \"[abc]*\"");
"Like \"[abc]*\"");
assertTrue(evalCondition("Like \"[abc]*\"", "afcd"));
assertFalse(evalCondition("Like \"[abc]*\"", "fcd"));

validateExpr("Like \"[abc*\"", "<ELikeOp>{<EThisValue>{<THIS_COL>} Like \"[abc*\"((?!))}",
"<THIS_COL> Like \"[abc*\"");
validateExpr("Like \"[abc*\"", "<ELikeOp>{<EThisValue>{<THIS_COL>} Like \"[abc*\"((?!))}",
"Like \"[abc*\"");
assertFalse(evalCondition("Like \"[abc*\"", "afcd"));
assertFalse(evalCondition("Like \"[abc*\"", "fcd"));
assertTrue(evalCondition("Not Like \"[abc*\"", "fcd"));
@@ -500,17 +503,17 @@ public class ExpressionatorTest extends TestCase

private static void validateExpr(String exprStr, String debugStr,
String cleanStr) {
TestContext ctx = new TestContext();
Expression expr = Expressionator.parse(
Expressionator.Type.FIELD_VALIDATOR, exprStr, null,
new TestContext());
String foundDebugStr = expr.toDebugString();
Expressionator.Type.FIELD_VALIDATOR, exprStr, null, ctx);
String foundDebugStr = expr.toDebugString(ctx);
if(foundDebugStr.startsWith("<EImplicitCompOp>")) {
assertEquals("<EImplicitCompOp>{<EThisValue>{<THIS_COL>} = " +
debugStr + "}", foundDebugStr);
} else {
assertEquals(debugStr, foundDebugStr);
}
assertEquals(cleanStr, expr.toString());
assertEquals(cleanStr, expr.toCleanString(ctx));
assertEquals(exprStr, expr.toRawString());
}

@@ -588,6 +591,15 @@ public class ExpressionatorTest extends TestCase
return sdf;
}

public Calendar getCalendar() {
return createDateFormat(getTemporalConfig().getDefaultDateTimeFormat())
.getCalendar();
}

public NumericConfig getNumericConfig() {
return NumericConfig.US_NUMERIC_CONFIG;
}

public FunctionLookup getFunctionLookup() {
return DefaultFunctions.LOOKUP;
}

Loading…
Cancel
Save