]> source.dussan.org Git - jackcess.git/commitdiff
rework public expression api with better locale handling; support parsing of number...
authorJames Ahlborn <jtahlborn@yahoo.com>
Thu, 4 Oct 2018 02:07:32 +0000 (02:07 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Thu, 4 Oct 2018 02:07:32 +0000 (02:07 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1203 f203690c-595d-4dc9-a70b-905162fa7fd2

31 files changed:
src/main/java/com/healthmarketscience/jackcess/expr/EvalConfig.java
src/main/java/com/healthmarketscience/jackcess/expr/Expression.java
src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java
src/main/java/com/healthmarketscience/jackcess/expr/NumericConfig.java [new file with mode: 0644]
src/main/java/com/healthmarketscience/jackcess/expr/Value.java
src/main/java/com/healthmarketscience/jackcess/expr/package-info.java
src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java
src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java
src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDateValue.java [deleted file]
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/BigDecimalValue.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/DateValue.java [deleted file]
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFinancialFunctions.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/DoubleValue.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/FunctionSupport.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/TimeValue.java [deleted file]
src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java
src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java

index 0f9a859bc1d23014733f715fd8948b22e5b5880c..d09955e121bba262a71837fdbd7d21598ae9619e 100644 (file)
@@ -41,6 +41,18 @@ public interface EvalConfig
    */
   public void setTemporalConfig(TemporalConfig temporal);
 
+  /**
+   * @return the currently configured NumericConfig
+   */
+  public NumericConfig getNumericConfig();
+
+  /**
+   * Sets the NumericConfig for use when evaluating expressions.  The default
+   * date/time formatting is US based, so this may need to be modified when
+   * interacting with {@link Database} instances from other locales.
+   */
+  public void setNumericConfig(NumericConfig numeric);
+
   /**
    * @return the currently configured FunctionLookup
    */
index 504d69757c97eaa25e7a214cea38894ff2e4d0d5..15de064b5b312643d52906ba9b8f3e78f5cd6db0 100644 (file)
@@ -44,12 +44,18 @@ public interface Expression
    * @return a detailed string which indicates how the expression was
    *         interpreted by the expression evaluation engine.
    */
-  public String toDebugString();
+  public String toDebugString(LocaleContext ctx);
 
   /**
-   * @return the original, unparsed expression string.  By contrast, the {@link
-   *         Object#toString} result may return a value which has been cleaned
-   *         up with respect to the original expression.
+   * @return a parsed and re-formated version of the expression.  This may
+   *         look slightly different than the original, raw string, although
+   *         it should be an equivalent expression.
+   */
+  public String toCleanString(LocaleContext ctx);
+
+  /**
+   * @return the original, unparsed expression string.  This is the same as
+   *         the value which will be returned by {@link Object#toString}.
    */
   public String toRawString();
 
index 8a9a6499332b5246b87436f8003d6938b73d4981..d086836bbfa18b83eaa11a5c2dbc314b83e9b08d 100644 (file)
@@ -17,6 +17,7 @@ limitations under the License.
 package com.healthmarketscience.jackcess.expr;
 
 import java.text.SimpleDateFormat;
+import java.util.Calendar;
 
 /**
  * LocaleContext encapsulates all shared localization state for expression
@@ -38,4 +39,15 @@ public interface LocaleContext
    */
   public SimpleDateFormat createDateFormat(String formatStr);
 
+  /**
+   * @return an appropriately configured (i.e. TimeZone and other date/time
+   *         flags) Calendar.
+   */
+  public Calendar getCalendar();
+
+  /**
+   * @return the currently configured NumericConfig (from the
+   *         {@link EvalConfig})
+   */
+  public NumericConfig getNumericConfig();
 }
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 (file)
index 0000000..9ae09b3
--- /dev/null
@@ -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;
+  }
+}
index 210c170199b9df871aed1c5afa5e902598bed0d5..f9bbf9667bb96bfc8ac1d77d33f1a8c425da9368 100644 (file)
@@ -85,30 +85,30 @@ public interface Value
   /**
    * @return this primitive value converted to a boolean
    */
-  public boolean getAsBoolean();
+  public boolean getAsBoolean(LocaleContext ctx);
 
   /**
    * @return this primitive value converted to a String
    */
-  public String getAsString();
+  public String getAsString(LocaleContext ctx);
 
   /**
    * @return this primitive value converted to a Date
    */
-  public Date getAsDateTime(EvalContext ctx);
+  public Date getAsDateTime(LocaleContext ctx);
 
   /**
    * @return this primitive value converted (rounded) to an int
    */
-  public Integer getAsLongInt();
+  public Integer getAsLongInt(LocaleContext ctx);
 
   /**
    * @return this primitive value converted (rounded) to a double
    */
-  public Double getAsDouble();
+  public Double getAsDouble(LocaleContext ctx);
 
   /**
    * @return this primitive value converted to a BigDecimal
    */
-  public BigDecimal getAsBigDecimal();
+  public BigDecimal getAsBigDecimal(LocaleContext ctx);
 }
index 05c72e6ca1dd8574294a168d0f5154bd29a18705..1f461392d25a4609e559bfd23aef9f9f1ecb6ef1 100644 (file)
@@ -63,6 +63,8 @@ limitations under the License.
  *     evaluation context for a given {@link com.healthmarketscience.jackcess.Database} instance.</li>
  * <li>{@link com.healthmarketscience.jackcess.expr.TemporalConfig} encapsulates date/time formatting options for
  *     expression evaluation.</li>
+ * <li>{@link com.healthmarketscience.jackcess.expr.NumericConfig} encapsulates number formatting options for
+ *     expression evaluation.</li>
  * <li>{@link com.healthmarketscience.jackcess.expr.FunctionLookup} provides a source for {@link com.healthmarketscience.jackcess.expr.Function} instances
  *     used during expression evaluation.</li>
  * <li>{@link com.healthmarketscience.jackcess.expr.EvalException} wrapper exception thrown for failures which occur
index 058aea86e0e0e1f11bc71db1de25f6ca7abcb01e..bd7298e2a4c5c3ca85ea868c53e51dc14fe79a5d 100644 (file)
@@ -19,6 +19,7 @@ package com.healthmarketscience.jackcess.impl;
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.text.SimpleDateFormat;
+import java.util.Calendar;
 import java.util.Collection;
 import java.util.Date;
 import java.util.EnumMap;
@@ -31,6 +32,8 @@ import com.healthmarketscience.jackcess.expr.EvalContext;
 import com.healthmarketscience.jackcess.expr.EvalException;
 import com.healthmarketscience.jackcess.expr.Expression;
 import com.healthmarketscience.jackcess.expr.Identifier;
+import com.healthmarketscience.jackcess.expr.LocaleContext;
+import com.healthmarketscience.jackcess.expr.NumericConfig;
 import com.healthmarketscience.jackcess.expr.TemporalConfig;
 import com.healthmarketscience.jackcess.expr.Value;
 import com.healthmarketscience.jackcess.impl.expr.Expressionator;
@@ -82,6 +85,14 @@ public abstract class BaseEvalContext implements EvalContext
     return _dbCtx.createDateFormat(formatStr);
   }
 
+  public Calendar getCalendar() {
+    return _dbCtx.getCalendar();
+  }
+
+  public NumericConfig getNumericConfig() {
+    return _dbCtx.getNumericConfig();
+  }
+
   public float getRandom(Integer seed) {
     return _dbCtx.getRandom(seed);
   }
@@ -142,7 +153,7 @@ public abstract class BaseEvalContext implements EvalContext
       case DATE:
       case TIME:
       case DATE_TIME:
-        return ValueSupport.toValue(this, vType, (Date)val);
+        return ValueSupport.toValue(vType, (Date)val);
       case LONG:
         Integer i = ((val instanceof Integer) ? (Integer)val :
                      ((Number)val).intValue());
@@ -191,14 +202,18 @@ public abstract class BaseEvalContext implements EvalContext
       return getExpr().eval(ctx);
     }
 
-    public String toDebugString() {
-      return "<raw>{" + _exprStr + "}";
+    public String toDebugString(LocaleContext ctx) {
+      return getExpr().toDebugString(ctx);
     }
 
     public String toRawString() {
       return _exprStr;
     }
 
+    public String toCleanString(LocaleContext ctx) {
+      return getExpr().toCleanString(ctx);
+    }
+
     public boolean isConstant() {
       return getExpr().isConstant();
     }
@@ -209,7 +224,7 @@ public abstract class BaseEvalContext implements EvalContext
 
     @Override
     public String toString() {
-      return _exprStr;
+      return toRawString();
     }
   }
 }
index e791c0b6d87ffa404290bf6416fca59c460e419d..6bc2326714cc2989561b04e68f8109f62f8cb92f 100644 (file)
@@ -17,6 +17,7 @@ limitations under the License.
 package com.healthmarketscience.jackcess.impl;
 
 import java.text.SimpleDateFormat;
+import java.util.Calendar;
 import java.util.Map;
 import javax.script.Bindings;
 import javax.script.SimpleBindings;
@@ -24,6 +25,7 @@ import javax.script.SimpleBindings;
 import com.healthmarketscience.jackcess.expr.EvalConfig;
 import com.healthmarketscience.jackcess.expr.Function;
 import com.healthmarketscience.jackcess.expr.FunctionLookup;
+import com.healthmarketscience.jackcess.expr.NumericConfig;
 import com.healthmarketscience.jackcess.expr.TemporalConfig;
 import com.healthmarketscience.jackcess.impl.expr.DefaultFunctions;
 import com.healthmarketscience.jackcess.impl.expr.Expressionator;
@@ -41,6 +43,7 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig
   private FunctionLookup _funcs = DefaultFunctions.LOOKUP;
   private Map<String,SimpleDateFormat> _sdfs;
   private TemporalConfig _temporal;
+  private NumericConfig _numeric;
   private final RandomContext _rndCtx = new RandomContext();
   private Bindings _bindings = new SimpleBindings();
 
@@ -61,6 +64,18 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig
     _temporal = temporal;
   }
 
+  public Calendar getCalendar() {
+    return _db.getCalendar();
+  }
+
+  public NumericConfig getNumericConfig() {
+    return _numeric;
+  }
+
+  public void setNumericConfig(NumericConfig numeric) {
+    _numeric = numeric;
+  }
+
   public FunctionLookup getFunctionLookup() {
     return _funcs;
   }
@@ -92,4 +107,8 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig
   public float getRandom(Integer seed) {
     return _rndCtx.getRandom(seed);
   }
+
+  void resetDateTimeConfig() {
+    _sdfs = null;
+  }
 }
index 5878a473d5083d756d807b92025bfc7d18226ef7..6831e99c7e53c50d3426cff9359a1a94991d806d 100644 (file)
@@ -73,6 +73,7 @@ import com.healthmarketscience.jackcess.util.LinkResolver;
 import com.healthmarketscience.jackcess.util.ReadOnlyFileChannel;
 import com.healthmarketscience.jackcess.util.SimpleColumnValidatorFactory;
 import com.healthmarketscience.jackcess.util.TableIterableBuilder;
+import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.builder.ToStringBuilder;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -643,8 +644,11 @@ public class DatabaseImpl implements Database
       newTimeZone = getDefaultTimeZone();
     }
     _timeZone = newTimeZone;
-    // clear cached calendar when timezone is changed
+    // clear cached calendar(s) when timezone is changed
     _calendar = null;
+    if(_evalCtx != null) {
+      _evalCtx.resetDateTimeConfig();
+    }
   }
 
   public Charset getCharset()
@@ -1823,7 +1827,7 @@ public class DatabaseImpl implements Database
    * space, {@code false} otherwise.
    */
   public static boolean isBlank(String name) {
-    return((name == null) || (name.trim().length() == 0));
+    return StringUtils.isBlank(name);
   }
 
   /**
@@ -1831,11 +1835,7 @@ public class DatabaseImpl implements Database
    * null} or empty.
    */
   public static String trimToNull(String str) {
-    if(str == null) {
-      return null;
-    }
-    str = str.trim();
-    return((str.length() > 0) ? str : null);
+    return StringUtils.trimToNull(str);
   }
 
   @Override
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 (file)
index 188416a..0000000
+++ /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());
-  }
-}
index e8ae3397410bc24bcf460d7a50527fa77ddc150c..6805d074bf8396f070f86840413718e2a994cefa 100644 (file)
@@ -19,7 +19,7 @@ package com.healthmarketscience.jackcess.impl.expr;
 import java.math.BigDecimal;
 import java.util.Date;
 
