From 443c9fef2bb52d8564e400fe429a22412f0f2cc6 Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Thu, 4 Oct 2018 02:07:32 +0000 Subject: [PATCH] rework public expression api with better locale handling; support parsing of number strings with grouping separators git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1203 f203690c-595d-4dc9-a70b-905162fa7fd2 --- .../jackcess/expr/EvalConfig.java | 12 + .../jackcess/expr/Expression.java | 14 +- .../jackcess/expr/LocaleContext.java | 12 + .../jackcess/expr/NumericConfig.java | 45 +++ .../jackcess/expr/Value.java | 12 +- .../jackcess/expr/package-info.java | 2 + .../jackcess/impl/BaseEvalContext.java | 23 +- .../jackcess/impl/DBEvalContext.java | 19 ++ .../jackcess/impl/DatabaseImpl.java | 14 +- .../jackcess/impl/expr/BaseDateValue.java | 83 ------ .../jackcess/impl/expr/BaseDelayedValue.java | 24 +- .../jackcess/impl/expr/BaseNumericValue.java | 15 +- .../jackcess/impl/expr/BaseValue.java | 18 +- .../jackcess/impl/expr/BigDecimalValue.java | 7 +- .../jackcess/impl/expr/BuiltinOperators.java | 222 +++++++------- .../jackcess/impl/expr/DateTimeValue.java | 59 +++- .../jackcess/impl/expr/DateValue.java | 36 --- .../impl/expr/DefaultDateFunctions.java | 101 +++---- .../impl/expr/DefaultFinancialFunctions.java | 88 +++--- .../jackcess/impl/expr/DefaultFunctions.java | 34 +-- .../impl/expr/DefaultNumberFunctions.java | 38 +-- .../impl/expr/DefaultTextFunctions.java | 71 ++--- .../jackcess/impl/expr/DoubleValue.java | 9 +- .../impl/expr/ExpressionTokenizer.java | 77 ++--- .../jackcess/impl/expr/Expressionator.java | 281 ++++++++++-------- .../jackcess/impl/expr/FunctionSupport.java | 2 +- .../jackcess/impl/expr/LongValue.java | 10 +- .../jackcess/impl/expr/StringValue.java | 29 +- .../jackcess/impl/expr/TimeValue.java | 37 --- .../jackcess/impl/expr/ValueSupport.java | 43 +-- .../impl/expr/ExpressionatorTest.java | 28 +- 31 files changed, 730 insertions(+), 735 deletions(-) create mode 100644 src/main/java/com/healthmarketscience/jackcess/expr/NumericConfig.java delete mode 100644 src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java delete mode 100644 src/main/java/com/healthmarketscience/jackcess/impl/expr/DateValue.java delete mode 100644 src/main/java/com/healthmarketscience/jackcess/impl/expr/TimeValue.java diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/EvalConfig.java b/src/main/java/com/healthmarketscience/jackcess/expr/EvalConfig.java index 0f9a859..d09955e 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/EvalConfig.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/EvalConfig.java @@ -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 */ diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/Expression.java b/src/main/java/com/healthmarketscience/jackcess/expr/Expression.java index 504d697..15de064 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/Expression.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/Expression.java @@ -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(); diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java b/src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java index 8a9a649..d086836 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java @@ -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(); } diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/NumericConfig.java b/src/main/java/com/healthmarketscience/jackcess/expr/NumericConfig.java new file mode 100644 index 0000000..9ae09b3 --- /dev/null +++ b/src/main/java/com/healthmarketscience/jackcess/expr/NumericConfig.java @@ -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; + } +} diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/Value.java b/src/main/java/com/healthmarketscience/jackcess/expr/Value.java index 210c170..f9bbf96 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/Value.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/Value.java @@ -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); } diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/package-info.java b/src/main/java/com/healthmarketscience/jackcess/expr/package-info.java index 05c72e6..1f46139 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/package-info.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/package-info.java @@ -63,6 +63,8 @@ limitations under the License. * evaluation context for a given {@link com.healthmarketscience.jackcess.Database} instance. *
  • {@link com.healthmarketscience.jackcess.expr.TemporalConfig} encapsulates date/time formatting options for * expression evaluation.
  • + *
  • {@link com.healthmarketscience.jackcess.expr.NumericConfig} encapsulates number formatting options for + * expression evaluation.
  • *
  • {@link com.healthmarketscience.jackcess.expr.FunctionLookup} provides a source for {@link com.healthmarketscience.jackcess.expr.Function} instances * used during expression evaluation.
  • *
  • {@link com.healthmarketscience.jackcess.expr.EvalException} wrapper exception thrown for failures which occur diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java b/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java index 058aea8..bd7298e 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java @@ -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 "{" + _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(); } } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java b/src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java index e791c0b..6bc2326 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java @@ -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 _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; + } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java index 5878a47..6831e99 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java @@ -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 diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java deleted file mode 100644 index 188416a..0000000 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java +++ /dev/null @@ -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()); - } -} diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java index e8ae339..6805d07 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java @@ -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(); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java index eb1ac7e..cdf686c 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java @@ -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(); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java index 0f081dd..d83af09 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java @@ -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(); } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BigDecimalValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BigDecimalValue.java index 89e4004..b847b59 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BigDecimalValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BigDecimalValue.java @@ -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; } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java index a655a1a..f53a6be 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java @@ -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 diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java index abc047f..0f6937f 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java @@ -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)); } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DateValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DateValue.java deleted file mode 100644 index 558e3ab..0000000 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DateValue.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java index b062a05..87389d5 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java @@ -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; } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFinancialFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFinancialFunctions.java index 3ca6725..c5a2356 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFinancialFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFinancialFunctions.java @@ -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); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java index 20de8ca..f794a62 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java @@ -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 diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java index 278306e..4473af4 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java @@ -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))); } }); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java index bd1aac2..4a6da20 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java @@ -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 diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DoubleValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DoubleValue.java index 7f68ad8..fbbdd96 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DoubleValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DoubleValue.java @@ -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); } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java index c75fae0..5b2864a 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java @@ -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(); } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java index d53d6d3..d2bb847 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java @@ -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 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 _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 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 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 exprs, String sep, StringBuilder sb, boolean isDebug) { + List exprs, String sep, LocaleContext ctx, StringBuilder sb, + boolean isDebug) { Iterator 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 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(""); + protected void toExprString( + LocaleContext ctx, StringBuilder sb, boolean isDebug) { + if(isDebug) { + sb.append(""); + } } } @@ -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); } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/FunctionSupport.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/FunctionSupport.java index bc87f59..483ecd8 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/FunctionSupport.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/FunctionSupport.java @@ -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; } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java index 3a47a84..30025ad 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java @@ -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(); } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java index 781faf4..d3b829b 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java @@ -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; } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/TimeValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/TimeValue.java deleted file mode 100644 index cedb461..0000000 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/TimeValue.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java index b0924f3..06833de 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java @@ -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: diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java index 5b623a0..d7b5a00 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java @@ -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\" '", "{\" \"\"A\"\" \"}", "\" \"\"A\"\" \""); - validateExpr("<=1 And >=0", "{{{} <= {1}} And {{} >= {0}}}", " <= 1 And >= 0"); + validateExpr("<=1 And >=0", "{{{} <= {1}} And {{} >= {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]*\"", "{{} Like \"[abc]*\"([abc].*)}", - " Like \"[abc]*\""); + "Like \"[abc]*\""); assertTrue(evalCondition("Like \"[abc]*\"", "afcd")); assertFalse(evalCondition("Like \"[abc]*\"", "fcd")); - validateExpr("Like \"[abc*\"", "{{} Like \"[abc*\"((?!))}", - " Like \"[abc*\""); + validateExpr("Like \"[abc*\"", "{{} 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("")) { assertEquals("{{} = " + 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; } -- 2.39.5