diff options
author | James Ahlborn <jtahlborn@yahoo.com> | 2019-02-11 22:26:03 +0000 |
---|---|---|
committer | James Ahlborn <jtahlborn@yahoo.com> | 2019-02-11 22:26:03 +0000 |
commit | 831d8c0b1d0996ba63c9e2ef3b73d6a8982a3ad2 (patch) | |
tree | 863cb3ae6e4f864ba13f1bd7ab59fdc7302318c9 /src/main | |
parent | 4d6305152e060fea9e3aab12ec55bc8941d4d23f (diff) | |
download | jackcess-831d8c0b1d0996ba63c9e2ef3b73d6a8982a3ad2.tar.gz jackcess-831d8c0b1d0996ba63c9e2ef3b73d6a8982a3ad2.zip |
Add ColumnFormatter utility which can apply Column Format property for display of column values
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1291 f203690c-595d-4dc9-a70b-905162fa7fd2
Diffstat (limited to 'src/main')
8 files changed, 250 insertions, 43 deletions
diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/package-info.java b/src/main/java/com/healthmarketscience/jackcess/expr/package-info.java index d5d380a..d6f137e 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/package-info.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/package-info.java @@ -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> diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/ColEvalContext.java b/src/main/java/com/healthmarketscience/jackcess/impl/ColEvalContext.java index e62f0db..6ec3f49 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/ColEvalContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/ColEvalContext.java @@ -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()); + } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/ColValidatorEvalContext.java b/src/main/java/com/healthmarketscience/jackcess/impl/ColValidatorEvalContext.java index 794def1..27ca9dd 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/ColValidatorEvalContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/ColValidatorEvalContext.java @@ -65,7 +65,7 @@ public class ColValidatorEvalContext extends ColEvalContext @Override public Value getThisColumnValue() { - return toValue(_val, getCol().getType()); + return toValue(_val); } @Override diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java index 5a871e4..6182dd4 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java @@ -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); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java index 39f7050..544c08b 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java @@ -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; } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java index 14ad3fa..68fce3c 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java @@ -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 index 0000000..0133db8 --- /dev/null +++ b/src/main/java/com/healthmarketscience/jackcess/util/ColumnFormatter.java @@ -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(); + } + } + } +} diff --git a/src/main/java/com/healthmarketscience/jackcess/util/ColumnValidator.java b/src/main/java/com/healthmarketscience/jackcess/util/ColumnValidator.java index 101ff1e..ffe2e71 100644 --- a/src/main/java/com/healthmarketscience/jackcess/util/ColumnValidator.java +++ b/src/main/java/com/healthmarketscience/jackcess/util/ColumnValidator.java @@ -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 |