-import com.healthmarketscience.jackcess.expr.EvalContext;
+import com.healthmarketscience.jackcess.expr.LocaleContext;
 import com.healthmarketscience.jackcess.expr.Value;
 
 /**
@@ -52,28 +52,28 @@ public abstract class BaseDelayedValue implements Value
     return getDelegate().get();
   }
 
-  public boolean getAsBoolean() {
-    return getDelegate().getAsBoolean();
+  public boolean getAsBoolean(LocaleContext ctx) {
+    return getDelegate().getAsBoolean(ctx);
   }
 
-  public String getAsString() {
-    return getDelegate().getAsString();
+  public String getAsString(LocaleContext ctx) {
+    return getDelegate().getAsString(ctx);
   }
 
-  public Date getAsDateTime(EvalContext ctx) {
+  public Date getAsDateTime(LocaleContext ctx) {
     return getDelegate().getAsDateTime(ctx);
   }
 
-  public Integer getAsLongInt() {
-    return getDelegate().getAsLongInt();
+  public Integer getAsLongInt(LocaleContext ctx) {
+    return getDelegate().getAsLongInt(ctx);
   }
 
-  public Double getAsDouble() {
-    return getDelegate().getAsDouble();
+  public Double getAsDouble(LocaleContext ctx) {
+    return getDelegate().getAsDouble(ctx);
   }
 
-  public BigDecimal getAsBigDecimal() {
-    return getDelegate().getAsBigDecimal();
+  public BigDecimal getAsBigDecimal(LocaleContext ctx) {
+    return getDelegate().getAsBigDecimal(ctx);
   }
 
   protected abstract Value eval();
index eb1ac7e9453a5ab682de52517b12aad0d6a1de35..cdf686ccdf14470f1f4703e1e676852a2735301b 100644 (file)
@@ -19,7 +19,7 @@ package com.healthmarketscience.jackcess.impl.expr;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
-import com.healthmarketscience.jackcess.expr.EvalContext;
+import com.healthmarketscience.jackcess.expr.LocaleContext;
 import com.healthmarketscience.jackcess.impl.ColumnImpl;
 
 /**
@@ -34,22 +34,19 @@ public abstract class BaseNumericValue extends BaseValue
   }
 
   @Override
-  public Integer getAsLongInt() {
-    return roundToLongInt();
+  public Integer getAsLongInt(LocaleContext ctx) {
+    return roundToLongInt(ctx);
   }
 
   @Override
-  public Double getAsDouble() {
+  public Double getAsDouble(LocaleContext ctx) {
     return getNumber().doubleValue();
   }
 
   @Override
-  public Date getAsDateTime(EvalContext ctx) {
+  public Date getAsDateTime(LocaleContext ctx) {
     double d = getNumber().doubleValue();
-
-    SimpleDateFormat sdf = ctx.createDateFormat(
-        ctx.getTemporalConfig().getDefaultDateTimeFormat());
-    return new Date(ColumnImpl.fromDateDouble(d, sdf.getCalendar()));
+    return new Date(ColumnImpl.fromDateDouble(d, ctx.getCalendar()));
   }
 
   protected abstract Number getNumber();
index 0f081dd11eb3d5a2a1a89e28ff25abb5e48dd9c9..d83af098d23870c9c020ef4f746459e63b0e058e 100644 (file)
@@ -20,8 +20,8 @@ import java.math.BigDecimal;
 import java.util.Date;
 
 import com.healthmarketscience.jackcess.expr.Value;
-import com.healthmarketscience.jackcess.expr.EvalContext;
 import com.healthmarketscience.jackcess.expr.EvalException;
+import com.healthmarketscience.jackcess.expr.LocaleContext;
 import com.healthmarketscience.jackcess.impl.NumberFormatter;
 
 /**
@@ -34,27 +34,27 @@ public abstract class BaseValue implements Value
     return(getType() == Type.NULL);
   }
 
-  public boolean getAsBoolean() {
+  public boolean getAsBoolean(LocaleContext ctx) {
     throw invalidConversion(Value.Type.LONG);
   }
 
-  public String getAsString() {
+  public String getAsString(LocaleContext ctx) {
     throw invalidConversion(Value.Type.STRING);
   }
 
-  public Date getAsDateTime(EvalContext ctx) {
+  public Date getAsDateTime(LocaleContext ctx) {
     throw invalidConversion(Value.Type.DATE_TIME);
   }
 
-  public Integer getAsLongInt() {
+  public Integer getAsLongInt(LocaleContext ctx) {
     throw invalidConversion(Value.Type.LONG);
   }
 
-  public Double getAsDouble() {
+  public Double getAsDouble(LocaleContext ctx) {
     throw invalidConversion(Value.Type.DOUBLE);
   }
 
-  public BigDecimal getAsBigDecimal() {
+  public BigDecimal getAsBigDecimal(LocaleContext ctx) {
     throw invalidConversion(Value.Type.BIG_DEC);
   }
 
@@ -63,8 +63,8 @@ public abstract class BaseValue implements Value
         getType() + " value cannot be converted to " + newType);
   }
 
-  protected Integer roundToLongInt() {
-    return getAsBigDecimal().setScale(0, NumberFormatter.ROUND_MODE)
+  protected Integer roundToLongInt(LocaleContext ctx) {
+    return getAsBigDecimal(ctx).setScale(0, NumberFormatter.ROUND_MODE)
       .intValueExact();
   }
 
index 89e400476b5476613150c49b2c40a980804e2f7d..b847b592b1af92dd870ed10e3e8d6fb6000c887e 100644 (file)
@@ -18,6 +18,7 @@ package com.healthmarketscience.jackcess.impl.expr;
 
 import java.math.BigDecimal;
 
+import com.healthmarketscience.jackcess.expr.LocaleContext;
 import com.healthmarketscience.jackcess.impl.NumberFormatter;
 
 /**
@@ -47,17 +48,17 @@ public class BigDecimalValue extends BaseNumericValue
   }
 
   @Override
-  public boolean getAsBoolean() {
+  public boolean getAsBoolean(LocaleContext ctx) {
     return (_val.compareTo(BigDecimal.ZERO) != 0L);
   }
 
   @Override
-  public String getAsString() {
+  public String getAsString(LocaleContext ctx) {
     return NumberFormatter.format(_val);
   }
 
   @Override
-  public BigDecimal getAsBigDecimal() {
+  public BigDecimal getAsBigDecimal(LocaleContext ctx) {
     return _val;
   }
 }
index a655a1a17c0df7bcfe72c07b9888a4b88285d2ec..f53a6bef852a66e3bcb6689333e9cb08b4475aad 100644 (file)
@@ -19,8 +19,8 @@ package com.healthmarketscience.jackcess.impl.expr;
 import java.math.BigDecimal;
 import java.util.regex.Pattern;
 
-import com.healthmarketscience.jackcess.expr.EvalContext;
 import com.healthmarketscience.jackcess.expr.EvalException;
+import com.healthmarketscience.jackcess.expr.LocaleContext;
 import com.healthmarketscience.jackcess.expr.Value;
 import com.healthmarketscience.jackcess.impl.NumberFormatter;
 import static com.healthmarketscience.jackcess.impl.expr.ValueSupport.*;
@@ -63,7 +63,7 @@ public class BuiltinOperators
   // - between, not, like, in
   // - *NOT* concal op '&'
 
-  public static Value negate(EvalContext ctx, Value param1) {
+  public static Value negate(LocaleContext ctx, Value param1) {
     if(param1.isNull()) {
       // null propagation
       return NULL_VAL;
@@ -76,60 +76,60 @@ public class BuiltinOperators
     case TIME:
     case DATE_TIME:
       // dates/times get converted to date doubles for arithmetic
-      double result = -param1.getAsDouble();
-      return toDateValue(ctx, mathType, result, param1, null);
+      double result = -param1.getAsDouble(ctx);
+      return toDateValue(ctx, mathType, result);
     case LONG:
-      return toValue(-param1.getAsLongInt());
+      return toValue(-param1.getAsLongInt(ctx));
     case DOUBLE:
-      return toValue(-param1.getAsDouble());
+      return toValue(-param1.getAsDouble(ctx));
     case STRING:
     case BIG_DEC:
-      return toValue(param1.getAsBigDecimal().negate(
+      return toValue(param1.getAsBigDecimal(ctx).negate(
                          NumberFormatter.DEC_MATH_CONTEXT));
     default:
       throw new EvalException("Unexpected type " + mathType);
     }
   }
 
-  public static Value add(EvalContext ctx, Value param1, Value param2) {
+  public static Value add(LocaleContext ctx, Value param1, Value param2) {
     if(anyParamIsNull(param1, param2)) {
       // null propagation
       return NULL_VAL;
     }
 
-    Value.Type mathType = getMathTypePrecedence(param1, param2,
+    Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
                                                 CoercionType.SIMPLE);
 
     switch(mathType) {
     case STRING:
       // string '+' is a null-propagation (handled above) concat
-      return nonNullConcat(param1, param2);
+      return nonNullConcat(ctx, param1, param2);
     case DATE:
     case TIME:
     case DATE_TIME:
       // dates/times get converted to date doubles for arithmetic
-      double result = param1.getAsDouble() + param2.getAsDouble();
-      return toDateValue(ctx, mathType, result, param1, param2);
+      double result = param1.getAsDouble(ctx) + param2.getAsDouble(ctx);
+      return toDateValue(ctx, mathType, result);
     case LONG:
-      return toValue(param1.getAsLongInt() + param2.getAsLongInt());
+      return toValue(param1.getAsLongInt(ctx) + param2.getAsLongInt(ctx));
     case DOUBLE:
-      return toValue(param1.getAsDouble() + param2.getAsDouble());
+      return toValue(param1.getAsDouble(ctx) + param2.getAsDouble(ctx));
     case BIG_DEC:
-      return toValue(param1.getAsBigDecimal().add(
-                         param2.getAsBigDecimal(),
+      return toValue(param1.getAsBigDecimal(ctx).add(
+                         param2.getAsBigDecimal(ctx),
                          NumberFormatter.DEC_MATH_CONTEXT));
     default:
       throw new EvalException("Unexpected type " + mathType);
     }
   }
 
-  public static Value subtract(EvalContext ctx, Value param1, Value param2) {
+  public static Value subtract(LocaleContext ctx, Value param1, Value param2) {
     if(anyParamIsNull(param1, param2)) {
       // null propagation
       return NULL_VAL;
     }
 
-    Value.Type mathType = getMathTypePrecedence(param1, param2,
+    Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
                                                 CoercionType.SIMPLE);
 
     switch(mathType) {
@@ -138,28 +138,28 @@ public class BuiltinOperators
     case TIME:
     case DATE_TIME:
       // dates/times get converted to date doubles for arithmetic
-      double result = param1.getAsDouble() - param2.getAsDouble();
-      return toDateValue(ctx, mathType, result, param1, param2);
+      double result = param1.getAsDouble(ctx) - param2.getAsDouble(ctx);
+      return toDateValue(ctx, mathType, result);
     case LONG:
-      return toValue(param1.getAsLongInt() - param2.getAsLongInt());
+      return toValue(param1.getAsLongInt(ctx) - param2.getAsLongInt(ctx));
     case DOUBLE:
-      return toValue(param1.getAsDouble() - param2.getAsDouble());
+      return toValue(param1.getAsDouble(ctx) - param2.getAsDouble(ctx));
     case BIG_DEC:
-      return toValue(param1.getAsBigDecimal().subtract(
-                         param2.getAsBigDecimal(),
+      return toValue(param1.getAsBigDecimal(ctx).subtract(
+                         param2.getAsBigDecimal(ctx),
                          NumberFormatter.DEC_MATH_CONTEXT));
     default:
       throw new EvalException("Unexpected type " + mathType);
     }
   }
 
-  public static Value multiply(Value param1, Value param2) {
+  public static Value multiply(LocaleContext ctx, Value param1, Value param2) {
     if(anyParamIsNull(param1, param2)) {
       // null propagation
       return NULL_VAL;
     }
 
-    Value.Type mathType = getMathTypePrecedence(param1, param2,
+    Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
                                                 CoercionType.GENERAL);
 
     switch(mathType) {
@@ -168,25 +168,25 @@ public class BuiltinOperators
     // case TIME: break; promoted to double
     // case DATE_TIME: break; promoted to double
     case LONG:
-      return toValue(param1.getAsLongInt() * param2.getAsLongInt());
+      return toValue(param1.getAsLongInt(ctx) * param2.getAsLongInt(ctx));
     case DOUBLE:
-      return toValue(param1.getAsDouble() * param2.getAsDouble());
+      return toValue(param1.getAsDouble(ctx) * param2.getAsDouble(ctx));
     case BIG_DEC:
-      return toValue(param1.getAsBigDecimal().multiply(
-                         param2.getAsBigDecimal(),
+      return toValue(param1.getAsBigDecimal(ctx).multiply(
+                         param2.getAsBigDecimal(ctx),
                          NumberFormatter.DEC_MATH_CONTEXT));
     default:
       throw new EvalException("Unexpected type " + mathType);
     }
   }
 
-  public static Value divide(Value param1, Value param2) {
+  public static Value divide(LocaleContext ctx, Value param1, Value param2) {
     if(anyParamIsNull(param1, param2)) {
       // null propagation
       return NULL_VAL;
     }
 
-    Value.Type mathType = getMathTypePrecedence(param1, param2,
+    Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
                                                 CoercionType.GENERAL);
 
     switch(mathType) {
@@ -195,54 +195,54 @@ public class BuiltinOperators
     // case TIME: break; promoted to double
     // case DATE_TIME: break; promoted to double
     case LONG:
-      int lp1 = param1.getAsLongInt();
-      int lp2 = param2.getAsLongInt();
+      int lp1 = param1.getAsLongInt(ctx);
+      int lp2 = param2.getAsLongInt(ctx);
       if((lp1 % lp2) == 0) {
         return toValue(lp1 / lp2);
       }
       return toValue((double)lp1 / (double)lp2);
     case DOUBLE:
-      double d2 = param2.getAsDouble();
+      double d2 = param2.getAsDouble(ctx);
       if(d2 == 0.0d) {
         throw new ArithmeticException(DIV_BY_ZERO);
       }
-      return toValue(param1.getAsDouble() / d2);
+      return toValue(param1.getAsDouble(ctx) / d2);
     case BIG_DEC:
-      return toValue(divide(param1.getAsBigDecimal(), param2.getAsBigDecimal()));
+      return toValue(divide(param1.getAsBigDecimal(ctx), param2.getAsBigDecimal(ctx)));
     default:
       throw new EvalException("Unexpected type " + mathType);
     }
   }
 
-  public static Value intDivide(Value param1, Value param2) {
+  public static Value intDivide(LocaleContext ctx, Value param1, Value param2) {
     if(anyParamIsNull(param1, param2)) {
       // null propagation
       return NULL_VAL;
     }
 
-    Value.Type mathType = getMathTypePrecedence(param1, param2,
+    Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
                                                 CoercionType.GENERAL);
     if(mathType == Value.Type.STRING) {
       throw new EvalException("Unexpected type " + mathType);
     }
-    return toValue(param1.getAsLongInt() / param2.getAsLongInt());
+    return toValue(param1.getAsLongInt(ctx) / param2.getAsLongInt(ctx));
   }
 
-  public static Value exp(Value param1, Value param2) {
+  public static Value exp(LocaleContext ctx, Value param1, Value param2) {
     if(anyParamIsNull(param1, param2)) {
       // null propagation
       return NULL_VAL;
     }
 
-    Value.Type mathType = getMathTypePrecedence(param1, param2,
+    Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
                                                 CoercionType.GENERAL);
 
     if(mathType == Value.Type.BIG_DEC) {
       // see if we can handle the limited options supported for BigDecimal
       // (must be a positive int exponent)
       try {
-        BigDecimal result = param1.getAsBigDecimal().pow(
-            param2.getAsBigDecimal().intValueExact(),
+        BigDecimal result = param1.getAsBigDecimal(ctx).pow(
+            param2.getAsBigDecimal(ctx).intValueExact(),
             NumberFormatter.DEC_MATH_CONTEXT);
         return toValue(result);
       } catch(ArithmeticException ae) {
@@ -251,7 +251,7 @@ public class BuiltinOperators
     }
 
     // jdk only supports general pow() as doubles, let's go with that
-    double result = Math.pow(param1.getAsDouble(), param2.getAsDouble());
+    double result = Math.pow(param1.getAsDouble(ctx), param2.getAsDouble(ctx));
 
     // attempt to convert integral types back to integrals if possible
     if((mathType == Value.Type.LONG) && isIntegral(result)) {
@@ -261,22 +261,22 @@ public class BuiltinOperators
     return toValue(result);
   }
 
-  public static Value mod(Value param1, Value param2) {
+  public static Value mod(LocaleContext ctx, Value param1, Value param2) {
     if(anyParamIsNull(param1, param2)) {
       // null propagation
       return NULL_VAL;
     }
 
-    Value.Type mathType = getMathTypePrecedence(param1, param2,
+    Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
                                                 CoercionType.GENERAL);
 
     if(mathType == Value.Type.STRING) {
       throw new EvalException("Unexpected type " + mathType);
     }
-    return toValue(param1.getAsLongInt() % param2.getAsLongInt());
+    return toValue(param1.getAsLongInt(ctx) % param2.getAsLongInt(ctx));
   }
 
-  public static Value concat(Value param1, Value param2) {
+  public static Value concat(LocaleContext ctx, Value param1, Value param2) {
 
     // note, this op converts null to empty string
     if(param1.isNull()) {
@@ -287,77 +287,81 @@ public class BuiltinOperators
       param2 = EMPTY_STR_VAL;
     }
 
-    return nonNullConcat(param1, param2);
+    return nonNullConcat(ctx, param1, param2);
   }
 
-  private static Value nonNullConcat(Value param1, Value param2) {
-    return toValue(param1.getAsString().concat(param2.getAsString()));
+  private static Value nonNullConcat(
+      LocaleContext ctx, Value param1, Value param2) {
+    return toValue(param1.getAsString(ctx).concat(param2.getAsString(ctx)));
   }
 
-  public static Value not(Value param1) {
+  public static Value not(LocaleContext ctx, Value param1) {
     if(param1.isNull()) {
       // null propagation
       return NULL_VAL;
     }
 
-    return toValue(!param1.getAsBoolean());
+    return toValue(!param1.getAsBoolean(ctx));
   }
 
-  public static Value lessThan(Value param1, Value param2) {
+  public static Value lessThan(LocaleContext ctx, Value param1, Value param2) {
     if(anyParamIsNull(param1, param2)) {
       // null propagation
       return NULL_VAL;
     }
 
-    return toValue(nonNullCompareTo(param1, param2) < 0);
+    return toValue(nonNullCompareTo(ctx, param1, param2) < 0);
   }
 
-  public static Value greaterThan(Value param1, Value param2) {
+  public static Value greaterThan(
+      LocaleContext ctx, Value param1, Value param2) {
     if(anyParamIsNull(param1, param2)) {
       // null propagation
       return NULL_VAL;
     }
 
-    return toValue(nonNullCompareTo(param1, param2) > 0);
+    return toValue(nonNullCompareTo(ctx, param1, param2) > 0);
   }
 
-  public static Value lessThanEq(Value param1, Value param2) {
+  public static Value lessThanEq(
+      LocaleContext ctx, Value param1, Value param2) {
     if(anyParamIsNull(param1, param2)) {
       // null propagation
       return NULL_VAL;
     }
 
-    return toValue(nonNullCompareTo(param1, param2) <= 0);
+    return toValue(nonNullCompareTo(ctx, param1, param2) <= 0);
   }
 
-  public static Value greaterThanEq(Value param1, Value param2) {
+  public static Value greaterThanEq(
+      LocaleContext ctx, Value param1, Value param2) {
     if(anyParamIsNull(param1, param2)) {
       // null propagation
       return NULL_VAL;
     }
 
-    return toValue(nonNullCompareTo(param1, param2) >= 0);
+    return toValue(nonNullCompareTo(ctx, param1, param2) >= 0);
   }
 
-  public static Value equals(Value param1, Value param2) {
+  public static Value equals(LocaleContext ctx, Value param1, Value param2) {
     if(anyParamIsNull(param1, param2)) {
       // null propagation
       return NULL_VAL;
     }
 
-    return toValue(nonNullCompareTo(param1, param2) == 0);
+    return toValue(nonNullCompareTo(ctx, param1, param2) == 0);
   }
 
-  public static Value notEquals(Value param1, Value param2) {
+  public static Value notEquals(LocaleContext ctx, Value param1, Value param2) {
     if(anyParamIsNull(param1, param2)) {
       // null propagation
       return NULL_VAL;
     }
 
-    return toValue(nonNullCompareTo(param1, param2) != 0);
+    return toValue(nonNullCompareTo(ctx, param1, param2) != 0);
   }
 
-  public static Value and(Value param1, Value param2) {
+  public static Value and(LocaleContext ctx, Value param1, Value param2) {
 
     // "and" uses short-circuit logic
 
@@ -365,7 +369,7 @@ public class BuiltinOperators
       return NULL_VAL;
     }
 
-    boolean b1 = param1.getAsBoolean();
+    boolean b1 = param1.getAsBoolean(ctx);
     if(!b1) {
       return FALSE_VAL;
     }
@@ -374,10 +378,10 @@ public class BuiltinOperators
       return NULL_VAL;
     }
 
-    return toValue(param2.getAsBoolean());
+    return toValue(param2.getAsBoolean(ctx));
   }
 
-  public static Value or(Value param1, Value param2) {
+  public static Value or(LocaleContext ctx, Value param1, Value param2) {
 
     // "or" uses short-circuit logic
 
@@ -385,7 +389,7 @@ public class BuiltinOperators
       return NULL_VAL;
     }
 
-    boolean b1 = param1.getAsBoolean();
+    boolean b1 = param1.getAsBoolean(ctx);
     if(b1) {
       return TRUE_VAL;
     }
@@ -394,39 +398,39 @@ public class BuiltinOperators
       return NULL_VAL;
     }
 
-    return toValue(param2.getAsBoolean());
+    return toValue(param2.getAsBoolean(ctx));
   }
 
-  public static Value eqv(Value param1, Value param2) {
+  public static Value eqv(LocaleContext ctx, Value param1, Value param2) {
     if(anyParamIsNull(param1, param2)) {
       // null propagation
       return NULL_VAL;
     }
 
-    boolean b1 = param1.getAsBoolean();
-    boolean b2 = param2.getAsBoolean();
+    boolean b1 = param1.getAsBoolean(ctx);
+    boolean b2 = param2.getAsBoolean(ctx);
 
     return toValue(b1 == b2);
   }
 
-  public static Value xor(Value param1, Value param2) {
+  public static Value xor(LocaleContext ctx, Value param1, Value param2) {
     if(anyParamIsNull(param1, param2)) {
       // null propagation
       return NULL_VAL;
     }
 
-    boolean b1 = param1.getAsBoolean();
-    boolean b2 = param2.getAsBoolean();
+    boolean b1 = param1.getAsBoolean(ctx);
+    boolean b2 = param2.getAsBoolean(ctx);
 
     return toValue(b1 ^ b2);
   }
 
-  public static Value imp(Value param1, Value param2) {
+  public static Value imp(LocaleContext ctx, Value param1, Value param2) {
 
     // "imp" uses short-circuit logic
 
     if(param1.isNull()) {
-      if(param2.isNull() || !param2.getAsBoolean()) {
+      if(param2.isNull() || !param2.getAsBoolean(ctx)) {
         // null propagation
         return NULL_VAL;
       }
@@ -434,7 +438,7 @@ public class BuiltinOperators
       return TRUE_VAL;
     }
 
-    boolean b1 = param1.getAsBoolean();
+    boolean b1 = param1.getAsBoolean(ctx);
     if(!b1) {
       return TRUE_VAL;
     }
@@ -444,7 +448,7 @@ public class BuiltinOperators
       return NULL_VAL;
     }
 
-    return toValue(param2.getAsBoolean());
+    return toValue(param2.getAsBoolean(ctx));
   }
 
   public static Value isNull(Value param1) {
@@ -455,20 +459,22 @@ public class BuiltinOperators
     return toValue(!param1.isNull());
   }
 
-  public static Value like(Value param1, Pattern pattern) {
+  public static Value like(LocaleContext ctx, Value param1, Pattern pattern) {
     if(param1.isNull()) {
       // null propagation
       return NULL_VAL;
     }
 
-    return toValue(pattern.matcher(param1.getAsString()).matches());
+    return toValue(pattern.matcher(param1.getAsString(ctx)).matches());
   }
 
-  public static Value notLike(Value param1, Pattern pattern) {
-    return not(like(param1, pattern));
+  public static Value notLike(
+      LocaleContext ctx, Value param1, Pattern pattern) {
+    return not(ctx, like(ctx, param1, pattern));
   }
 
-  public static Value between(Value param1, Value param2, Value param3) {
+  public static Value between(
+      LocaleContext ctx, Value param1, Value param2, Value param3) {
     // null propagate any param.  uses short circuit eval of params
     if(anyParamIsNull(param1, param2, param3)) {
       // null propagation
@@ -478,20 +484,21 @@ public class BuiltinOperators
     // the between values can be in either order!?!
     Value min = param2;
     Value max = param3;
-    Value gt = greaterThan(min, max);
-    if(gt.getAsBoolean()) {
+    Value gt = greaterThan(ctx, min, max);
+    if(gt.getAsBoolean(ctx)) {
       min = param3;
       max = param2;
     }
 
-    return and(greaterThanEq(param1, min), lessThanEq(param1, max));
+    return and(ctx, greaterThanEq(ctx, param1, min), lessThanEq(ctx, param1, max));
   }
 
-  public static Value notBetween(Value param1, Value param2, Value param3) {
-    return not(between(param1, param2, param3));
+  public static Value notBetween(
+      LocaleContext ctx, Value param1, Value param2, Value param3) {
+    return not(ctx, between(ctx, param1, param2, param3));
   }
 
-  public static Value in(Value param1, Value[] params) {
+  public static Value in(LocaleContext ctx, Value param1, Value[] params) {
 
     // null propagate any param.  uses short circuit eval of params
     if(param1.isNull()) {
@@ -504,8 +511,8 @@ public class BuiltinOperators
         continue;
       }
 
-      Value eq = equals(param1, val);
-      if(eq.getAsBoolean()) {
+      Value eq = equals(ctx, param1, val);
+      if(eq.getAsBoolean(ctx)) {
         return TRUE_VAL;
       }
     }
@@ -513,8 +520,8 @@ public class BuiltinOperators
     return FALSE_VAL;
   }
 
-  public static Value notIn(Value param1, Value[] params) {
-    return not(in(param1, params));
+  public static Value notIn(LocaleContext ctx, Value param1, Value[] params) {
+    return not(ctx, in(ctx, param1, params));
   }
 
 
@@ -528,10 +535,10 @@ public class BuiltinOperators
   }
 
   protected static int nonNullCompareTo(
-      Value param1, Value param2)
+      LocaleContext ctx, Value param1, Value param2)
   {
     // note that comparison does not do string to num coercion
-    Value.Type compareType = getMathTypePrecedence(param1, param2,
+    Value.Type compareType = getMathTypePrecedence(ctx, param1, param2,
                                                    CoercionType.COMPARE);
 
     switch(compareType) {
@@ -540,23 +547,23 @@ public class BuiltinOperators
       if(param1.getType() != param2.getType()) {
         throw new EvalException("Unexpected type " + compareType);
       }
-      return param1.getAsString().compareToIgnoreCase(param2.getAsString());
+      return param1.getAsString(ctx).compareToIgnoreCase(param2.getAsString(ctx));
     // case DATE: break; promoted to double
     // case TIME: break; promoted to double
     // case DATE_TIME: break; promoted to double
     case LONG:
-      return param1.getAsLongInt().compareTo(param2.getAsLongInt());
+      return param1.getAsLongInt(ctx).compareTo(param2.getAsLongInt(ctx));
     case DOUBLE:
-      return param1.getAsDouble().compareTo(param2.getAsDouble());
+      return param1.getAsDouble(ctx).compareTo(param2.getAsDouble(ctx));
     case BIG_DEC:
-      return param1.getAsBigDecimal().compareTo(param2.getAsBigDecimal());
+      return param1.getAsBigDecimal(ctx).compareTo(param2.getAsBigDecimal(ctx));
     default:
       throw new EvalException("Unexpected type " + compareType);
     }
   }
 
   private static Value.Type getMathTypePrecedence(
-      Value param1, Value param2, CoercionType cType)
+      LocaleContext ctx, Value param1, Value param2, CoercionType cType)
   {
     Value.Type t1 = param1.getType();
     Value.Type t2 = param2.getType();
@@ -577,7 +584,8 @@ public class BuiltinOperators
       if(cType._allowCoerceStringToNum) {
         // see if this is mixed string/numeric and the string can be coerced
         // to a number
-        Value.Type numericType = coerceStringToNumeric(param1, param2, cType);
+        Value.Type numericType = coerceStringToNumeric(
+            ctx, param1, param2, cType);
         if(numericType != null) {
           // string can be coerced to number
           return numericType;
@@ -614,7 +622,7 @@ public class BuiltinOperators
   }
 
   private static Value.Type coerceStringToNumeric(
-      Value param1, Value param2, CoercionType cType) {
+      LocaleContext ctx, Value param1, Value param2, CoercionType cType) {
     Value.Type t1 = param1.getType();
     Value.Type t2 = param2.getType();
 
@@ -639,7 +647,7 @@ public class BuiltinOperators
 
     try {
       // see if string can be coerced to a number
-      strParam.getAsBigDecimal();
+      strParam.getAsBigDecimal(ctx);
       if(prefType.isNumeric()) {
         // seems like when strings are coerced to numbers, they are usually
         // doubles, unless the current context is decimal
index abc047f6be0a24340923c88ec35cc74d3802c517..0f6937ff4155599e1ca396e4a7835c86c23f2f8f 100644 (file)
@@ -16,22 +16,69 @@ limitations under the License.
 
 package com.healthmarketscience.jackcess.impl.expr;
 
-import java.text.DateFormat;
+import java.math.BigDecimal;
 import java.util.Date;
 
+import com.healthmarketscience.jackcess.impl.ColumnImpl;
+import com.healthmarketscience.jackcess.expr.LocaleContext;
+
 /**
  *
  * @author James Ahlborn
  */
