]> source.dussan.org Git - jackcess.git/commitdiff
Add ColumnFormatter utility which can apply Column Format property for display of...
authorJames Ahlborn <jtahlborn@yahoo.com>
Mon, 11 Feb 2019 22:26:03 +0000 (22:26 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Mon, 11 Feb 2019 22:26:03 +0000 (22:26 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1291 f203690c-595d-4dc9-a70b-905162fa7fd2

12 files changed:
src/changes/changes.xml
src/main/java/com/healthmarketscience/jackcess/expr/package-info.java
src/main/java/com/healthmarketscience/jackcess/impl/ColEvalContext.java
src/main/java/com/healthmarketscience/jackcess/impl/ColValidatorEvalContext.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java
src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java
src/main/java/com/healthmarketscience/jackcess/util/ColumnFormatter.java [new file with mode: 0644]
src/main/java/com/healthmarketscience/jackcess/util/ColumnValidator.java
src/test/java/com/healthmarketscience/jackcess/TestUtil.java
src/test/java/com/healthmarketscience/jackcess/impl/CodecHandlerTest.java
src/test/java/com/healthmarketscience/jackcess/util/ColumnFormatterTest.java [new file with mode: 0644]

index 972ab9dfd3e7334e4237d3eef4fee686ef450572..c7a37e02ad17718f5c76af05a7d423a6a9a12faf 100644 (file)
@@ -4,6 +4,12 @@
     <author email="javajedi@users.sf.net">Tim McCune</author>
   </properties>
   <body>
+    <release version="3.0.1" date="TBD">
+      <action dev="jahlborn" type="update">
+        Add ColumnFormatter utility which can apply Column "Format" property
+        for display of column values.
+      </action>
+    </release>
     <release version="3.0.0" date="2019-02-08" description="Update to Java 8">
       <action dev="jahlborn" type="update">
         Jackcess now requires a Java 8+ runtime.  As part of this update, all
index d5d380ab067706783ef705d9b8882f86df483007..d6f137e74817c1f8d100e4a19afefbbd34374950 100644 (file)
@@ -215,7 +215,7 @@ limitations under the License.
  *
  * <table border="1" width="25%" cellpadding="3" cellspacing="0">
  * <tr class="TableHeadingColor" align="left"><th>Function</th><th>Supported</th></tr>
- * <tr class="TableRowColor"><td>Format[$]</td><td>Partial</td></tr>
+ * <tr class="TableRowColor"><td>Format[$]</td><td>Y</td></tr>
  * <tr class="TableRowColor"><td>InStr</td><td>Y</td></tr>
  * <tr class="TableRowColor"><td>InStrRev</td><td>Y</td></tr>
  * <tr class="TableRowColor"><td>LCase[$]</td><td>Y</td></tr>
index e62f0dbd97cbeb801c5690283fd12bd0c6af74c2..6ec3f49f04dea6f6dddcfe37c11b9f6a0784c652 100644 (file)
@@ -16,6 +16,8 @@ limitations under the License.
 
 package com.healthmarketscience.jackcess.impl;
 
+import com.healthmarketscience.jackcess.expr.Value;
+
 
 
 
@@ -40,4 +42,8 @@ public abstract class ColEvalContext extends BaseEvalContext
   protected String withErrorContext(String msg) {
     return _col.withErrorContext(msg);
   }
+
+  protected Value toValue(Object val) {
+    return toValue(val, _col.getType());
+  }
 }
index 794def10688ed12f1366682c2546a739b742c47b..27ca9ddc08793fd6591fbe296a9a1009c4085e8b 100644 (file)
@@ -65,7 +65,7 @@ public class ColValidatorEvalContext extends ColEvalContext
 
   @Override
   public Value getThisColumnValue() {
-    return toValue(_val, getCol().getType());
+    return toValue(_val);
   }
 
   @Override
index 5a871e491cd89ec31759d8a0998d8dd157fe9281..6182dd45f6cc47f91b92e9cd829d7d26966c254e 100644 (file)
@@ -29,6 +29,7 @@ import com.healthmarketscience.jackcess.expr.EvalContext;
 import com.healthmarketscience.jackcess.expr.EvalException;
 import com.healthmarketscience.jackcess.expr.Function;
 import com.healthmarketscience.jackcess.expr.FunctionLookup;
+import com.healthmarketscience.jackcess.expr.LocaleContext;
 import com.healthmarketscience.jackcess.expr.NumericConfig;
 import com.healthmarketscience.jackcess.expr.TemporalConfig;
 import com.healthmarketscience.jackcess.expr.Value;
@@ -473,11 +474,11 @@ public class DefaultFunctions
     }
   });
 
