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