-public class DateTimeValue extends BaseDateValue
+public class DateTimeValue extends BaseValue
 {
+  private final Type _type;
+  private final Date _val;
 
-  public DateTimeValue(Date val, DateFormat fmt) 
-  {
-    super(val, fmt);
+  public DateTimeValue(Type type, Date val) {
+    if(!type.isTemporal()) {
+      throw new IllegalArgumentException("invalid date/time type");
+    }
+    _type = type;
+    _val = val;
   }
 
   public Type getType() {
-    return Type.DATE_TIME;
+    return _type;
+  }
+
+  public Object get() {
+    return _val;
+  }
+
+  protected Double getNumber(LocaleContext ctx) {
+    return ColumnImpl.toDateDouble(_val, ctx.getCalendar());
+  }
+
+  @Override
+  public boolean getAsBoolean(LocaleContext ctx) {
+    // ms access seems to treat dates/times as "true"
+    return true;
+  }
+
+  @Override
+  public String getAsString(LocaleContext ctx) {
+    return ValueSupport.getDateFormatForType(ctx, getType()).format(_val);
+  }
+
+  @Override
+  public Date getAsDateTime(LocaleContext ctx) {
+    return _val;
+  }
+
+  @Override
+  public Integer getAsLongInt(LocaleContext ctx) {
+    return roundToLongInt(ctx);
+  }
+
+  @Override
+  public Double getAsDouble(LocaleContext ctx) {
+    return getNumber(ctx);
+  }
+
+  @Override
+  public BigDecimal getAsBigDecimal(LocaleContext ctx) {
+    return BigDecimal.valueOf(getNumber(ctx));
   }
 }
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 (file)
index 558e3ab..0000000
+++ /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;
-  }
-}
index b062a0529cab3012ed041b8df38fec2172aef46f..87389d5a0fef070c01e042f8f9d9751101730592 100644 (file)
@@ -26,6 +26,7 @@ import java.util.Date;
 import com.healthmarketscience.jackcess.expr.EvalContext;
 import com.healthmarketscience.jackcess.expr.EvalException;
 import com.healthmarketscience.jackcess.expr.Function;
+import com.healthmarketscience.jackcess.expr.LocaleContext;
 import com.healthmarketscience.jackcess.expr.TemporalConfig;
 import com.healthmarketscience.jackcess.expr.Value;
 import com.healthmarketscience.jackcess.impl.ColumnImpl;
@@ -58,9 +59,8 @@ public class DefaultDateFunctions
   public static final Function DATE = registerFunc(new Func0("Date") {
     @Override
     protected Value eval0(EvalContext ctx) {
-      DateFormat fmt = ValueSupport.getDateFormatForType(ctx, Value.Type.DATE);
-      double dd = dateOnly(currentTimeDouble(fmt));
-      return ValueSupport.toValue(Value.Type.DATE, dd, fmt);
+      double dd = dateOnly(currentTimeDouble(ctx));
+      return ValueSupport.toDateValue(ctx, Value.Type.DATE, dd);
     }
   });
 
@@ -71,26 +71,24 @@ public class DefaultDateFunctions
       if(dv.getType() == Value.Type.DATE) {
         return dv;
       }
-      double dd = dateOnly(dv.getAsDouble());
-      DateFormat fmt = ValueSupport.getDateFormatForType(ctx, Value.Type.DATE);
-      return ValueSupport.toValue(Value.Type.DATE, dd, fmt);
+      double dd = dateOnly(dv.getAsDouble(ctx));
+      return ValueSupport.toDateValue(ctx, Value.Type.DATE, dd);
     }
   });
 
   public static final Function DATESERIAL = registerFunc(new Func3("DateSerial") {
     @Override
     protected Value eval3(EvalContext ctx, Value param1, Value param2, Value param3) {
-      int year = param1.getAsLongInt();
-      int month = param2.getAsLongInt();
-      int day = param3.getAsLongInt();
+      int year = param1.getAsLongInt(ctx);
+      int month = param2.getAsLongInt(ctx);
+      int day = param3.getAsLongInt(ctx);
 
       // "default" two digit year handling
       if(year < 100) {
         year += ((year <= 29) ? 2000 : 1900);
       }
 
-      DateFormat fmt = ValueSupport.getDateFormatForType(ctx, Value.Type.DATE);
-      Calendar cal = fmt.getCalendar();
+      Calendar cal = ctx.getCalendar();
       cal.clear();
 
       cal.set(Calendar.YEAR, year);
@@ -98,24 +96,22 @@ public class DefaultDateFunctions
       cal.set(Calendar.MONTH, month - 1);
       cal.set(Calendar.DAY_OF_MONTH, day);
 
-      return ValueSupport.toValue(Value.Type.DATE, cal.getTime(), fmt);
+      return ValueSupport.toValue(Value.Type.DATE, cal.getTime());
     }
   });
 
   public static final Function NOW = registerFunc(new Func0("Now") {
     @Override
     protected Value eval0(EvalContext ctx) {
-      DateFormat fmt = ValueSupport.getDateFormatForType(ctx, Value.Type.DATE_TIME);
-      return ValueSupport.toValue(Value.Type.DATE_TIME, new Date(), fmt);
+      return ValueSupport.toValue(Value.Type.DATE_TIME, new Date());
     }
   });
 
   public static final Function TIME = registerFunc(new Func0("Time") {
     @Override
     protected Value eval0(EvalContext ctx) {
-      DateFormat fmt = ValueSupport.getDateFormatForType(ctx, Value.Type.TIME);
-      double dd = timeOnly(currentTimeDouble(fmt));
-      return ValueSupport.toValue(Value.Type.TIME, dd, fmt);
+      double dd = timeOnly(currentTimeDouble(ctx));
+      return ValueSupport.toDateValue(ctx, Value.Type.TIME, dd);
     }
   });
 
@@ -126,17 +122,15 @@ public class DefaultDateFunctions
       if(dv.getType() == Value.Type.TIME) {
         return dv;
       }