-  private static boolean stringIsNumeric(EvalContext ctx, Value param) {
+  private static boolean stringIsNumeric(LocaleContext ctx, Value param) {
     return (maybeGetAsBigDecimal(ctx, param) != null);
   }
 
-  static BigDecimal maybeGetAsBigDecimal(EvalContext ctx, Value param) {
+  static BigDecimal maybeGetAsBigDecimal(LocaleContext ctx, Value param) {
     try {
       return param.getAsBigDecimal(ctx);
     } catch(EvalException ignored) {
@@ -490,7 +491,7 @@ public class DefaultFunctions
     return (maybeGetAsDateTimeValue(ctx, param) != null);
   }
 
-  static Value maybeGetAsDateTimeValue(EvalContext ctx, Value param) {
+  static Value maybeGetAsDateTimeValue(LocaleContext ctx, Value param) {
     try {
       // see if we can coerce to date/time
       return param.getAsDateTimeValue(ctx);
index 39f7050d1ca14354efb370dfd8dad9157b7032e6..544c08b2871b7dd69530c411044b2ac539ef68bf 100644 (file)
@@ -38,6 +38,7 @@ import com.healthmarketscience.jackcess.expr.ParseException;
 import com.healthmarketscience.jackcess.expr.TemporalConfig;
 import com.healthmarketscience.jackcess.expr.Value;
 import com.healthmarketscience.jackcess.impl.ColumnImpl;
+import org.apache.commons.lang3.StringUtils;
 
 
 /**
@@ -89,7 +90,7 @@ class ExpressionTokenizer
       exprStr = exprStr.trim();
     }
 
-    if((exprStr == null) || (exprStr.length() == 0)) {
+    if(StringUtils.isEmpty(exprStr)) {
       return null;
     }
 
index 14ad3fa67ee6472c9af3732197e27a409f4a0067..68fce3c68aa87d42067d5260bc93c303852487bd 100644 (file)
@@ -41,6 +41,7 @@ import com.healthmarketscience.jackcess.expr.EvalException;
 import com.healthmarketscience.jackcess.expr.NumericConfig;
 import com.healthmarketscience.jackcess.expr.TemporalConfig;
 import com.healthmarketscience.jackcess.expr.Value;
+import org.apache.commons.lang3.StringUtils;
 import static com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.ExprBuf;
 
 /**
@@ -114,40 +115,41 @@ public class FormatUtil
   private static final Map<String,Fmt> PREDEF_FMTS = new HashMap<String,Fmt>();
 
   static {
-    PREDEF_FMTS.put("General Date", args -> ValueSupport.toValue(
+    putPredefFormat("General Date", args -> ValueSupport.toValue(
                         args.coerceToDateTimeValue().getAsString()));
-    PREDEF_FMTS.put("Long Date",
+    putPredefFormat("Long Date",
                     new PredefDateFmt(TemporalConfig.Type.LONG_DATE));
-    PREDEF_FMTS.put("Medium Date",
+    putPredefFormat("Medium Date",
                     new PredefDateFmt(TemporalConfig.Type.MEDIUM_DATE));
-    PREDEF_FMTS.put("Short Date",
+    putPredefFormat("Short Date",
                     new PredefDateFmt(TemporalConfig.Type.SHORT_DATE));
-    PREDEF_FMTS.put("Long Time",
+    putPredefFormat("Long Time",
                     new PredefDateFmt(TemporalConfig.Type.LONG_TIME));
-    PREDEF_FMTS.put("Medium Time",
+    putPredefFormat("Medium Time",
                     new PredefDateFmt(TemporalConfig.Type.MEDIUM_TIME));
-    PREDEF_FMTS.put("Short Time",
+    putPredefFormat("Short Time",
                     new PredefDateFmt(TemporalConfig.Type.SHORT_TIME));
 
-    PREDEF_FMTS.put("General Number", args -> ValueSupport.toValue(
+    putPredefFormat("General Number", args -> ValueSupport.toValue(
                         args.coerceToNumberValue().getAsString()));
-    PREDEF_FMTS.put("Currency",
+    putPredefFormat("Currency",
                     new PredefNumberFmt(NumericConfig.Type.CURRENCY));
-    PREDEF_FMTS.put("Euro", new PredefNumberFmt(NumericConfig.Type.EURO));
-    PREDEF_FMTS.put("Fixed",
+    putPredefFormat("Euro", new PredefNumberFmt(NumericConfig.Type.EURO));
+    putPredefFormat("Fixed",
                     new PredefNumberFmt(NumericConfig.Type.FIXED));
-    PREDEF_FMTS.put("Standard",
+    putPredefFormat("Standard",
                     new PredefNumberFmt(NumericConfig.Type.STANDARD));
-    PREDEF_FMTS.put("Percent",
+    putPredefFormat("Percent",
                     new PredefNumberFmt(NumericConfig.Type.PERCENT));
-    PREDEF_FMTS.put("Scientific", new ScientificPredefNumberFmt());
+    putPredefFormat("Scientific", new ScientificPredefNumberFmt());
 
-    PREDEF_FMTS.put("True/False", new PredefBoolFmt("True", "False"));
-    PREDEF_FMTS.put("Yes/No", new PredefBoolFmt("Yes", "No"));
-    PREDEF_FMTS.put("On/Off", new PredefBoolFmt("On", "Off"));
+    putPredefFormat("True/False", new PredefBoolFmt("True", "False"));
+    putPredefFormat("Yes/No", new PredefBoolFmt("Yes", "No"));
+    putPredefFormat("On/Off", new PredefBoolFmt("On", "Off"));
   }
 
   private static final Fmt NULL_FMT = args -> ValueSupport.EMPTY_STR_VAL;
+  private static final Fmt DUMMY_FMT = args -> args.getNonNullExpr();
 
   private static final char QUOTE_CHAR = '"';
   private static final char ESCAPE_CHAR = '\\';
@@ -282,6 +284,15 @@ public class FormatUtil
       _firstWeekType = firstWeekType;
     }
 
+    public Args setExpr(Value expr) {
+      _expr = expr;
+      return this;
+    }
+
+    public Value getNonNullExpr() {
+      return (_expr.isNull() ? ValueSupport.EMPTY_STR_VAL : _expr);
+    }
+
     public boolean isNullOrEmptyString() {
       return(_expr.isNull() ||
              // only a string value could ever be an empty string
@@ -385,37 +396,67 @@ public class FormatUtil
     public String getAsString() {
       return _expr.getAsString(_ctx);
     }
+
+    public Value format(Fmt fmt) {
+      Value origExpr = _expr;
+      try {
+        return fmt.format(this);
+      } catch(EvalException ee) {
+        // values which cannot be formatted as the target type are just
+        // returned "as is"
+        return origExpr;
+      }
+    }
   }
 
   private FormatUtil() {}
 
+  /**
+   * Utility for leveraging format support outside of expression evaluation.
+   */
+  public static class StandaloneFormatter
+  {
+    private final Fmt _fmt;
+    private final Args _args;
+
+    private StandaloneFormatter(Fmt fmt, Args args) {
+      _fmt = fmt;
+      _args = args;
+    }
+
+    public Value format(Value expr) {
+      return _args.setExpr(expr).format(_fmt);
+    }
+  }
 
   public static Value format(EvalContext ctx, Value expr, String fmtStr,
                              int firstDay, int firstWeekType) {
+    Args args = new Args(ctx, expr, firstDay, firstWeekType);
+    return args.format(createFormat(args, fmtStr));
+  }
 
-    try {
-      Args args = new Args(ctx, expr, firstDay, firstWeekType);
+  public static StandaloneFormatter createStandaloneFormatter(
+      EvalContext ctx, String fmtStr, int firstDay, int firstWeekType) {
+    Args args = new Args(ctx, null, firstDay, firstWeekType);
+    Fmt fmt = createFormat(args, fmtStr);
+    return new StandaloneFormatter(fmt, args);
+  }
 
-      Fmt predefFmt = PREDEF_FMTS.get(fmtStr);
-      if(predefFmt != null) {
-        if(args.isNullOrEmptyString()) {
-          // predefined formats return empty string for null
-          return ValueSupport.EMPTY_STR_VAL;
-        }
-        return predefFmt.format(args);
-      }
+  private static Fmt createFormat(Args args, String fmtStr) {
+    Fmt predefFmt = PREDEF_FMTS.get(fmtStr);
+    if(predefFmt != null) {
+      return predefFmt;
+    }
 
-      // TODO implement caching for custom formats?  put into Bindings.  use
-      // special "cache" prefix to know which caches to clear when evalconfig
-      // is altered (could also cache other Format* functions)
+    if(StringUtils.isEmpty(fmtStr)) {
+      return DUMMY_FMT;
+    }
 
-      return parseCustomFormat(fmtStr, args).format(args);
+    // TODO implement caching for custom formats?  put into Bindings.  use
+    // special "cache" prefix to know which caches to clear when evalconfig
+    // is altered (could also cache other Format* functions)
 
-    } catch(EvalException ee) {
-      // values which cannot be formatted as the target type are just
-      // returned "as is"
-      return expr;
-    }
+    return parseCustomFormat(fmtStr, args);
   }
 
   private static Fmt parseCustomFormat(String fmtStr, Args args) {
@@ -1178,6 +1219,14 @@ public class FormatUtil
     }
   }
 
+  private static void putPredefFormat(String key, Fmt fmt) {
+    // predefined formats return empty string for null
+    Fmt wrapFmt = args -> (args.isNullOrEmptyString() ?
+                           ValueSupport.EMPTY_STR_VAL :
+                           fmt.format(args));
+    PREDEF_FMTS.put(key, wrapFmt);
+  }
+
   private static final class PredefDateFmt implements Fmt
   {
     private final TemporalConfig.Type _type;
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/ColumnFormatter.java b/src/main/java/com/healthmarketscience/jackcess/util/ColumnFormatter.java
new file mode 100644 (file)
index 0000000..0133db8
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+Copyright (c) 2019 James Ahlborn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package com.healthmarketscience.jackcess.util;
+
+import java.io.IOException;
+import java.util.Map;
+
+import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.PropertyMap;
+import com.healthmarketscience.jackcess.expr.EvalConfig;
+import com.healthmarketscience.jackcess.expr.EvalException;
+import com.healthmarketscience.jackcess.impl.ColEvalContext;
+import com.healthmarketscience.jackcess.impl.ColumnImpl;
+import com.healthmarketscience.jackcess.impl.expr.FormatUtil;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Utility for applying Column formatting to column values for display.  This
+ * utility loads the "Format" property from the given column and builds an
+ * appropriate formatter (essentially leveraging the internals of the
+ * expression execution engine's support for the "Format()" function).  Since
+ * formats leverage the expression evaluation engine, the underlying
+ * Database's {@link EvalConfig} can be used to alter how this utility formats
+ * values.  Note, formatted values may be suitable for <i>display only</i>
+ * (i.e. a formatted value may not be accepted as an input value to a Table
+ * add/update method).
+ *
+ * @author James Ahlborn
+ * @usage _general_class_
+ */
+public class ColumnFormatter
+{
+  private final ColumnImpl _col;
+  private final FormatEvalContext _ctx;
+  private String _fmtStr;
+  private FormatUtil.StandaloneFormatter _fmt;
+
+  public ColumnFormatter(Column col) throws IOException {
+    _col = (ColumnImpl)col;
+    _ctx = new FormatEvalContext(_col);
+    reload();
+  }
+
+  /**
+   * Returns the currently loaded "Format" property for this formatter, may be
+   * {@code null}.
+   */
+  public String getFormatString() {
+    return _fmtStr;
+  }
+
+  /**
+   * Sets the given format string as the "Format" property for the underlying
+   * Column and reloads this formatter.
+   *
+   * @param fmtStr the new format string.  may be {@code null}, in which case
+   *               the "Format" property is removed from the underlying Column
+   */
+  public void setFormatString(String fmtStr) throws IOException {
+    PropertyMap props = _col.getProperties();
+    if(!StringUtils.isEmpty(fmtStr)) {
+      props.put(PropertyMap.FORMAT_PROP, fmtStr);
+    } else {
+      props.remove(PropertyMap.FORMAT_PROP);
+    }
+    props.save();
+    reload();
+  }
+
+  /**
+   * Formats the given value according to the format currently defined for the
+   * underlying Column.
+   *
+   * @param val a valid input value for the DataType of the underlying Column
+   *            (i.e. a value which could be passed to a Table add/update
+   *            method for this Column).  may be {@code null}
+   *
+   * @return the formatted result, always non-{@code null}
+   */
+  public String format(Object val) {
+    return _ctx.format(val);
+  }
+
+  /**
+   * Convenience method for retrieving the appropriate Column value from the
+   * given row array and formatting it.
+   *
+   * @return the formatted result, always non-{@code null}
+   */
+  public String getRowValue(Object[] rowArray) {
+    return format(_col.getRowValue(rowArray));
+  }
+
+  /**
+   * Convenience method for retrieving the appropriate Column value from the
+   * given row map and formatting it.
+   *
+   * @return the formatted result, always non-{@code null}
+   */
+  public String getRowValue(Map<String,?> rowMap) {
+    return format(_col.getRowValue(rowMap));
+  }
+
+  /**
+   * If the properties for the underlying Column have been modified directly
+   * (or the EvalConfig for the underlying Database has been modified), this
+   * method may be called to reload the format for the underlying Column.
+   */
+  public final void reload() throws IOException {
+    _fmt = null;
+    _fmtStr = null;
+
+    _fmtStr = (String)_col.getProperties().getValue(PropertyMap.FORMAT_PROP);
+    _fmt = FormatUtil.createStandaloneFormatter(_ctx, _fmtStr, 1, 1);
+  }
+
+  /**
+   * Utility class to provide an EvalContext for the expression evaluation
+   * engine format support.
+   */
+  private class FormatEvalContext extends ColEvalContext
+  {
+    private FormatEvalContext(ColumnImpl col) {
+      super(col);
+    }
+
+    public String format(Object val) {
+      try {
+        return _fmt.format(toValue(val)).getAsString(this);
+      } catch(EvalException ee) {
+        // invalid values for a given format result in returning the value as is
+        return val.toString();
+      }
+    }
+  }
+}
index 101ff1eb784036550c37c0f09d405a98410ff19b..ffe2e71cedbe38d68d24677e6d8ddd94323c3025 100644 (file)
@@ -27,7 +27,7 @@ import com.healthmarketscience.jackcess.Column;
  *
  * @author James Ahlborn
  */
-public interface ColumnValidator 
+public interface ColumnValidator
 {
   /**
    * Validates and/or manipulates the given potential new value for the given
index 83b2d7df2425ff6559f6c53f7486a04460f8046f..fef09ccdacd2747bc9574b9da9af2fb6efe66ba5 100644 (file)
@@ -117,18 +117,24 @@ public class TestUtil
   public static Database create(FileFormat fileFormat, boolean keep)
     throws Exception
   {
-    return create(fileFormat, keep, false);
+    return create(fileFormat, keep, true);
   }
 
   public static Database createMem(FileFormat fileFormat) throws Exception {
-    return create(fileFormat, false, true);
+    return create(fileFormat);
+  }
+
+  public static Database createFile(FileFormat fileFormat) throws Exception {
+    return create(fileFormat, false, false);
   }
 
   private static Database create(FileFormat fileFormat, boolean keep,
                                  boolean inMem)
     throws Exception
   {
-    FileChannel channel = (inMem ? MemFileChannel.newChannel() : null);
+
+    FileChannel channel = ((inMem && !keep) ? MemFileChannel.newChannel() :
+                           null);
 
     if (fileFormat == FileFormat.GENERIC_JET4) {
       // while we don't support creating GENERIC_JET4 as a jackcess feature,
index d6c788c463857786bd0e1d3e167dc72335fd561d..51e9f3c14d4502d971565583db7e40cbb90ed41a 100644 (file)
@@ -72,7 +72,7 @@ public class CodecHandlerTest extends TestCase
   private static void doTestCodecHandler(boolean simple) throws Exception
   {
     for(Database.FileFormat ff : SUPPORTED_FILEFORMATS) {
-      Database db = TestUtil.create(ff);
+      Database db = TestUtil.createFile(ff);
       int pageSize = ((DatabaseImpl)db).getFormat().PAGE_SIZE;
       File dbFile = db.getFile();
       db.close();
@@ -163,7 +163,7 @@ public class CodecHandlerTest extends TestCase
     assertEquals(valuePrefix.length() + 100, value.length());
   }
 
-  private static void encodeFile(File dbFile, int pageSize, boolean simple) 
+  private static void encodeFile(File dbFile, int pageSize, boolean simple)
     throws Exception
   {
     long dbLen = dbFile.length();
@@ -174,7 +174,7 @@ public class CodecHandlerTest extends TestCase
 
       bb.clear();
       fileChannel.read(bb, offset);
-      
+
       int pageNumber = (int)(offset / pageSize);
       if(simple) {
         simpleEncode(bb.array(), bb.array(), pageNumber, 0, pageSize);
@@ -221,12 +221,12 @@ public class CodecHandlerTest extends TestCase
     }
   }
 
-  private static final class SimpleCodecHandler implements CodecHandler 
+  private static final class SimpleCodecHandler implements CodecHandler
   {
     private final TempBufferHolder _bufH = TempBufferHolder.newHolder(
         TempBufferHolder.Type.HARD, true);
     private final PageChannel _channel;
-    
+
     private SimpleCodecHandler(PageChannel channel) {
       _channel = channel;
     }
@@ -238,37 +238,37 @@ public class CodecHandlerTest extends TestCase
     public boolean canDecodeInline() {
       return true;
     }
-    
+
     public void decodePage(ByteBuffer inPage, ByteBuffer outPage,
-                           int pageNumber) 
-      throws IOException 
+                           int pageNumber)
+      throws IOException
     {
       byte[] arr = inPage.array();
       simpleDecode(arr, arr, pageNumber);
     }
 
     public ByteBuffer encodePage(ByteBuffer page, int pageNumber,
-                                 int pageOffset) 
+                                 int pageOffset)
       throws IOException
     {
       ByteBuffer bb = _bufH.getPageBuffer(_channel);
       bb.clear();
-      simpleEncode(page.array(), bb.array(), pageNumber, pageOffset, 
+      simpleEncode(page.array(), bb.array(), pageNumber, pageOffset,
                    page.limit());
       return bb;
     }
   }
 
-  private static final class FullCodecHandler implements CodecHandler 
+  private static final class FullCodecHandler implements CodecHandler
   {
     private final TempBufferHolder _bufH = TempBufferHolder.newHolder(
         TempBufferHolder.Type.HARD, true);
     private final PageChannel _channel;
-    
+
     private FullCodecHandler(PageChannel channel) {
       _channel = channel;
     }
-    
+
     public boolean canEncodePartialPage() {
       return false;
     }
@@ -276,17 +276,17 @@ public class CodecHandlerTest extends TestCase
     public boolean canDecodeInline() {
       return true;
     }
-    
-    public void decodePage(ByteBuffer inPage, ByteBuffer outPage, 
-                           int pageNumber) 
-      throws IOException 
+
+    public void decodePage(ByteBuffer inPage, ByteBuffer outPage,
+                           int pageNumber)
+      throws IOException
     {
       byte[] arr = inPage.array();
       fullDecode(arr, arr, pageNumber);
     }
 
-    public ByteBuffer encodePage(ByteBuffer page, int pageNumber, 
-                                 int pageOffset) 
+    public ByteBuffer encodePage(ByteBuffer page, int pageNumber,
+                                 int pageOffset)
       throws IOException
     {
       assertEquals(0, pageOffset);
diff --git a/src/test/java/com/healthmarketscience/jackcess/util/ColumnFormatterTest.java b/src/test/java/com/healthmarketscience/jackcess/util/ColumnFormatterTest.java
new file mode 100644 (file)
index 0000000..d9aecb7
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+Copyright (c) 2019 James Ahlborn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package com.healthmarketscience.jackcess.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.ColumnBuilder;
+import com.healthmarketscience.jackcess.CursorBuilder;
+import com.healthmarketscience.jackcess.DataType;
+import com.healthmarketscience.jackcess.Database;
+import com.healthmarketscience.jackcess.Database.FileFormat;
+import com.healthmarketscience.jackcess.IndexCursor;
+import com.healthmarketscience.jackcess.PropertyMap;
+import com.healthmarketscience.jackcess.Row;
+import com.healthmarketscience.jackcess.Table;
+import com.healthmarketscience.jackcess.TableBuilder;
+import junit.framework.TestCase;
+import static com.healthmarketscience.jackcess.TestUtil.*;
+import static com.healthmarketscience.jackcess.impl.JetFormatTest.*;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class ColumnFormatterTest extends TestCase
+{
+
+  public void testFormat() throws Exception {
+
+    for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
+      Database db = create(fileFormat);
+      db.setEvaluateExpressions(true);
+
+      Table t = new TableBuilder("test")
+        .addColumn(new ColumnBuilder("id", DataType.LONG).setAutoNumber(true))
+        .addColumn(new ColumnBuilder("data1", DataType.TEXT)
+                   .putProperty(PropertyMap.FORMAT_PROP,
+                                ">@@\\x\\x"))
+        .addColumn(new ColumnBuilder("data2", DataType.LONG)
+                   .putProperty(PropertyMap.FORMAT_PROP,
+                                "#.#E+0"))
+        .addColumn(new ColumnBuilder("data3", DataType.MONEY)
+                   .putProperty(PropertyMap.FORMAT_PROP,
+                                "Currency"))
+        .toTable(db);
+
+      ColumnFormatter d1Fmt = new ColumnFormatter(t.getColumn("data1"));
+      ColumnFormatter d2Fmt = new ColumnFormatter(t.getColumn("data2"));
+      ColumnFormatter d3Fmt = new ColumnFormatter(t.getColumn("data3"));
+
+      t.addRow(Column.AUTO_NUMBER, "foobar", 37, "0.03");
+      t.addRow(Column.AUTO_NUMBER, "37", 4500, 4500);
+      t.addRow(Column.AUTO_NUMBER, "foobarbaz", -37, "-37.13");
+      t.addRow(Column.AUTO_NUMBER, null, null, null);
+
+      List<String> found = new ArrayList<>();
+      for(Row r : t) {
+        found.add(d1Fmt.getRowValue(r));
+        found.add(d2Fmt.getRowValue(r));
+        found.add(d3Fmt.getRowValue(r));
+      }
+
+      assertEquals(Arrays.asList(
+                       "FOxxOBAR", "3.7E+1", "$0.03",
+                       "37xx", "4.5E+3", "$4,500.00",
+                       "FOxxOBARBAZ", "-3.7E+1", "($37.13)",
+                       "", "", ""),
+                   found);
+
+      d1Fmt.setFormatString("Scientific");
+      d2Fmt.setFormatString(null);
+      d3Fmt.setFormatString("General Date");
+
+      assertEquals("Scientific", t.getColumn("data1").getProperties()
+                   .getValue(PropertyMap.FORMAT_PROP));
+      assertEquals("General Date", t.getColumn("data3").getProperties()
+                   .getValue(PropertyMap.FORMAT_PROP));
+
+      found = new ArrayList<>();
+      for(Row r : t) {
+        found.add(d1Fmt.getRowValue(r));
+        found.add(d2Fmt.getRowValue(r));
+        found.add(d3Fmt.getRowValue(r));
+      }
+
+      assertEquals(Arrays.asList(
+                       "foobar", "37", "12:43:12 AM",
+                       "3.70E+1", "4500", "4/26/1912",
+                       "foobarbaz", "-37", "11/23/1899 3:07:12 AM",
+                       "", "", ""),
+                   found);
+
+      db.close();
+    }
+  }
+}