-      double dd = timeOnly(dv.getAsDouble());
-      DateFormat fmt = ValueSupport.getDateFormatForType(ctx, Value.Type.TIME);
-      return ValueSupport.toValue(Value.Type.TIME, dd, fmt);
+      double dd = timeOnly(dv.getAsDouble(ctx));
+      return ValueSupport.toDateValue(ctx, Value.Type.TIME, dd);
     }
   });
 
   public static final Function TIMER = registerFunc(new Func0("Timer") {
     @Override
     protected Value eval0(EvalContext ctx) {
-      DateFormat fmt = ValueSupport.getDateFormatForType(ctx, Value.Type.TIME);
-      double dd = timeOnly(currentTimeDouble(fmt)) * DSECONDS_PER_DAY;
+      double dd = timeOnly(currentTimeDouble(ctx)) * DSECONDS_PER_DAY;
       return ValueSupport.toValue(dd);
     }
   });
@@ -144,9 +138,9 @@ public class DefaultDateFunctions
   public static final Function TIMESERIAL = registerFunc(new Func3("TimeSerial") {
     @Override
     protected Value eval3(EvalContext ctx, Value param1, Value param2, Value param3) {
-      int hours = param1.getAsLongInt();
-      int minutes = param2.getAsLongInt();
-      int seconds = param3.getAsLongInt();
+      int hours = param1.getAsLongInt(ctx);
+      int minutes = param2.getAsLongInt(ctx);
+      int seconds = param3.getAsLongInt(ctx);
 
       long totalSeconds = (hours * SECONDS_PER_HOUR) +
         (minutes * SECONDS_PER_MINUTE) + seconds;
@@ -158,9 +152,8 @@ public class DefaultDateFunctions
         totalSeconds %= SECONDS_PER_DAY;
       }
 
-      DateFormat fmt = ValueSupport.getDateFormatForType(ctx, Value.Type.TIME);
       double dd = totalSeconds / DSECONDS_PER_DAY;
-      return ValueSupport.toValue(Value.Type.TIME, dd, fmt);
+      return ValueSupport.toDateValue(ctx, Value.Type.TIME, dd);
     }
   });
 
@@ -213,12 +206,11 @@ public class DefaultDateFunctions
         return null;
       }
       // convert from 1 based to 0 based value
-      int month = param1.getAsLongInt() - 1;
+      int month = param1.getAsLongInt(ctx) - 1;
 
-      boolean abbreviate = getOptionalBooleanParam(params, 1);
+      boolean abbreviate = getOptionalBooleanParam(ctx, params, 1);
 
-      DateFormatSymbols syms = ctx.createDateFormat(
-          ctx.getTemporalConfig().getDateFormat()).getDateFormatSymbols();
+      DateFormatSymbols syms = ctx.getTemporalConfig().getDateFormatSymbols();
       String[] monthNames = (abbreviate ?
                              syms.getShortMonths() : syms.getMonths());
       // note, the array is 1 based
@@ -243,7 +235,7 @@ public class DefaultDateFunctions
       }
       int dayOfWeek = nonNullToCalendarField(ctx, param1, Calendar.DAY_OF_WEEK);
 
-      int firstDay = getFirstDayParam(params, 1);
+      int firstDay = getFirstDayParam(ctx, params, 1);
 
       return ValueSupport.toValue(dayOfWeekToWeekDay(dayOfWeek, firstDay));
     }
@@ -256,16 +248,15 @@ public class DefaultDateFunctions
       if(param1 == null) {
         return null;
       }
-      int weekday = param1.getAsLongInt();
+      int weekday = param1.getAsLongInt(ctx);
 
-      boolean abbreviate = getOptionalBooleanParam(params, 1);
+      boolean abbreviate = getOptionalBooleanParam(ctx, params, 1);
 
-      int firstDay = getFirstDayParam(params, 2);
+      int firstDay = getFirstDayParam(ctx, params, 2);
 
       int dayOfWeek = weekDayToDayOfWeek(weekday, firstDay);
 
-      DateFormatSymbols syms = ctx.createDateFormat(
-          ctx.getTemporalConfig().getDateFormat()).getDateFormatSymbols();
+      DateFormatSymbols syms = ctx.getTemporalConfig().getDateFormatSymbols();
       String[] weekdayNames = (abbreviate ?
                                syms.getShortWeekdays() : syms.getWeekdays());
       // note, the array is 1 based
@@ -287,7 +278,7 @@ public class DefaultDateFunctions
                               origParam + "'");
     }
 
-    Calendar cal = getDateValueFormat(ctx, param).getCalendar();
+    Calendar cal = ctx.getCalendar();
     cal.setTime(param.getAsDateTime(ctx));
     return cal;
   }
@@ -301,18 +292,17 @@ public class DefaultDateFunctions
     if(type == Value.Type.STRING) {
 
       // see if we can coerce to date/time or double
-      String valStr = param.getAsString();
+      String valStr = param.getAsString(ctx);
       TemporalConfig.Type valTempType = ExpressionTokenizer.determineDateType(
           valStr, ctx);
 
       if(valTempType != null) {
 
         try {
-          DateFormat parseDf = ExpressionTokenizer.createParseDateFormat(
+          DateFormat parseDf = ExpressionTokenizer.createParseDateTimeFormat(
               valTempType, ctx);
           Date dateVal = ExpressionTokenizer.parseComplete(parseDf, valStr);
-          return ValueSupport.toValue(ctx, valTempType.getValueType(),
-                                      dateVal);
+          return ValueSupport.toValue(valTempType.getValueType(), dateVal);
         } catch(java.text.ParseException pe) {
           // not a valid date string, not a date/time
           return null;
@@ -321,7 +311,7 @@ public class DefaultDateFunctions
 
       // see if string can be coerced to number
       try {
-        return numberToDateValue(ctx, param.getAsDouble());
+        return numberToDateValue(ctx, param.getAsDouble(ctx));
       } catch(NumberFormatException ignored) {
         // not a number, not a date/time
         return null;
@@ -329,7 +319,7 @@ public class DefaultDateFunctions
     }
 
     // must be a number
-    return numberToDateValue(ctx, param.getAsDouble());
+    return numberToDateValue(ctx, param.getAsDouble(ctx));
   }
 
   private static Value numberToDateValue(EvalContext ctx, double dd) {
@@ -344,14 +334,7 @@ public class DefaultDateFunctions
     Value.Type type = (hasDate ? (hasTime ? Value.Type.DATE_TIME :
                                   Value.Type.DATE) :
                        Value.Type.TIME);
-    DateFormat fmt = ValueSupport.getDateFormatForType(ctx, type);
-    return ValueSupport.toValue(type, dd, fmt);
-  }
-
-  private static DateFormat getDateValueFormat(EvalContext ctx, Value param) {
-    return ((param instanceof BaseDateValue) ?
-            ((BaseDateValue)param).getFormat() :
-       ValueSupport.getDateFormatForType(ctx, param.getType()));
+    return ValueSupport.toDateValue(ctx, type, dd);
   }
 
   private static double dateOnly(double dd) {
@@ -366,8 +349,8 @@ public class DefaultDateFunctions
     return new BigDecimal(dd).remainder(BigDecimal.ONE).doubleValue();
   }
 
-  private static double currentTimeDouble(DateFormat fmt) {
-    return ColumnImpl.toDateDouble(System.currentTimeMillis(), fmt.getCalendar());
+  private static double currentTimeDouble(LocaleContext ctx) {
+    return ColumnImpl.toDateDouble(System.currentTimeMillis(), ctx.getCalendar());
   }
 
   private static int dayOfWeekToWeekDay(int day, int firstDay) {
@@ -382,11 +365,12 @@ public class DefaultDateFunctions
     return (((firstDay - 1) + (weekday - 1)) % 7) + 1;
   }
 
-  private static int getFirstDayParam(Value[] params, int idx) {
+  private static int getFirstDayParam(
+      LocaleContext ctx, Value[] params, int idx) {
     // vbSunday (default)
     int firstDay = 1;
     if(params.length > idx) {
-      firstDay = params[idx].getAsLongInt();
+      firstDay = params[idx].getAsLongInt(ctx);
       if(firstDay == 0) {
         // 0 == vbUseSystem, so we will use the default "sunday"
         firstDay = 1;
@@ -395,9 +379,10 @@ public class DefaultDateFunctions
     return firstDay;
   }
 
-  private static boolean getOptionalBooleanParam(Value[] params, int idx) {
+  private static boolean getOptionalBooleanParam(
+      LocaleContext ctx, Value[] params, int idx) {
     if(params.length > idx) {
-      return params[idx].getAsBoolean();
+      return params[idx].getAsBoolean(ctx);
     }
     return false;
   }
index 3ca67255293f4b0c138ce2647f12313b441e3b26..c5a23568b90dcf30ba7238c742737734ebdae49b 100644 (file)
@@ -45,18 +45,18 @@ public class DefaultFinancialFunctions
   public static final Function NPER = registerFunc(new FuncVar("NPer", 3, 5) {
     @Override
     protected Value evalVar(EvalContext ctx, Value[] params) {
-      double rate = params[0].getAsDouble();
-      double pmt = params[1].getAsDouble();
-      double pv = params[2].getAsDouble();
+      double rate = params[0].getAsDouble(ctx);
+      double pmt = params[1].getAsDouble(ctx);
+      double pv = params[2].getAsDouble(ctx);
 
       double fv = 0d;
       if(params.length > 3) {
-        fv = params[3].getAsDouble();
+        fv = params[3].getAsDouble(ctx);
       }
 
       int pmtType = PMT_END_MNTH;
       if(params.length > 4) {
-        pmtType = params[4].getAsLongInt();
+        pmtType = params[4].getAsLongInt(ctx);
       }
 
       double result = calculateLoanPaymentPeriods(rate, pmt, pv, pmtType);
@@ -72,18 +72,18 @@ public class DefaultFinancialFunctions
   public static final Function FV = registerFunc(new FuncVar("FV", 3, 5) {
     @Override
     protected Value evalVar(EvalContext ctx, Value[] params) {
-      double rate = params[0].getAsDouble();
-      double nper = params[1].getAsDouble();
-      double pmt = params[2].getAsDouble();
+      double rate = params[0].getAsDouble(ctx);
+      double nper = params[1].getAsDouble(ctx);
+      double pmt = params[2].getAsDouble(ctx);
 
       double pv = 0d;
       if(params.length > 3) {
-        pv = params[3].getAsDouble();
+        pv = params[3].getAsDouble(ctx);
       }
 
       int pmtType = PMT_END_MNTH;
       if(params.length > 4) {
-        pmtType = params[4].getAsLongInt();
+        pmtType = params[4].getAsLongInt(ctx);
       }
 
       if(pv != 0d) {
@@ -99,18 +99,18 @@ public class DefaultFinancialFunctions
   public static final Function PV = registerFunc(new FuncVar("PV", 3, 5) {
     @Override
     protected Value evalVar(EvalContext ctx, Value[] params) {
-      double rate = params[0].getAsDouble();
-      double nper = params[1].getAsDouble();
-      double pmt = params[2].getAsDouble();
+      double rate = params[0].getAsDouble(ctx);
+      double nper = params[1].getAsDouble(ctx);
+      double pmt = params[2].getAsDouble(ctx);
 
       double fv = 0d;
       if(params.length > 3) {
-        fv = params[3].getAsDouble();
+        fv = params[3].getAsDouble(ctx);
       }
 
       int pmtType = PMT_END_MNTH;
       if(params.length > 4) {
-        pmtType = params[4].getAsLongInt();
+        pmtType = params[4].getAsLongInt(ctx);
       }
 
       if(fv != 0d) {
@@ -126,18 +126,18 @@ public class DefaultFinancialFunctions
   public static final Function PMT = registerFunc(new FuncVar("Pmt", 3, 5) {
     @Override
     protected Value evalVar(EvalContext ctx, Value[] params) {
-      double rate = params[0].getAsDouble();
-      double nper = params[1].getAsDouble();
-      double pv = params[2].getAsDouble();
+      double rate = params[0].getAsDouble(ctx);
+      double nper = params[1].getAsDouble(ctx);
+      double pv = params[2].getAsDouble(ctx);
 
       double fv = 0d;
       if(params.length > 3) {
-        fv = params[3].getAsDouble();
+        fv = params[3].getAsDouble(ctx);
       }
 
       int pmtType = PMT_END_MNTH;
       if(params.length > 4) {
-        pmtType = params[4].getAsLongInt();
+        pmtType = params[4].getAsLongInt(ctx);
       }
 
       double result = calculateLoanPayment(rate, nper, pv, pmtType);
@@ -154,19 +154,19 @@ public class DefaultFinancialFunctions
   // public static final Function IPMT = registerFunc(new FuncVar("IPmt", 4, 6) {
   //   @Override
   //   protected Value evalVar(EvalContext ctx, Value[] params) {
-  //     double rate = params[0].getAsDouble();
-  //     double per = params[1].getAsDouble();
-  //     double nper = params[2].getAsDouble();
-  //     double pv = params[3].getAsDouble();
+  //     double rate = params[0].getAsDouble(ctx);
+  //     double per = params[1].getAsDouble(ctx);
+  //     double nper = params[2].getAsDouble(ctx);
+  //     double pv = params[3].getAsDouble(ctx);
 
   //     double fv = 0d;
   //     if(params.length > 4) {
-  //       fv = params[4].getAsDouble();
+  //       fv = params[4].getAsDouble(ctx);
   //     }
 
   //     int pmtType = PMT_END_MNTH;
   //     if(params.length > 5) {
-  //       pmtType = params[5].getAsLongInt();
+  //       pmtType = params[5].getAsLongInt(ctx);
   //     }
 
   //     double pmt = calculateLoanPayment(rate, nper, pv, pmtType);
@@ -185,19 +185,19 @@ public class DefaultFinancialFunctions
   // public static final Function PPMT = registerFunc(new FuncVar("PPmt", 4, 6) {
   //   @Override
   //   protected Value evalVar(EvalContext ctx, Value[] params) {
-  //     double rate = params[0].getAsDouble();
-  //     double per = params[1].getAsDouble();
-  //     double nper = params[2].getAsDouble();
-  //     double pv = params[3].getAsDouble();
+  //     double rate = params[0].getAsDouble(ctx);
+  //     double per = params[1].getAsDouble(ctx);
+  //     double nper = params[2].getAsDouble(ctx);
+  //     double pv = params[3].getAsDouble(ctx);
 
   //     double fv = 0d;
   //     if(params.length > 4) {
-  //       fv = params[4].getAsDouble();
+  //       fv = params[4].getAsDouble(ctx);
   //     }
 
   //     int pmtType = PMT_END_MNTH;
   //     if(params.length > 5) {
-  //       pmtType = params[5].getAsLongInt();
+  //       pmtType = params[5].getAsLongInt(ctx);
   //     }
 
   //     double pmt = calculateLoanPayment(rate, nper, pv, pmtType);
@@ -217,14 +217,14 @@ public class DefaultFinancialFunctions
   // public static final Function DDB = registerFunc(new FuncVar("DDB", 4, 5) {
   //   @Override
   //   protected Value evalVar(EvalContext ctx, Value[] params) {
-  //     double cost = params[0].getAsDouble();
-  //     double salvage = params[1].getAsDouble();
-  //     double life = params[2].getAsDouble();
-  //     double period = params[3].getAsDouble();
+  //     double cost = params[0].getAsDouble(ctx);
+  //     double salvage = params[1].getAsDouble(ctx);
+  //     double life = params[2].getAsDouble(ctx);
+  //     double period = params[3].getAsDouble(ctx);
 
   //     double factor = 2d;
   //     if(params.length > 4) {
-  //       factor = params[4].getAsDouble();
+  //       factor = params[4].getAsDouble(ctx);
   //     }
 
   //     double result = 0d;
@@ -263,9 +263,9 @@ public class DefaultFinancialFunctions
   // public static final Function SLN = registerFunc(new FuncVar("SLN", 3, 3) {
   //   @Override
   //   protected Value evalVar(EvalContext ctx, Value[] params) {
-  //     double cost = params[0].getAsDouble();
-  //     double salvage = params[1].getAsDouble();
-  //     double life = params[2].getAsDouble();
+  //     double cost = params[0].getAsDouble(ctx);
+  //     double salvage = params[1].getAsDouble(ctx);
+  //     double life = params[2].getAsDouble(ctx);
 
   //     double result = calculateStraightLineDepreciation(cost, salvage, life);
 
@@ -277,10 +277,10 @@ public class DefaultFinancialFunctions
   // public static final Function SYD = registerFunc(new FuncVar("SYD", 4, 4) {
   //   @Override
   //   protected Value evalVar(EvalContext ctx, Value[] params) {
-  //     double cost = params[0].getAsDouble();
-  //     double salvage = params[1].getAsDouble();
-  //     double life = params[2].getAsDouble();
-  //     double period = params[3].getAsDouble();
+  //     double cost = params[0].getAsDouble(ctx);
+  //     double salvage = params[1].getAsDouble(ctx);
+  //     double life = params[2].getAsDouble(ctx);
+  //     double period = params[3].getAsDouble(ctx);
 
   //     double result = calculateSumOfYearsDepreciation(
   //         cost, salvage, life, period);
index 20de8ca3b6edea052930b1d3aadc1c8f7cfb6220..f794a629d2708dd78910d412720c21f756b5ba65 100644 (file)
@@ -60,7 +60,7 @@ public class DefaultFunctions
     protected Value eval3(EvalContext ctx,
                           Value param1, Value param2, Value param3) {
       // null is false
-      return ((!param1.isNull() && param1.getAsBoolean()) ? param2 : param3);
+      return ((!param1.isNull() && param1.getAsBoolean(ctx)) ? param2 : param3);
     }
   });
 
@@ -68,10 +68,10 @@ public class DefaultFunctions
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
       if((param1.getType() == Value.Type.STRING) &&
-         (param1.getAsString().length() == 0)) {
+         (param1.getAsString(ctx).length() == 0)) {
         return ValueSupport.ZERO_VAL;
       }
-      int lv = param1.getAsLongInt();
+      int lv = param1.getAsLongInt(ctx);
       return ValueSupport.toValue(Integer.toHexString(lv).toUpperCase());
     }
   });
@@ -97,7 +97,7 @@ public class DefaultFunctions
     @Override
     protected Value evalVar(EvalContext ctx, Value[] params) {
       Value param1 = params[0];
-      int idx = param1.getAsLongInt();
+      int idx = param1.getAsLongInt(ctx);
       if((idx < 1) || (idx >= params.length)) {
         return ValueSupport.NULL_VAL;
       }
@@ -112,7 +112,7 @@ public class DefaultFunctions
         throw new EvalException("Odd number of parameters");
       }
       for(int i = 0; i < params.length; i+=2) {
-        if(params[i].getAsBoolean()) {
+        if(params[i].getAsBoolean(ctx)) {
           return params[i + 1];
         }
       }
@@ -124,10 +124,10 @@ public class DefaultFunctions
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
       if((param1.getType() == Value.Type.STRING) &&
-         (param1.getAsString().length() == 0)) {
+         (param1.getAsString(ctx).length() == 0)) {
         return ValueSupport.ZERO_VAL;
       }
-      int lv = param1.getAsLongInt();
+      int lv = param1.getAsLongInt(ctx);
       return ValueSupport.toValue(Integer.toOctalString(lv));
     }
   });
@@ -135,7 +135,7 @@ public class DefaultFunctions
   public static final Function CBOOL = registerFunc(new Func1("CBool") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      boolean b = param1.getAsBoolean();
+      boolean b = param1.getAsBoolean(ctx);
       return ValueSupport.toValue(b);
     }
   });
@@ -143,7 +143,7 @@ public class DefaultFunctions
   public static final Function CBYTE = registerFunc(new Func1("CByte") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      int lv = param1.getAsLongInt();
+      int lv = param1.getAsLongInt(ctx);
       if((lv < 0) || (lv > 255)) {
         throw new EvalException("Byte code '" + lv + "' out of range ");
       }
@@ -154,7 +154,7 @@ public class DefaultFunctions
   public static final Function CCUR = registerFunc(new Func1("CCur") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      BigDecimal bd = param1.getAsBigDecimal();
+      BigDecimal bd = param1.getAsBigDecimal(ctx);
       bd = bd.setScale(4, NumberFormatter.ROUND_MODE);
       return ValueSupport.toValue(bd);
     }
@@ -173,7 +173,7 @@ public class DefaultFunctions
   public static final Function CDBL = registerFunc(new Func1("CDbl") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      Double dv = param1.getAsDouble();
+      Double dv = param1.getAsDouble(ctx);
       return ValueSupport.toValue(dv);
     }
   });
@@ -181,7 +181,7 @@ public class DefaultFunctions
   public static final Function CDEC = registerFunc(new Func1("CDec") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      BigDecimal bd = param1.getAsBigDecimal();
+      BigDecimal bd = param1.getAsBigDecimal(ctx);
       return ValueSupport.toValue(bd);
     }
   });
@@ -189,7 +189,7 @@ public class DefaultFunctions
   public static final Function CINT = registerFunc(new Func1("CInt") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      int lv = param1.getAsLongInt();
+      int lv = param1.getAsLongInt(ctx);
       if((lv < Short.MIN_VALUE) || (lv > Short.MAX_VALUE)) {
         throw new EvalException("Int value '" + lv + "' out of range ");
       }
@@ -200,7 +200,7 @@ public class DefaultFunctions
   public static final Function CLNG = registerFunc(new Func1("CLng") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      int lv = param1.getAsLongInt();
+      int lv = param1.getAsLongInt(ctx);
       return ValueSupport.toValue(lv);
     }
   });
@@ -208,7 +208,7 @@ public class DefaultFunctions
   public static final Function CSNG = registerFunc(new Func1("CSng") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      Double dv = param1.getAsDouble();
+      Double dv = param1.getAsDouble(ctx);
       if((dv < Float.MIN_VALUE) || (dv > Float.MAX_VALUE)) {
         throw new EvalException("Single value '" + dv + "' out of range ");
       }
@@ -219,7 +219,7 @@ public class DefaultFunctions
   public static final Function CSTR = registerFunc(new Func1("CStr") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      return ValueSupport.toValue(param1.getAsString());
+      return ValueSupport.toValue(param1.getAsString(ctx));
     }
   });
 
@@ -255,7 +255,7 @@ public class DefaultFunctions
 
       if(param1.getType() == Value.Type.STRING) {
         try {
-          param1.getAsBigDecimal();
+          param1.getAsBigDecimal(ctx);
           return ValueSupport.TRUE_VAL;
         } catch(NumberFormatException ignored) {
           // fall through to FALSE_VAL
index 278306e6eebea172868ec83db62ca96f60eabad6..4473af45545e14169b7477453ec7300a03d238a7 100644 (file)
@@ -49,15 +49,15 @@ public class DefaultNumberFunctions
       case TIME:
       case DATE_TIME:
         // dates/times get converted to date doubles for arithmetic
-        double result = Math.abs(param1.getAsDouble());
-        return ValueSupport.toDateValue(ctx, mathType, result, param1, null);
+        double result = Math.abs(param1.getAsDouble(ctx));
+        return ValueSupport.toDateValue(ctx, mathType, result);
       case LONG:
-        return ValueSupport.toValue(Math.abs(param1.getAsLongInt()));
+        return ValueSupport.toValue(Math.abs(param1.getAsLongInt(ctx)));
       case DOUBLE:
-        return ValueSupport.toValue(Math.abs(param1.getAsDouble()));
+        return ValueSupport.toValue(Math.abs(param1.getAsDouble(ctx)));
       case STRING:
       case BIG_DEC:
-        return ValueSupport.toValue(param1.getAsBigDecimal().abs(
+        return ValueSupport.toValue(param1.getAsBigDecimal(ctx).abs(
                                             NumberFormatter.DEC_MATH_CONTEXT));
       default:
         throw new EvalException("Unexpected type " + mathType);
@@ -68,21 +68,21 @@ public class DefaultNumberFunctions
   public static final Function ATAN = registerFunc(new Func1("Atan") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      return ValueSupport.toValue(Math.atan(param1.getAsDouble()));
+      return ValueSupport.toValue(Math.atan(param1.getAsDouble(ctx)));
     }
   });
 
   public static final Function COS = registerFunc(new Func1("Cos") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      return ValueSupport.toValue(Math.cos(param1.getAsDouble()));
+      return ValueSupport.toValue(Math.cos(param1.getAsDouble(ctx)));
     }
   });
 
   public static final Function EXP = registerFunc(new Func1("Exp") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      return ValueSupport.toValue(Math.exp(param1.getAsDouble()));
+      return ValueSupport.toValue(Math.exp(param1.getAsDouble(ctx)));
     }
   });
 
@@ -92,7 +92,7 @@ public class DefaultNumberFunctions
       if(param1.getType().isIntegral()) {
         return param1;
       }
-      return ValueSupport.toValue(param1.getAsDouble().intValue());
+      return ValueSupport.toValue(param1.getAsDouble(ctx).intValue());
     }
   });
 
@@ -102,14 +102,14 @@ public class DefaultNumberFunctions
       if(param1.getType().isIntegral()) {
         return param1;
       }
-      return ValueSupport.toValue((int)Math.floor(param1.getAsDouble()));
+      return ValueSupport.toValue((int)Math.floor(param1.getAsDouble(ctx)));
     }
   });
 
   public static final Function LOG = registerFunc(new Func1("Log") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      return ValueSupport.toValue(Math.log(param1.getAsDouble()));
+      return ValueSupport.toValue(Math.log(param1.getAsDouble(ctx)));
     }
   });
 
@@ -120,7 +120,7 @@ public class DefaultNumberFunctions
     }
     @Override
     protected Value evalVar(EvalContext ctx, Value[] params) {
-      Integer seed = ((params.length > 0) ? params[0].getAsLongInt() : null);
+      Integer seed = ((params.length > 0) ? params[0].getAsLongInt(ctx) : null);
       return ValueSupport.toValue(ctx.getRandom(seed));
     }
   });
@@ -137,9 +137,9 @@ public class DefaultNumberFunctions
       }
       int scale = 0;
       if(params.length > 1) {
-        scale = params[1].getAsLongInt();
+        scale = params[1].getAsLongInt(ctx);
       }
-      BigDecimal bd = param1.getAsBigDecimal()
+      BigDecimal bd = param1.getAsBigDecimal(ctx)
         .setScale(scale, NumberFormatter.ROUND_MODE);
       return ValueSupport.toValue(bd);
     }
@@ -150,9 +150,9 @@ public class DefaultNumberFunctions
     protected Value eval1(EvalContext ctx, Value param1) {
       int val = 0;
       if(param1.getType().isIntegral()) {
-        val = param1.getAsLongInt();
+        val = param1.getAsLongInt(ctx);
       } else {
-        val = param1.getAsBigDecimal().signum();
+        val = param1.getAsBigDecimal(ctx).signum();
       }
       return ((val > 0) ? ValueSupport.ONE_VAL :
               ((val < 0) ? ValueSupport.NEG_ONE_VAL :
@@ -163,7 +163,7 @@ public class DefaultNumberFunctions
   public static final Function SQR = registerFunc(new Func1("Sqr") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      double dv = param1.getAsDouble();
+      double dv = param1.getAsDouble(ctx);
       if(dv < 0.0d) {
         throw new EvalException("Invalid value '" + dv + "'");
       }
@@ -174,14 +174,14 @@ public class DefaultNumberFunctions
   public static final Function SIN = registerFunc(new Func1("Sin") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      return ValueSupport.toValue(Math.sin(param1.getAsDouble()));
+      return ValueSupport.toValue(Math.sin(param1.getAsDouble(ctx)));
     }
   });
 
   public static final Function TAN = registerFunc(new Func1("Tan") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      return ValueSupport.toValue(Math.tan(param1.getAsDouble()));
+      return ValueSupport.toValue(Math.tan(param1.getAsDouble(ctx)));
     }
   });
 
index bd1aac29732dd6a7ac232b7505e82083694557f6..4a6da20da6dbe363406b5f0c731b86b98e8e9523 100644 (file)
@@ -21,6 +21,7 @@ import java.math.BigDecimal;
 import com.healthmarketscience.jackcess.expr.EvalContext;
 import com.healthmarketscience.jackcess.expr.EvalException;
 import com.healthmarketscience.jackcess.expr.Function;
+import com.healthmarketscience.jackcess.expr.LocaleContext;
 import com.healthmarketscience.jackcess.expr.Value;
 import static com.healthmarketscience.jackcess.impl.expr.DefaultFunctions.*;
 import static com.healthmarketscience.jackcess.impl.expr.FunctionSupport.*;
@@ -41,7 +42,7 @@ public class DefaultTextFunctions
   public static final Function ASC = registerFunc(new Func1("Asc") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      String str = param1.getAsString();
+      String str = param1.getAsString(ctx);
       int len = str.length();
       if(len == 0) {
         throw new EvalException("No characters in string");
@@ -58,7 +59,7 @@ public class DefaultTextFunctions
   public static final Function ASCW = registerFunc(new Func1("AscW") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      String str = param1.getAsString();
+      String str = param1.getAsString(ctx);
       int len = str.length();
       if(len == 0) {
         throw new EvalException("No characters in string");
@@ -71,7 +72,7 @@ public class DefaultTextFunctions
   public static final Function CHR = registerStringFunc(new Func1NullIsNull("Chr") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      int lv = param1.getAsLongInt();
+      int lv = param1.getAsLongInt(ctx);
       if((lv < 0) || (lv > 255)) {
         throw new EvalException("Character code '" + lv +
                                         "' out of range ");
@@ -84,7 +85,7 @@ public class DefaultTextFunctions
   public static final Function CHRW = registerStringFunc(new Func1NullIsNull("ChrW") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      int lv = param1.getAsLongInt();
+      int lv = param1.getAsLongInt(ctx);
       char[] cs = Character.toChars(lv);
       return ValueSupport.toValue(new String(cs));
     }
@@ -93,7 +94,7 @@ public class DefaultTextFunctions
   public static final Function STR = registerStringFunc(new Func1NullIsNull("Str") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      BigDecimal bd = param1.getAsBigDecimal();
+      BigDecimal bd = param1.getAsBigDecimal(ctx);
       String str = bd.toPlainString();
       if(bd.compareTo(BigDecimal.ZERO) >= 0) {
         str = " " + str;
@@ -109,14 +110,14 @@ public class DefaultTextFunctions
       int start = 0;
       if(params.length > 2) {
         // 1 based offsets
-        start = params[0].getAsLongInt() - 1;
+        start = params[0].getAsLongInt(ctx) - 1;
         ++idx;
       }
       Value param1 = params[idx++];
       if(param1.isNull()) {
         return param1;
       }
-      String s1 = param1.getAsString();
+      String s1 = param1.getAsString(ctx);
       int s1Len = s1.length();
       if(s1Len == 0) {
         return ValueSupport.ZERO_VAL;
@@ -125,7 +126,7 @@ public class DefaultTextFunctions
       if(param2.isNull()) {
         return param2;
       }
-      String s2 = param2.getAsString();
+      String s2 = param2.getAsString(ctx);
       int s2Len = s2.length();
       if(s2Len == 0) {
         // 1 based offsets
@@ -133,7 +134,7 @@ public class DefaultTextFunctions
       }
       boolean ignoreCase = true;
       if(params.length > 3) {
-        ignoreCase = doIgnoreCase(params[3]);
+        ignoreCase = doIgnoreCase(ctx, params[3]);
       }
       int end = s1Len - s2Len;
       while(start < end) {
@@ -154,7 +155,7 @@ public class DefaultTextFunctions
       if(param1.isNull()) {
         return param1;
       }
-      String s1 = param1.getAsString();
+      String s1 = param1.getAsString(ctx);
       int s1Len = s1.length();
       if(s1Len == 0) {
         return ValueSupport.ZERO_VAL;
@@ -163,7 +164,7 @@ public class DefaultTextFunctions
       if(param2.isNull()) {
         return param2;
       }
-      String s2 = param2.getAsString();
+      String s2 = param2.getAsString(ctx);
       int s2Len = s2.length();
       int start = s1Len - 1;
       if(s2Len == 0) {
@@ -171,7 +172,7 @@ public class DefaultTextFunctions
         return ValueSupport.toValue(start + 1);
       }
       if(params.length > 2) {
-        start = params[2].getAsLongInt();
+        start = params[2].getAsLongInt(ctx);
         if(start == -1) {
           start = s1Len;
         }
@@ -180,7 +181,7 @@ public class DefaultTextFunctions
       }
       boolean ignoreCase = true;
       if(params.length > 3) {
-        ignoreCase = doIgnoreCase(params[3]);
+        ignoreCase = doIgnoreCase(ctx, params[3]);
       }
       start = Math.min(s1Len - s2Len, start - s2Len + 1);
       while(start >= 0) {
@@ -197,7 +198,7 @@ public class DefaultTextFunctions
   public static final Function LCASE = registerStringFunc(new Func1NullIsNull("LCase") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      String str = param1.getAsString();
+      String str = param1.getAsString(ctx);
       return ValueSupport.toValue(str.toLowerCase());
     }
   });
@@ -205,7 +206,7 @@ public class DefaultTextFunctions
   public static final Function UCASE = registerStringFunc(new Func1NullIsNull("UCase") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      String str = param1.getAsString();
+      String str = param1.getAsString(ctx);
       return ValueSupport.toValue(str.toUpperCase());
     }
   });
@@ -216,8 +217,8 @@ public class DefaultTextFunctions
       if(param1.isNull()) {
         return param1;
       }
-      String str = param1.getAsString();
-      int len = Math.min(str.length(), param2.getAsLongInt());
+      String str = param1.getAsString(ctx);
+      int len = Math.min(str.length(), param2.getAsLongInt(ctx));
       return ValueSupport.toValue(str.substring(0, len));
     }
   });
@@ -228,9 +229,9 @@ public class DefaultTextFunctions
       if(param1.isNull()) {
         return param1;
       }
-      String str = param1.getAsString();
+      String str = param1.getAsString(ctx);
       int strLen = str.length();
-      int len = Math.min(strLen, param2.getAsLongInt());
+      int len = Math.min(strLen, param2.getAsLongInt(ctx));
       return ValueSupport.toValue(str.substring(strLen - len, strLen));
     }
   });
@@ -242,12 +243,12 @@ public class DefaultTextFunctions
       if(param1.isNull()) {
         return param1;
       }
-      String str = param1.getAsString();
+      String str = param1.getAsString(ctx);
       int strLen = str.length();
       // 1 based offsets
-      int start = Math.min(strLen, params[1].getAsLongInt() - 1);
+      int start = Math.min(strLen, params[1].getAsLongInt(ctx) - 1);
       int len = Math.min(
-          ((params.length > 2) ? params[2].getAsLongInt() : strLen),
+          ((params.length > 2) ? params[2].getAsLongInt(ctx) : strLen),
           (strLen - start));
       return ValueSupport.toValue(str.substring(start, start + len));
     }
@@ -256,7 +257,7 @@ public class DefaultTextFunctions
   public static final Function LEN = registerFunc(new Func1NullIsNull("Len") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      String str = param1.getAsString();
+      String str = param1.getAsString(ctx);
       return ValueSupport.toValue(str.length());
     }
   });
@@ -264,7 +265,7 @@ public class DefaultTextFunctions
   public static final Function LTRIM = registerStringFunc(new Func1NullIsNull("LTrim") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      String str = param1.getAsString();
+      String str = param1.getAsString(ctx);
       return ValueSupport.toValue(trim(str, true, false));
     }
   });
@@ -272,7 +273,7 @@ public class DefaultTextFunctions
   public static final Function RTRIM = registerStringFunc(new Func1NullIsNull("RTrim") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      String str = param1.getAsString();
+      String str = param1.getAsString(ctx);
       return ValueSupport.toValue(trim(str, false, true));
     }
   });
@@ -280,7 +281,7 @@ public class DefaultTextFunctions
   public static final Function TRIM = registerStringFunc(new Func1NullIsNull("Trim") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      String str = param1.getAsString();
+      String str = param1.getAsString(ctx);
       return ValueSupport.toValue(trim(str, true, true));
     }
   });
@@ -288,7 +289,7 @@ public class DefaultTextFunctions
   public static final Function SPACE = registerStringFunc(new Func1("Space") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      int lv = param1.getAsLongInt();
+      int lv = param1.getAsLongInt(ctx);
       return ValueSupport.toValue(nchars(lv, ' '));
     }
   });
@@ -301,11 +302,11 @@ public class DefaultTextFunctions
       if(param1.isNull() || param2.isNull()) {
         return ValueSupport.NULL_VAL;
       }
-      String s1 = param1.getAsString();
-      String s2 = param2.getAsString();
+      String s1 = param1.getAsString(ctx);
+      String s2 = param2.getAsString(ctx);
       boolean ignoreCase = true;
       if(params.length > 2) {
-        ignoreCase = doIgnoreCase(params[2]);
+        ignoreCase = doIgnoreCase(ctx, params[2]);
       }
       int cmp = (ignoreCase ?
                  s1.compareToIgnoreCase(s2) : s1.compareTo(s2));
@@ -322,8 +323,8 @@ public class DefaultTextFunctions
       if(param1.isNull() || param2.isNull()) {
         return ValueSupport.NULL_VAL;
       }
-      int lv = param1.getAsLongInt();
-      char c = (char)(param2.getAsString().charAt(0) % 256);
+      int lv = param1.getAsLongInt(ctx);
+      char c = (char)(param2.getAsString(ctx).charAt(0) % 256);
       return ValueSupport.toValue(nchars(lv, c));
     }
   });
@@ -331,7 +332,7 @@ public class DefaultTextFunctions
   public static final Function STRREVERSE = registerFunc(new Func1("StrReverse") {
     @Override
     protected Value eval1(EvalContext ctx, Value param1) {
-      String str = param1.getAsString();
+      String str = param1.getAsString(ctx);
       return ValueSupport.toValue(
           new StringBuilder(str).reverse().toString());
     }
@@ -363,8 +364,8 @@ public class DefaultTextFunctions
     return str.substring(start, end);
   }
 
-  private static boolean doIgnoreCase(Value paramCmp) {
-    int cmpType = paramCmp.getAsLongInt();
+  private static boolean doIgnoreCase(LocaleContext ctx, Value paramCmp) {
+    int cmpType = paramCmp.getAsLongInt(ctx);
     switch(cmpType) {
     case -1:
       // vbUseCompareOption -> default is binary
index 7f68ad892001f0ae00275623773bb719ee558ec9..fbbdd96cac7ffc0971970902af22eb6d0722a3f2 100644 (file)
@@ -18,6 +18,7 @@ package com.healthmarketscience.jackcess.impl.expr;
 
 import java.math.BigDecimal;
 
+import com.healthmarketscience.jackcess.expr.LocaleContext;
 import com.healthmarketscience.jackcess.impl.NumberFormatter;
 
 /**
@@ -47,22 +48,22 @@ public class DoubleValue extends BaseNumericValue
   }
 
   @Override
-  public boolean getAsBoolean() {
+  public boolean getAsBoolean(LocaleContext ctx) {
     return (_val.doubleValue() != 0.0d);
   }
 
   @Override
-  public Double getAsDouble() {
+  public Double getAsDouble(LocaleContext ctx) {
     return _val;
   }
 
   @Override
-  public BigDecimal getAsBigDecimal() {
+  public BigDecimal getAsBigDecimal(LocaleContext ctx) {
     return BigDecimal.valueOf(_val);
   }
 
   @Override
-  public String getAsString() {
+  public String getAsString(LocaleContext ctx) {
     return NumberFormatter.format(_val);
   }
 }
index c75fae00ca5574cc2f3a1ef9c3392b7aee4b821f..5b2864ad841fe214c92f9ce8a2562e5e42522b57 100644 (file)
@@ -57,7 +57,7 @@ class ExpressionTokenizer
   private static final String AM_SUFFIX = " am";
   private static final String PM_SUFFIX = " pm";
   // access times are based on this date (not the UTC base)
-  private static final String BASE_DATE = "12/30/1899 ";
+  private static final String BASE_DATE = "12/30/1899";
   private static final String BASE_DATE_FMT = "M/d/yyyy";
 
   private static final byte IS_OP_FLAG =     0x01;
@@ -303,12 +303,11 @@ class ExpressionTokenizer
 
     // note that although we may parse in the time "24" format, we will
     // display as the default time format
-    DateFormat parseDf = buf.getDateTimeFormat(type);
-    DateFormat df = buf.getDateTimeFormat(type.getDefaultType());
+    DateFormat parseDf = buf.getParseDateTimeFormat(type);
 
     try {
       return new Token(TokenType.LITERAL, parseComplete(parseDf, dateStr),
-                       dateStr, type.getValueType(), df);
+                       dateStr, type.getValueType());
     } catch(java.text.ParseException pe) {
       throw new ParseException(
           "Invalid date/time literal " + dateStr + " " + buf, pe);
@@ -345,36 +344,34 @@ class ExpressionTokenizer
     return null;
   }
 
-  static DateFormat createParseDateFormat(TemporalConfig.Type type,
-                                          LocaleContext ctx)
+  static DateFormat createParseDateTimeFormat(TemporalConfig.Type type,
+                                              LocaleContext ctx)
   {
-    TemporalConfig cfg = ctx.getTemporalConfig();
-    DateFormat df = ctx.createDateFormat(cfg.getDateTimeFormat(type));
-
-    TemporalConfig.Type parseType = null;
     switch(type) {
     case TIME:
-      parseType = TemporalConfig.Type.DATE_TIME;
-      break;
+      return createParseTimeFormat(TemporalConfig.Type.DATE_TIME, ctx);
     case TIME_12:
-      parseType = TemporalConfig.Type.DATE_TIME_12;
-      break;
+      return createParseTimeFormat(TemporalConfig.Type.DATE_TIME_12, ctx);
     case TIME_24:
-      parseType = TemporalConfig.Type.DATE_TIME_24;
-      break;
+      return createParseTimeFormat(TemporalConfig.Type.DATE_TIME_24, ctx);
     default:
+      // use normal formatter
     }
 
-    if(parseType != null) {
-      // we need to use a special DateFormat impl which handles parsing
-      // separately from formatting
-      String baseDate = getBaseDatePrefix(ctx);
-      DateFormat parseDf = ctx.createDateFormat(
-          cfg.getDateTimeFormat(parseType));
-      df = new TimeFormat(parseDf, df, baseDate);
-    }
+    TemporalConfig cfg = ctx.getTemporalConfig();
+    return ctx.createDateFormat(cfg.getDateTimeFormat(type));
+  }
 
-    return df;
+  private static DateFormat createParseTimeFormat(TemporalConfig.Type parseType,
+                                                  LocaleContext ctx)
+  {
+    TemporalConfig cfg = ctx.getTemporalConfig();
+    // we need to use a special DateFormat impl which manipulates the parsed
+    // time-only value so it becomes the right Date value
+    String baseDate = getBaseDatePrefix(ctx);
+    DateFormat parseDf = ctx.createDateFormat(
+        cfg.getDateTimeFormat(parseType));
+    return new ParseTimeFormat(parseDf, baseDate);
   }
 
   private static String getBaseDatePrefix(LocaleContext ctx) {
@@ -562,10 +559,10 @@ class ExpressionTokenizer
       return _ctx;
     }
 
-    public DateFormat getDateTimeFormat(TemporalConfig.Type type) {
+    public DateFormat getParseDateTimeFormat(TemporalConfig.Type type) {
       DateFormat df = _dateTimeFmts.get(type);
       if(df == null) {
-        df = createParseDateFormat(type, _ctx);
+        df = createParseDateTimeFormat(type, _ctx);
         _dateTimeFmts.put(type, df);
       }
       return df;
@@ -584,27 +581,20 @@ class ExpressionTokenizer
     private final Object _val;
     private final String _valStr;
     private final Value.Type _valType;
-    private final DateFormat _sdf;
 
     private Token(TokenType type, String val) {
       this(type, val, val);
     }
 
     private Token(TokenType type, Object val, String valStr) {
-      this(type, val, valStr, null, null);
+      this(type, val, valStr, null);
     }
 
     private Token(TokenType type, Object val, String valStr, Value.Type valType) {
-      this(type, val, valStr, valType, null);
-    }
-
-    private Token(TokenType type, Object val, String valStr, Value.Type valType,
-                  DateFormat sdf) {
       _type = type;
       _val = ((val != null) ? val : valStr);
       _valStr = valStr;
       _valType = valType;
-      _sdf = sdf;
     }
 
     public TokenType getType() {
@@ -623,10 +613,6 @@ class ExpressionTokenizer
       return _valType;
     }
 
-    public DateFormat getDateFormat() {
-      return _sdf;
-    }
-
     @Override
     public String toString() {
       if(_type == TokenType.SPACE) {
@@ -644,25 +630,22 @@ class ExpressionTokenizer
    * Special date/time format which will parse time-only strings "correctly"
    * according to how access handles time-only values.
    */
-  private static final class TimeFormat extends DateFormat
+  private static final class ParseTimeFormat extends DateFormat
   {
     private static final long serialVersionUID = 0L;
 
     private final DateFormat _parseDelegate;
-    private final DateFormat _fmtDelegate;
     private final String _baseDate;
 
-    private TimeFormat(DateFormat parseDelegate, DateFormat fmtDelegate,
-                       String baseDate)
+    private ParseTimeFormat(DateFormat parseDelegate, String baseDate)
     {
       _parseDelegate = parseDelegate;
-      _fmtDelegate = fmtDelegate;
       _baseDate = baseDate;
     }
 
     @Override
     public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
-      return _fmtDelegate.format(date, toAppendTo, fieldPosition);
+      throw new UnsupportedOperationException();
     }
 
     @Override
@@ -674,12 +657,12 @@ class ExpressionTokenizer
 
     @Override
     public Calendar getCalendar() {
-      return _fmtDelegate.getCalendar();
+      return _parseDelegate.getCalendar();
     }
 
     @Override
     public TimeZone getTimeZone() {
-      return _fmtDelegate.getTimeZone();
+      return _parseDelegate.getTimeZone();
     }
   }
 
index d53d6d3afd73ce334e2bd638bd7ac7f48672ba9c..d2bb847d6a847c150610804810250b81b0e66f8f 100644 (file)
@@ -47,6 +47,7 @@ import com.healthmarketscience.jackcess.expr.TemporalConfig;
 import com.healthmarketscience.jackcess.expr.Value;
 import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.Token;
 import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.TokenType;
+import org.apache.commons.lang.StringUtils;
 
 
 /**
@@ -125,7 +126,7 @@ public class Expressionator
     },
     NOT("Not", true) {
       @Override public Value eval(EvalContext ctx, Value param1) {
-        return BuiltinOperators.not(param1);
+        return BuiltinOperators.not(ctx, param1);
       }
     },
     // when a '-' immediately precedes a number, it needs "highest" precedence
@@ -179,32 +180,32 @@ public class Expressionator
     },
     MULT("*") {
       @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
-        return BuiltinOperators.multiply(param1, param2);
+        return BuiltinOperators.multiply(ctx, param1, param2);
       }
     },
     DIV("/") {
       @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
-        return BuiltinOperators.divide(param1, param2);
+        return BuiltinOperators.divide(ctx, param1, param2);
       }
     },
     INT_DIV("\\") {
       @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
-        return BuiltinOperators.intDivide(param1, param2);
+        return BuiltinOperators.intDivide(ctx, param1, param2);
       }
     },
     EXP("^") {
       @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
-        return BuiltinOperators.exp(param1, param2);
+        return BuiltinOperators.exp(ctx, param1, param2);
       }
     },
     CONCAT("&") {
       @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
-        return BuiltinOperators.concat(param1, param2);
+        return BuiltinOperators.concat(ctx, param1, param2);
       }
     },
     MOD("Mod") {
       @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
-        return BuiltinOperators.mod(param1, param2);
+        return BuiltinOperators.mod(ctx, param1, param2);
       }
     };
 
@@ -224,33 +225,33 @@ public class Expressionator
 
   private enum CompOp implements OpType {
     LT("<") {
-      @Override public Value eval(Value param1, Value param2) {
-        return BuiltinOperators.lessThan(param1, param2);
+      @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
+        return BuiltinOperators.lessThan(ctx, param1, param2);
       }
     },
     LTE("<=") {
-      @Override public Value eval(Value param1, Value param2) {
-        return BuiltinOperators.lessThanEq(param1, param2);
+      @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
+        return BuiltinOperators.lessThanEq(ctx, param1, param2);
       }
     },
     GT(">") {
-      @Override public Value eval(Value param1, Value param2) {
-        return BuiltinOperators.greaterThan(param1, param2);
+      @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
+        return BuiltinOperators.greaterThan(ctx, param1, param2);
       }
     },
     GTE(">=") {
-      @Override public Value eval(Value param1, Value param2) {
-        return BuiltinOperators.greaterThanEq(param1, param2);
+      @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
+        return BuiltinOperators.greaterThanEq(ctx, param1, param2);
       }
     },
     EQ("=") {
-      @Override public Value eval(Value param1, Value param2) {
-        return BuiltinOperators.equals(param1, param2);
+      @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
+        return BuiltinOperators.equals(ctx, param1, param2);
       }
     },
     NE("<>") {
-      @Override public Value eval(Value param1, Value param2) {
-        return BuiltinOperators.notEquals(param1, param2);
+      @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
+        return BuiltinOperators.notEquals(ctx, param1, param2);
       }
     };
 
@@ -265,33 +266,33 @@ public class Expressionator
       return _str;
     }
 
-    public abstract Value eval(Value param1, Value param2);
+    public abstract Value eval(EvalContext ctx, Value param1, Value param2);
   }
 
   private enum LogOp implements OpType {
     AND("And") {
-      @Override public Value eval(Value param1, Value param2) {
-        return BuiltinOperators.and(param1, param2);
+      @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
+        return BuiltinOperators.and(ctx, param1, param2);
       }
     },
     OR("Or") {
-      @Override public Value eval(Value param1, Value param2) {
-        return BuiltinOperators.or(param1, param2);
+      @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
+        return BuiltinOperators.or(ctx, param1, param2);
       }
     },
     EQV("Eqv") {
-      @Override public Value eval(Value param1, Value param2) {
-        return BuiltinOperators.eqv(param1, param2);
+      @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
+        return BuiltinOperators.eqv(ctx, param1, param2);
       }
     },
     XOR("Xor") {
-      @Override public Value eval(Value param1, Value param2) {
-        return BuiltinOperators.xor(param1, param2);
+      @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
+        return BuiltinOperators.xor(ctx, param1, param2);
       }
     },
     IMP("Imp") {
-      @Override public Value eval(Value param1, Value param2) {
-        return BuiltinOperators.imp(param1, param2);
+      @Override public Value eval(EvalContext ctx, Value param1, Value param2) {
+        return BuiltinOperators.imp(ctx, param1, param2);
       }
     };
 
@@ -306,55 +307,55 @@ public class Expressionator
       return _str;
     }
 
-    public abstract Value eval(Value param1, Value param2);
+    public abstract Value eval(EvalContext ctx, Value param1, Value param2);
   }
 
   private enum SpecOp implements OpType {
     // note, "NOT" is not actually used as a special operation, always
     // replaced with UnaryOp.NOT
     NOT("Not") {
-      @Override public Value eval(Value param1, Object param2, Object param3) {
+      @Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
         throw new UnsupportedOperationException();
       }
     },
     IS_NULL("Is Null") {
-      @Override public Value eval(Value param1, Object param2, Object param3) {
+      @Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
         return BuiltinOperators.isNull(param1);
       }
     },
     IS_NOT_NULL("Is Not Null") {
-      @Override public Value eval(Value param1, Object param2, Object param3) {
+      @Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
         return BuiltinOperators.isNotNull(param1);
       }
     },
     LIKE("Like") {
-      @Override public Value eval(Value param1, Object param2, Object param3) {
-        return BuiltinOperators.like(param1, (Pattern)param2);
+      @Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
+        return BuiltinOperators.like(ctx, param1, (Pattern)param2);
       }
     },
     NOT_LIKE("Not Like") {
-      @Override public Value eval(Value param1, Object param2, Object param3) {
-        return BuiltinOperators.notLike(param1, (Pattern)param2);
+      @Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
+        return BuiltinOperators.notLike(ctx, param1, (Pattern)param2);
       }
     },
     BETWEEN("Between") {
-      @Override public Value eval(Value param1, Object param2, Object param3) {
-        return BuiltinOperators.between(param1, (Value)param2, (Value)param3);
+      @Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
+        return BuiltinOperators.between(ctx, param1, (Value)param2, (Value)param3);
       }
     },
     NOT_BETWEEN("Not Between") {
-      @Override public Value eval(Value param1, Object param2, Object param3) {
-        return BuiltinOperators.notBetween(param1, (Value)param2, (Value)param3);
+      @Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
+        return BuiltinOperators.notBetween(ctx, param1, (Value)param2, (Value)param3);
       }
     },
     IN("In") {
-      @Override public Value eval(Value param1, Object param2, Object param3) {
-        return BuiltinOperators.in(param1, (Value[])param2);
+      @Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
+        return BuiltinOperators.in(ctx, param1, (Value[])param2);
       }
     },
     NOT_IN("Not In") {
-      @Override public Value eval(Value param1, Object param2, Object param3) {
-        return BuiltinOperators.notIn(param1, (Value[])param2);
+      @Override public Value eval(EvalContext ctx, Value param1, Object param2, Object param3) {
+        return BuiltinOperators.notIn(ctx, param1, (Value[])param2);
       }
     };
 
@@ -369,7 +370,7 @@ public class Expressionator
       return _str;
     }
 
-    public abstract Value eval(Value param1, Object param2, Object param3);
+    public abstract Value eval(EvalContext ctx, Value param1, Object param2, Object param3);
   }
 
   private static final Map<OpType, Integer> PRECENDENCE =
@@ -429,7 +430,7 @@ public class Expressionator
       // this is handled as a literal string value, not an expression.  no
       // need to memo-ize cause it's a simple literal value
       return new ExprWrapper(exprStr,
-          new ELiteralValue(Value.Type.STRING, exprStr, null), resultType);
+          new ELiteralValue(Value.Type.STRING, exprStr), resultType);
     }
 
     // normal expression handling
@@ -498,8 +499,7 @@ public class Expressionator
 
       case LITERAL:
 
-        buf.setPendingExpr(new ELiteralValue(t.getValueType(), t.getValue(),
-                                             t.getDateFormat()));
+        buf.setPendingExpr(new ELiteralValue(t.getValueType(), t.getValue()));
         break;
 
       case OP:
@@ -1027,13 +1027,25 @@ public class Expressionator
     throw new ParseException("Unexpected op string " + t.getValueStr());
   }
 
+  private static StringBuilder appendLeadingExpr(
+      Expr expr, LocaleContext ctx, StringBuilder sb, boolean isDebug)
+  {
+    int len = sb.length();
+    expr.toString(ctx, sb, isDebug);
+    if(sb.length() > len) {
+      // only add space if the leading expr added some text
+      sb.append(" ");
+    }
+    return sb;
+  }
+
   private static final class TokBuf
   {
     private final Type _exprType;
     private final List<Token> _tokens;
     private final TokBuf _parent;
     private final int _parentOff;
-    private final ParseContext _context;
+    private final ParseContext _ctx;
     private int _pos;
     private Expr _pendingExpr;
 
@@ -1042,7 +1054,7 @@ public class Expressionator
     }
 
     private TokBuf(List<Token> tokens, TokBuf parent, int parentOff) {
-      this(parent._exprType, tokens, parent, parentOff, parent._context);
+      this(parent._exprType, tokens, parent, parentOff, parent._ctx);
     }
 
     private TokBuf(Type exprType, List<Token> tokens, TokBuf parent,
@@ -1051,7 +1063,7 @@ public class Expressionator
       _tokens = tokens;
       _parent = parent;
       _parentOff = parentOff;
-      _context = context;
+      _ctx = context;
     }
 
     public Type getExprType() {
@@ -1129,7 +1141,7 @@ public class Expressionator
     }
 
     public Function getFunction(String funcName) {
-      return _context.getFunctionLookup().getFunction(funcName);
+      return _ctx.getFunctionLookup().getFunction(funcName);
     }
 
     @Override
@@ -1152,7 +1164,7 @@ public class Expressionator
       sb.append(")");
 
       if(_pendingExpr != null) {
-        sb.append(" [pending '").append(_pendingExpr.toDebugString())
+        sb.append(" [pending '").append(_pendingExpr.toDebugString(_ctx))
           .append("']");
       }
 
@@ -1184,12 +1196,13 @@ public class Expressionator
   }
 
   private static void exprListToString(
-      List<Expr> exprs, String sep, StringBuilder sb, boolean isDebug) {
+      List<Expr> exprs, String sep, LocaleContext ctx, StringBuilder sb,
+      boolean isDebug) {
     Iterator<Expr> iter = exprs.iterator();
-    iter.next().toString(sb, isDebug);
+    iter.next().toString(ctx, sb, isDebug);
     while(iter.hasNext()) {
       sb.append(sep);
-      iter.next().toString(sb, isDebug);
+      iter.next().toString(ctx, sb, isDebug);
     }
   }
 
@@ -1231,7 +1244,7 @@ public class Expressionator
 
   private static void literalStrToString(String str, StringBuilder sb) {
     sb.append("\"")
-      .append(str.replace("\"", "\"\""))
+      .append(StringUtils.replace(str, "\"", "\"\""))
       .append("\"");
   }
 
@@ -1298,18 +1311,14 @@ public class Expressionator
     }
   }
 
-  private static Value toLiteralValue(Value.Type valType, Object value,
-                                      DateFormat sdf)
-  {
+  private static Value toLiteralValue(Value.Type valType, Object value) {
     switch(valType) {
     case STRING:
       return ValueSupport.toValue((String)value);
     case DATE:
-      return new DateValue((Date)value, sdf);
     case TIME:
-      return new TimeValue((Date)value, sdf);
     case DATE_TIME:
-      return new DateTimeValue((Date)value, sdf);
+      return ValueSupport.toValue(valType, (Date)value);
     case LONG:
       return ValueSupport.toValue((Integer)value);
     case DOUBLE:
@@ -1375,31 +1384,28 @@ public class Expressionator
 
   private static abstract class Expr
   {
-    @Override
-    public String toString() {
-      StringBuilder sb = new StringBuilder();
-      toString(sb, false);
-      return sb.toString();
+    public String toCleanString(LocaleContext ctx) {
+      return toString(ctx, new StringBuilder(), false).toString();
     }
 
-    public String toDebugString() {
-      StringBuilder sb = new StringBuilder();
-      toString(sb, true);
-      return sb.toString();
+    public String toDebugString(LocaleContext ctx) {
+      return toString(ctx, new StringBuilder(), true).toString();
     }
 
     protected boolean isValidationExpr() {
       return false;
     }
 
-    protected void toString(StringBuilder sb, boolean isDebug) {
+    protected StringBuilder toString(
+        LocaleContext ctx, StringBuilder sb, boolean isDebug) {
       if(isDebug) {
         sb.append("<").append(getClass().getSimpleName()).append(">{");
       }
-      toExprString(sb, isDebug);
+      toExprString(ctx, sb, isDebug);
       if(isDebug) {
         sb.append("}");
       }
+      return sb;
     }
 
     protected Expr resolveOrderOfOperations() {
@@ -1464,7 +1470,8 @@ public class Expressionator
 
     public abstract void collectIdentifiers(Collection<Identifier> identifiers);
 
-    protected abstract void toExprString(StringBuilder sb, boolean isDebug);
+    protected abstract void toExprString(
+        LocaleContext ctx, StringBuilder sb, boolean isDebug);
   }
 
   private static final class EConstValue extends Expr
@@ -1493,7 +1500,8 @@ public class Expressionator
     }
 
     @Override
-    protected void toExprString(StringBuilder sb, boolean isDebug) {
+    protected void toExprString(
+        LocaleContext ctx, StringBuilder sb, boolean isDebug) {
       sb.append(_str);
     }
   }
@@ -1513,8 +1521,11 @@ public class Expressionator
       // none
     }
     @Override
-    protected void toExprString(StringBuilder sb, boolean isDebug) {
-      sb.append("<THIS_COL>");
+    protected void toExprString(
+        LocaleContext ctx, StringBuilder sb, boolean isDebug) {
+      if(isDebug) {
+        sb.append("<THIS_COL>");
+      }
     }
   }
 
@@ -1522,9 +1533,8 @@ public class Expressionator
   {
     private final Value _val;
 
-    private ELiteralValue(Value.Type valType, Object value,
-                          DateFormat sdf) {
-      _val = toLiteralValue(valType, value, sdf);
+    private ELiteralValue(Value.Type valType, Object value) {
+      _val = toLiteralValue(valType, value);
     }
 
     @Override
@@ -1543,11 +1553,12 @@ public class Expressionator
     }
 
     @Override
-    protected void toExprString(StringBuilder sb, boolean isDebug) {
+    protected void toExprString(
+        LocaleContext ctx, StringBuilder sb, boolean isDebug) {
       if(_val.getType() == Value.Type.STRING) {
         literalStrToString((String)_val.get(), sb);
       } else if(_val.getType().isTemporal()) {
-        sb.append("#").append(_val.getAsString()).append("#");
+        sb.append("#").append(_val.getAsString(ctx)).append("#");
       } else {
         sb.append(_val.get());
       }
@@ -1578,7 +1589,8 @@ public class Expressionator
     }
 
     @Override
-    protected void toExprString(StringBuilder sb, boolean isDebug) {
+    protected void toExprString(
+        LocaleContext ctx, StringBuilder sb, boolean isDebug) {
       sb.append(_identifier);
     }
   }
@@ -1612,9 +1624,10 @@ public class Expressionator
     }
 
     @Override
-    protected void toExprString(StringBuilder sb, boolean isDebug) {
+    protected void toExprString(
+        LocaleContext ctx, StringBuilder sb, boolean isDebug) {
       sb.append("(");
-      _expr.toString(sb, isDebug);
+      _expr.toString(ctx, sb, isDebug);
       sb.append(")");
     }
   }
@@ -1647,11 +1660,12 @@ public class Expressionator
     }
 
     @Override
-    protected void toExprString(StringBuilder sb, boolean isDebug) {
+    protected void toExprString(
+        LocaleContext ctx, StringBuilder sb, boolean isDebug) {
       sb.append(_func.getName()).append("(");
 
       if(!_params.isEmpty()) {
-        exprListToString(_params, ",", sb, isDebug);
+        exprListToString(_params, ",", ctx, sb, isDebug);
       }
 
       sb.append(")");
@@ -1703,10 +1717,11 @@ public class Expressionator
     }
 
     @Override
-    protected void toExprString(StringBuilder sb, boolean isDebug) {
-      _left.toString(sb, isDebug);
-      sb.append(" ").append(_op).append(" ");
-      _right.toString(sb, isDebug);
+    protected void toExprString(
+        LocaleContext ctx, StringBuilder sb, boolean isDebug) {
+      appendLeadingExpr(_left, ctx, sb, isDebug)
+        .append(_op).append(" ");
+      _right.toString(ctx, sb, isDebug);
     }
   }
 
@@ -1761,12 +1776,13 @@ public class Expressionator
     }
 
     @Override
-    protected void toExprString(StringBuilder sb, boolean isDebug) {
+    protected void toExprString(
+        LocaleContext ctx, StringBuilder sb, boolean isDebug) {
       sb.append(_op);
       if(isDebug || ((UnaryOp)_op).needsSpace()) {
         sb.append(" ");
       }
-      _expr.toString(sb, isDebug);
+      _expr.toString(ctx, sb, isDebug);
     }
   }
 
@@ -1783,7 +1799,7 @@ public class Expressionator
 
     @Override
     public Value eval(EvalContext ctx) {
-      return ((CompOp)_op).eval(_left.eval(ctx), _right.eval(ctx));
+      return ((CompOp)_op).eval(ctx, _left.eval(ctx), _right.eval(ctx));
     }
   }
 
@@ -1794,13 +1810,14 @@ public class Expressionator
     }
 
     @Override
-    protected void toExprString(StringBuilder sb, boolean isDebug) {
+    protected void toExprString(
+        LocaleContext ctx, StringBuilder sb, boolean isDebug) {
       // only output the full "implicit" comparison in debug mode
       if(isDebug) {
-        super.toExprString(sb, isDebug);
+        super.toExprString(ctx, sb, isDebug);
       } else {
         // just output the explicit part of the expression
-        _right.toString(sb, isDebug);
+        _right.toString(ctx, sb, isDebug);
       }
     }
   }
@@ -1821,7 +1838,7 @@ public class Expressionator
 
       // logical operations do short circuit evaluation, so we need to delay
       // computing results until necessary
-      return ((LogOp)_op).eval(new DelayedValue(_left, ctx),
+      return ((LogOp)_op).eval(ctx, new DelayedValue(_left, ctx),
                                new DelayedValue(_right, ctx));
     }
   }
@@ -1873,13 +1890,14 @@ public class Expressionator
 
     @Override
     public Value eval(EvalContext ctx) {
-      return _op.eval(_expr.eval(ctx), null, null);
+      return _op.eval(ctx, _expr.eval(ctx), null, null);
     }
 
     @Override
-    protected void toExprString(StringBuilder sb, boolean isDebug) {
-      _expr.toString(sb, isDebug);
-      sb.append(" ").append(_op);
+    protected void toExprString(
+        LocaleContext ctx, StringBuilder sb, boolean isDebug) {
+      appendLeadingExpr(_expr, ctx, sb, isDebug)
+        .append(_op);
     }
   }
 
@@ -1903,13 +1921,14 @@ public class Expressionator
 
     @Override
     public Value eval(EvalContext ctx) {
-      return _op.eval(_expr.eval(ctx), getPattern(), null);
+      return _op.eval(ctx, _expr.eval(ctx), getPattern(), null);
     }
 
     @Override
-    protected void toExprString(StringBuilder sb, boolean isDebug) {
-      _expr.toString(sb, isDebug);
-      sb.append(" ").append(_op).append(" ");
+    protected void toExprString(
+        LocaleContext ctx, StringBuilder sb, boolean isDebug) {
+      appendLeadingExpr(_expr, ctx, sb, isDebug)
+        .append(_op).append(" ");
       literalStrToString(_patternStr, sb);
       if(isDebug) {
         sb.append("(").append(getPattern()).append(")");
@@ -1933,7 +1952,7 @@ public class Expressionator
 
     @Override
     public Value eval(EvalContext ctx) {
-      return _op.eval(_expr.eval(ctx),
+      return _op.eval(ctx, _expr.eval(ctx),
                       exprListToDelayedValues(_exprs, ctx), null);
     }
 
@@ -1945,10 +1964,11 @@ public class Expressionator
     }
 
     @Override
-    protected void toExprString(StringBuilder sb, boolean isDebug) {
-      _expr.toString(sb, isDebug);
-      sb.append(" ").append(_op).append(" (");
-      exprListToString(_exprs, ",", sb, isDebug);
+    protected void toExprString(
+        LocaleContext ctx, StringBuilder sb, boolean isDebug) {
+      appendLeadingExpr(_expr, ctx, sb, isDebug)
+        .append(_op).append(" (");
+      exprListToString(_exprs, ",", ctx, sb, isDebug);
       sb.append(")");
     }
   }
@@ -1981,7 +2001,7 @@ public class Expressionator
 
     @Override
     public Value eval(EvalContext ctx) {
-      return _op.eval(_expr.eval(ctx),
+      return _op.eval(ctx, _expr.eval(ctx),
                       new DelayedValue(_startRangeExpr, ctx),
                       new DelayedValue(_endRangeExpr, ctx));
     }
@@ -1994,12 +2014,13 @@ public class Expressionator
     }
 
     @Override
-    protected void toExprString(StringBuilder sb, boolean isDebug) {
-      _expr.toString(sb, isDebug);
-      sb.append(" ").append(_op).append(" ");
-      _startRangeExpr.toString(sb, isDebug);
+    protected void toExprString(
+        LocaleContext ctx, StringBuilder sb, boolean isDebug) {
+      appendLeadingExpr(_expr, ctx, sb, isDebug)
+        .append(_op).append(" ");
+      _startRangeExpr.toString(ctx, sb, isDebug);
       sb.append(" And ");
-      _endRangeExpr.toString(sb, isDebug);
+      _endRangeExpr.toString(ctx, sb, isDebug);
     }
   }
 
@@ -2016,14 +2037,18 @@ public class Expressionator
       _expr = expr;
     }
 
-    public String toDebugString() {
-      return _expr.toDebugString();
+    public String toDebugString(LocaleContext ctx) {
+      return _expr.toDebugString(ctx);
     }
 
     public String toRawString() {
       return _rawExprStr;
     }
 
+    public String toCleanString(LocaleContext ctx) {
+      return _expr.toCleanString(ctx);
+    }
+
     public boolean isConstant() {
       return _expr.isConstant();
     }
@@ -2034,7 +2059,7 @@ public class Expressionator
 
     @Override
     public String toString() {
-      return _expr.toString();
+      return toRawString();
     }
 
     protected Object evalValue(Value.Type resultType, EvalContext ctx) {
@@ -2052,17 +2077,17 @@ public class Expressionator
       // FIXME possibly do some type coercion.  are there conversions here which don't work elsewhere? (string -> date, string -> number)?
       switch(resultType) {
       case STRING:
-        return val.getAsString();
+        return val.getAsString(ctx);
       case DATE:
       case TIME:
       case DATE_TIME:
         return val.getAsDateTime(ctx);
       case LONG:
-        return val.getAsLongInt();
+        return val.getAsLongInt(ctx);
       case DOUBLE:
-        return val.getAsDouble();
+        return val.getAsDouble(ctx);
       case BIG_DEC:
-        return val.getAsBigDecimal();
+        return val.getAsBigDecimal(ctx);
       default:
         throw new IllegalStateException("unexpected result type " + resultType);
       }
@@ -2076,7 +2101,7 @@ public class Expressionator
         throw new EvalException("Condition evaluated to Null");
       }
 
-      return val.getAsBoolean();
+      return val.getAsBoolean(ctx);
     }
   }
 
index bc87f5938adaf31d7001c85c4c98144e561b3328..483ecd85234cb7673f57356deea49fb8e895486f 100644 (file)
@@ -227,7 +227,7 @@ public class FunctionSupport
       if(result.isNull()) {
         // non-variant version does not do null-propagation, so force
         // exception to be thrown here
-        result.getAsString();
+        result.getAsString(ctx);
       }
       return result;
     }
index 3a47a843e329697f314b6ae3a94235aa6421b7e7..30025ad9be21ee4ec5ef118c9ffd60e97b67c3fc 100644 (file)
@@ -18,6 +18,8 @@ package com.healthmarketscience.jackcess.impl.expr;
 
 import java.math.BigDecimal;
 
+import com.healthmarketscience.jackcess.expr.LocaleContext;
+
 /**
  *
  * @author James Ahlborn
@@ -45,22 +47,22 @@ public class LongValue extends BaseNumericValue
   }
 
   @Override
-  public boolean getAsBoolean() {
+  public boolean getAsBoolean(LocaleContext ctx) {
     return (_val.longValue() != 0L);
   }
 
   @Override
-  public Integer getAsLongInt() {
+  public Integer getAsLongInt(LocaleContext ctx) {
     return _val;
   }
 
   @Override
-  public BigDecimal getAsBigDecimal() {
+  public BigDecimal getAsBigDecimal(LocaleContext ctx) {
     return BigDecimal.valueOf(_val);
   }
 
   @Override
-  public String getAsString() {
+  public String getAsString(LocaleContext ctx) {
     return _val.toString();
   }
 }
index 781faf4df0a6a8b005e222da4fae403b59a49e85..d3b829b7c95aca7d6e51f6b2d6ecd4abc0100b70 100644 (file)
@@ -20,6 +20,10 @@ import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.util.regex.Pattern;
 
+import com.healthmarketscience.jackcess.expr.LocaleContext;
+import org.apache.commons.lang.CharUtils;
+import org.apache.commons.lang.StringUtils;
+
 /**
  *
  * @author James Ahlborn
@@ -51,32 +55,32 @@ public class StringValue extends BaseValue
   }
 
   @Override
-  public boolean getAsBoolean() {
+  public boolean getAsBoolean(LocaleContext ctx) {
     // ms access seems to treat strings as "true"
     return true;
   }
 
   @Override
-  public String getAsString() {
+  public String getAsString(LocaleContext ctx) {
     return _val;
   }
 
   @Override
-  public Integer getAsLongInt() {
-    return roundToLongInt();
+  public Integer getAsLongInt(LocaleContext ctx) {
+    return roundToLongInt(ctx);
   }
 
   @Override
-  public Double getAsDouble() {
-    return getNumber().doubleValue();
+  public Double getAsDouble(LocaleContext ctx) {
+    return getNumber(ctx).doubleValue();
   }
 
   @Override
-  public BigDecimal getAsBigDecimal() {
-    return getNumber();
+  public BigDecimal getAsBigDecimal(LocaleContext ctx) {
+    return getNumber(ctx);
   }
 
-  protected BigDecimal getNumber() {
+  protected BigDecimal getNumber(LocaleContext ctx) {
     if(_num instanceof BigDecimal) {
       return (BigDecimal)_num;
     }
@@ -89,8 +93,11 @@ public class StringValue extends BaseValue
         if(tmpVal.length() > 0) {
 
           if(tmpVal.charAt(0) != NUMBER_BASE_PREFIX) {
-            // parse using standard numeric support
-            // FIXME, this should handle grouping separator, but needs ctx
+            // parse using standard numeric support, after discarding any
+            // grouping separators
+            char groupSepChar = ctx.getNumericConfig().getDecimalFormatSymbols()
+              .getGroupingSeparator();
+            tmpVal = StringUtils.remove(tmpVal, groupSepChar);
             _num = ValueSupport.normalize(new BigDecimal(tmpVal));
             return (BigDecimal)_num;
           }
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 (file)
index cedb461..0000000
+++ /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;
-  }
-}
index b0924f3c525a05cb2a989a259b6a063c4f59a364..06833de02eac04405b99211864c9e0940a1f673c 100644 (file)
@@ -85,46 +85,17 @@ public class ValueSupport
     return new BigDecimalValue(normalize(s));
   }
 
-  public static Value toValue(Value.Type type, double dd, DateFormat fmt) {
-    return toValue(type, new Date(ColumnImpl.fromDateDouble(
-                                      dd, fmt.getCalendar())), fmt);
-  }
-
-  public static Value toValue(LocaleContext ctx, Value.Type type, Date d) {
-    return toValue(type, d, getDateFormatForType(ctx, type));
-  }
-
-  public static Value toValue(Value.Type type, Date d, DateFormat fmt) {
-    switch(type) {
-    case DATE:
-      return new DateValue(d, fmt);
-    case TIME:
-      return new TimeValue(d, fmt);
-    case DATE_TIME:
-      return new DateTimeValue(d, fmt);
-    default:
-      throw new EvalException("Unexpected date/time type " + type);
-    }
-  }
-
-  static Value toDateValue(LocaleContext ctx, Value.Type type, double v,
-                           Value param1, Value param2)
+  public static Value toDateValue(LocaleContext ctx, Value.Type type, double dd)
   {
-    DateFormat fmt = null;
-    if((param1 instanceof BaseDateValue) && (param1.getType() == type)) {
-      fmt = ((BaseDateValue)param1).getFormat();
-    } else if((param2 instanceof BaseDateValue) && (param2.getType() == type)) {
-      fmt = ((BaseDateValue)param2).getFormat();
-    } else {
-      fmt = getDateFormatForType(ctx, type);
-    }
-
-    Date d = new Date(ColumnImpl.fromDateDouble(v, fmt.getCalendar()));
+    return toValue(type, new Date(
+                       ColumnImpl.fromDateDouble(dd, ctx.getCalendar())));
+  }
 
-    return toValue(type, d, fmt);
+  public static Value toValue(Value.Type type, Date d) {
+    return new DateTimeValue(type, d);
   }
 
-  static DateFormat getDateFormatForType(LocaleContext ctx, Value.Type type) {
+  public static DateFormat getDateFormatForType(LocaleContext ctx, Value.Type type) {
       String fmtStr = null;
       switch(type) {
       case DATE:
index 5b623a0e9840aad6ad11739403cf7c0e42b43884..d7b5a00a14ea8033817f6ec33300707bea348cf2 100644 (file)
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
 import java.io.FileReader;
 import java.math.BigDecimal;
 import java.text.SimpleDateFormat;
+import java.util.Calendar;
 import java.util.Date;
 import javax.script.Bindings;
 import javax.script.SimpleBindings;
@@ -31,6 +32,7 @@ import com.healthmarketscience.jackcess.expr.EvalContext;
 import com.healthmarketscience.jackcess.expr.Expression;
 import com.healthmarketscience.jackcess.expr.FunctionLookup;
 import com.healthmarketscience.jackcess.expr.Identifier;
+import com.healthmarketscience.jackcess.expr.NumericConfig;
 import com.healthmarketscience.jackcess.expr.ParseException;
 import com.healthmarketscience.jackcess.expr.TemporalConfig;
 import com.healthmarketscience.jackcess.expr.Value;
@@ -98,7 +100,7 @@ public class ExpressionatorTest extends TestCase
     validateExpr("' \"A\" '", "<ELiteralValue>{\" \"\"A\"\" \"}",
                  "\" \"\"A\"\" \"");
 
-    validateExpr("<=1 And >=0", "<ELogicalOp>{<ECompOp>{<EThisValue>{<THIS_COL>} <= <ELiteralValue>{1}} And <ECompOp>{<EThisValue>{<THIS_COL>} >= <ELiteralValue>{0}}}", "<THIS_COL> <= 1 And <THIS_COL> >= 0");
+    validateExpr("<=1 And >=0", "<ELogicalOp>{<ECompOp>{<EThisValue>{<THIS_COL>} <= <ELiteralValue>{1}} And <ECompOp>{<EThisValue>{<THIS_COL>} >= <ELiteralValue>{0}}}", "<= 1 And >= 0");
   }
 
   private static void doTestSimpleBinOp(String opName, String... ops) throws Exception
@@ -387,6 +389,7 @@ public class ExpressionatorTest extends TestCase
     assertEquals(37d, eval("=\" &h1A \" + 11"));
     assertEquals(37d, eval("=\" &h1a \" + 11"));
     assertEquals(37d, eval("=\" &O32 \" + 11"));
+    assertEquals(1037d, eval("=\"1,025\" + 12"));
 
     evalFail(("=12 - \"foo\""), RuntimeException.class);
     evalFail(("=\"foo\" - 12"), RuntimeException.class);
@@ -403,12 +406,12 @@ public class ExpressionatorTest extends TestCase
   public void testLikeExpression() throws Exception
   {
     validateExpr("Like \"[abc]*\"", "<ELikeOp>{<EThisValue>{<THIS_COL>} Like \"[abc]*\"([abc].*)}",
-                 "<THIS_COL> Like \"[abc]*\"");
+                 "Like \"[abc]*\"");
     assertTrue(evalCondition("Like \"[abc]*\"", "afcd"));
     assertFalse(evalCondition("Like \"[abc]*\"", "fcd"));
 
-    validateExpr("Like \"[abc*\"", "<ELikeOp>{<EThisValue>{<THIS_COL>} Like \"[abc*\"((?!))}",
-                 "<THIS_COL> Like \"[abc*\"");
+    validateExpr("Like  \"[abc*\"", "<ELikeOp>{<EThisValue>{<THIS_COL>} Like \"[abc*\"((?!))}",
+                 "Like \"[abc*\"");
     assertFalse(evalCondition("Like \"[abc*\"", "afcd"));
     assertFalse(evalCondition("Like \"[abc*\"", "fcd"));
     assertTrue(evalCondition("Not Like \"[abc*\"", "fcd"));
@@ -500,17 +503,17 @@ public class ExpressionatorTest extends TestCase
 
   private static void validateExpr(String exprStr, String debugStr,
                                    String cleanStr) {
+    TestContext ctx = new TestContext();
     Expression expr = Expressionator.parse(
-        Expressionator.Type.FIELD_VALIDATOR, exprStr, null,
-        new TestContext());
-    String foundDebugStr = expr.toDebugString();
+        Expressionator.Type.FIELD_VALIDATOR, exprStr, null, ctx);
+    String foundDebugStr = expr.toDebugString(ctx);
     if(foundDebugStr.startsWith("<EImplicitCompOp>")) {
       assertEquals("<EImplicitCompOp>{<EThisValue>{<THIS_COL>} = " +
                    debugStr + "}", foundDebugStr);
     } else {
       assertEquals(debugStr, foundDebugStr);
     }
-    assertEquals(cleanStr, expr.toString());
+    assertEquals(cleanStr, expr.toCleanString(ctx));
     assertEquals(exprStr, expr.toRawString());
   }
 
@@ -588,6 +591,15 @@ public class ExpressionatorTest extends TestCase
       return sdf;
     }
 
+    public Calendar getCalendar() {
+      return createDateFormat(getTemporalConfig().getDefaultDateTimeFormat())
+        .getCalendar();
+    }
+
+    public NumericConfig getNumericConfig() {
+      return NumericConfig.US_NUMERIC_CONFIG;
+    }
+
     public FunctionLookup getFunctionLookup() {
       return DefaultFunctions.LOOKUP;
     }