git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1291 f203690c-595d-4dc9-a70b-905162fa7fd2tags/jackcess-3.0.1
<author email="javajedi@users.sf.net">Tim McCune</author> | <author email="javajedi@users.sf.net">Tim McCune</author> | ||||
</properties> | </properties> | ||||
<body> | <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"> | <release version="3.0.0" date="2019-02-08" description="Update to Java 8"> | ||||
<action dev="jahlborn" type="update"> | <action dev="jahlborn" type="update"> | ||||
Jackcess now requires a Java 8+ runtime. As part of this update, all | Jackcess now requires a Java 8+ runtime. As part of this update, all |
* | * | ||||
* <table border="1" width="25%" cellpadding="3" cellspacing="0"> | * <table border="1" width="25%" cellpadding="3" cellspacing="0"> | ||||
* <tr class="TableHeadingColor" align="left"><th>Function</th><th>Supported</th></tr> | * <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>InStr</td><td>Y</td></tr> | ||||
* <tr class="TableRowColor"><td>InStrRev</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> | * <tr class="TableRowColor"><td>LCase[$]</td><td>Y</td></tr> |
package com.healthmarketscience.jackcess.impl; | package com.healthmarketscience.jackcess.impl; | ||||
import com.healthmarketscience.jackcess.expr.Value; | |||||
protected String withErrorContext(String msg) { | protected String withErrorContext(String msg) { | ||||
return _col.withErrorContext(msg); | return _col.withErrorContext(msg); | ||||
} | } | ||||
protected Value toValue(Object val) { | |||||
return toValue(val, _col.getType()); | |||||
} | |||||
} | } |
@Override | @Override | ||||
public Value getThisColumnValue() { | public Value getThisColumnValue() { | ||||
return toValue(_val, getCol().getType()); | |||||
return toValue(_val); | |||||
} | } | ||||
@Override | @Override |
import com.healthmarketscience.jackcess.expr.EvalException; | import com.healthmarketscience.jackcess.expr.EvalException; | ||||
import com.healthmarketscience.jackcess.expr.Function; | import com.healthmarketscience.jackcess.expr.Function; | ||||
import com.healthmarketscience.jackcess.expr.FunctionLookup; | import com.healthmarketscience.jackcess.expr.FunctionLookup; | ||||
import com.healthmarketscience.jackcess.expr.LocaleContext; | |||||
import com.healthmarketscience.jackcess.expr.NumericConfig; | import com.healthmarketscience.jackcess.expr.NumericConfig; | ||||
import com.healthmarketscience.jackcess.expr.TemporalConfig; | import com.healthmarketscience.jackcess.expr.TemporalConfig; | ||||
import com.healthmarketscience.jackcess.expr.Value; | import com.healthmarketscience.jackcess.expr.Value; | ||||
} | } | ||||
}); | }); | ||||
private static boolean stringIsNumeric(EvalContext ctx, Value param) { | |||||
private static boolean stringIsNumeric(LocaleContext ctx, Value param) { | |||||
return (maybeGetAsBigDecimal(ctx, param) != null); | return (maybeGetAsBigDecimal(ctx, param) != null); | ||||
} | } | ||||
static BigDecimal maybeGetAsBigDecimal(EvalContext ctx, Value param) { | |||||
static BigDecimal maybeGetAsBigDecimal(LocaleContext ctx, Value param) { | |||||
try { | try { | ||||
return param.getAsBigDecimal(ctx); | return param.getAsBigDecimal(ctx); | ||||
} catch(EvalException ignored) { | } catch(EvalException ignored) { | ||||
return (maybeGetAsDateTimeValue(ctx, param) != null); | return (maybeGetAsDateTimeValue(ctx, param) != null); | ||||
} | } | ||||
static Value maybeGetAsDateTimeValue(EvalContext ctx, Value param) { | |||||
static Value maybeGetAsDateTimeValue(LocaleContext ctx, Value param) { | |||||
try { | try { | ||||
// see if we can coerce to date/time | // see if we can coerce to date/time | ||||
return param.getAsDateTimeValue(ctx); | return param.getAsDateTimeValue(ctx); |
import com.healthmarketscience.jackcess.expr.TemporalConfig; | import com.healthmarketscience.jackcess.expr.TemporalConfig; | ||||
import com.healthmarketscience.jackcess.expr.Value; | import com.healthmarketscience.jackcess.expr.Value; | ||||
import com.healthmarketscience.jackcess.impl.ColumnImpl; | import com.healthmarketscience.jackcess.impl.ColumnImpl; | ||||
import org.apache.commons.lang3.StringUtils; | |||||
/** | /** | ||||
exprStr = exprStr.trim(); | exprStr = exprStr.trim(); | ||||
} | } | ||||
if((exprStr == null) || (exprStr.length() == 0)) { | |||||
if(StringUtils.isEmpty(exprStr)) { | |||||
return null; | return null; | ||||
} | } | ||||
import com.healthmarketscience.jackcess.expr.NumericConfig; | import com.healthmarketscience.jackcess.expr.NumericConfig; | ||||
import com.healthmarketscience.jackcess.expr.TemporalConfig; | import com.healthmarketscience.jackcess.expr.TemporalConfig; | ||||
import com.healthmarketscience.jackcess.expr.Value; | import com.healthmarketscience.jackcess.expr.Value; | ||||
import org.apache.commons.lang3.StringUtils; | |||||
import static com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.ExprBuf; | import static com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.ExprBuf; | ||||
/** | /** | ||||
private static final Map<String,Fmt> PREDEF_FMTS = new HashMap<String,Fmt>(); | private static final Map<String,Fmt> PREDEF_FMTS = new HashMap<String,Fmt>(); | ||||
static { | static { | ||||
PREDEF_FMTS.put("General Date", args -> ValueSupport.toValue( | |||||
putPredefFormat("General Date", args -> ValueSupport.toValue( | |||||
args.coerceToDateTimeValue().getAsString())); | args.coerceToDateTimeValue().getAsString())); | ||||
PREDEF_FMTS.put("Long Date", | |||||
putPredefFormat("Long Date", | |||||
new PredefDateFmt(TemporalConfig.Type.LONG_DATE)); | new PredefDateFmt(TemporalConfig.Type.LONG_DATE)); | ||||
PREDEF_FMTS.put("Medium Date", | |||||
putPredefFormat("Medium Date", | |||||
new PredefDateFmt(TemporalConfig.Type.MEDIUM_DATE)); | new PredefDateFmt(TemporalConfig.Type.MEDIUM_DATE)); | ||||
PREDEF_FMTS.put("Short Date", | |||||
putPredefFormat("Short Date", | |||||
new PredefDateFmt(TemporalConfig.Type.SHORT_DATE)); | new PredefDateFmt(TemporalConfig.Type.SHORT_DATE)); | ||||
PREDEF_FMTS.put("Long Time", | |||||
putPredefFormat("Long Time", | |||||
new PredefDateFmt(TemporalConfig.Type.LONG_TIME)); | new PredefDateFmt(TemporalConfig.Type.LONG_TIME)); | ||||
PREDEF_FMTS.put("Medium Time", | |||||
putPredefFormat("Medium Time", | |||||
new PredefDateFmt(TemporalConfig.Type.MEDIUM_TIME)); | new PredefDateFmt(TemporalConfig.Type.MEDIUM_TIME)); | ||||
PREDEF_FMTS.put("Short Time", | |||||
putPredefFormat("Short Time", | |||||
new PredefDateFmt(TemporalConfig.Type.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())); | args.coerceToNumberValue().getAsString())); | ||||
PREDEF_FMTS.put("Currency", | |||||
putPredefFormat("Currency", | |||||
new PredefNumberFmt(NumericConfig.Type.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)); | new PredefNumberFmt(NumericConfig.Type.FIXED)); | ||||
PREDEF_FMTS.put("Standard", | |||||
putPredefFormat("Standard", | |||||
new PredefNumberFmt(NumericConfig.Type.STANDARD)); | new PredefNumberFmt(NumericConfig.Type.STANDARD)); | ||||
PREDEF_FMTS.put("Percent", | |||||
putPredefFormat("Percent", | |||||
new PredefNumberFmt(NumericConfig.Type.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 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 QUOTE_CHAR = '"'; | ||||
private static final char ESCAPE_CHAR = '\\'; | private static final char ESCAPE_CHAR = '\\'; | ||||
_firstWeekType = firstWeekType; | _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() { | public boolean isNullOrEmptyString() { | ||||
return(_expr.isNull() || | return(_expr.isNull() || | ||||
// only a string value could ever be an empty string | // only a string value could ever be an empty string | ||||
public String getAsString() { | public String getAsString() { | ||||
return _expr.getAsString(_ctx); | 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() {} | 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, | public static Value format(EvalContext ctx, Value expr, String fmtStr, | ||||
int firstDay, int firstWeekType) { | 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) { | private static Fmt parseCustomFormat(String fmtStr, Args args) { | ||||
} | } | ||||
} | } | ||||
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 static final class PredefDateFmt implements Fmt | ||||
{ | { | ||||
private final TemporalConfig.Type _type; | private final TemporalConfig.Type _type; |
/* | |||||
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(); | |||||
} | |||||
} | |||||
} | |||||
} |
* | * | ||||
* @author James Ahlborn | * @author James Ahlborn | ||||
*/ | */ | ||||
public interface ColumnValidator | |||||
public interface ColumnValidator | |||||
{ | { | ||||
/** | /** | ||||
* Validates and/or manipulates the given potential new value for the given | * Validates and/or manipulates the given potential new value for the given |
public static Database create(FileFormat fileFormat, boolean keep) | public static Database create(FileFormat fileFormat, boolean keep) | ||||
throws Exception | throws Exception | ||||
{ | { | ||||
return create(fileFormat, keep, false); | |||||
return create(fileFormat, keep, true); | |||||
} | } | ||||
public static Database createMem(FileFormat fileFormat) throws Exception { | 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, | private static Database create(FileFormat fileFormat, boolean keep, | ||||
boolean inMem) | boolean inMem) | ||||
throws Exception | throws Exception | ||||
{ | { | ||||
FileChannel channel = (inMem ? MemFileChannel.newChannel() : null); | |||||
FileChannel channel = ((inMem && !keep) ? MemFileChannel.newChannel() : | |||||
null); | |||||
if (fileFormat == FileFormat.GENERIC_JET4) { | if (fileFormat == FileFormat.GENERIC_JET4) { | ||||
// while we don't support creating GENERIC_JET4 as a jackcess feature, | // while we don't support creating GENERIC_JET4 as a jackcess feature, |
private static void doTestCodecHandler(boolean simple) throws Exception | private static void doTestCodecHandler(boolean simple) throws Exception | ||||
{ | { | ||||
for(Database.FileFormat ff : SUPPORTED_FILEFORMATS) { | for(Database.FileFormat ff : SUPPORTED_FILEFORMATS) { | ||||
Database db = TestUtil.create(ff); | |||||
Database db = TestUtil.createFile(ff); | |||||
int pageSize = ((DatabaseImpl)db).getFormat().PAGE_SIZE; | int pageSize = ((DatabaseImpl)db).getFormat().PAGE_SIZE; | ||||
File dbFile = db.getFile(); | File dbFile = db.getFile(); | ||||
db.close(); | db.close(); | ||||
assertEquals(valuePrefix.length() + 100, value.length()); | 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 | throws Exception | ||||
{ | { | ||||
long dbLen = dbFile.length(); | long dbLen = dbFile.length(); | ||||
bb.clear(); | bb.clear(); | ||||
fileChannel.read(bb, offset); | fileChannel.read(bb, offset); | ||||
int pageNumber = (int)(offset / pageSize); | int pageNumber = (int)(offset / pageSize); | ||||
if(simple) { | if(simple) { | ||||
simpleEncode(bb.array(), bb.array(), pageNumber, 0, pageSize); | simpleEncode(bb.array(), bb.array(), pageNumber, 0, pageSize); | ||||
} | } | ||||
} | } | ||||
private static final class SimpleCodecHandler implements CodecHandler | |||||
private static final class SimpleCodecHandler implements CodecHandler | |||||
{ | { | ||||
private final TempBufferHolder _bufH = TempBufferHolder.newHolder( | private final TempBufferHolder _bufH = TempBufferHolder.newHolder( | ||||
TempBufferHolder.Type.HARD, true); | TempBufferHolder.Type.HARD, true); | ||||
private final PageChannel _channel; | private final PageChannel _channel; | ||||
private SimpleCodecHandler(PageChannel channel) { | private SimpleCodecHandler(PageChannel channel) { | ||||
_channel = channel; | _channel = channel; | ||||
} | } | ||||
public boolean canDecodeInline() { | public boolean canDecodeInline() { | ||||
return true; | return true; | ||||
} | } | ||||
public void decodePage(ByteBuffer inPage, ByteBuffer outPage, | public void decodePage(ByteBuffer inPage, ByteBuffer outPage, | ||||
int pageNumber) | |||||
throws IOException | |||||
int pageNumber) | |||||
throws IOException | |||||
{ | { | ||||
byte[] arr = inPage.array(); | byte[] arr = inPage.array(); | ||||
simpleDecode(arr, arr, pageNumber); | simpleDecode(arr, arr, pageNumber); | ||||
} | } | ||||
public ByteBuffer encodePage(ByteBuffer page, int pageNumber, | public ByteBuffer encodePage(ByteBuffer page, int pageNumber, | ||||
int pageOffset) | |||||
int pageOffset) | |||||
throws IOException | throws IOException | ||||
{ | { | ||||
ByteBuffer bb = _bufH.getPageBuffer(_channel); | ByteBuffer bb = _bufH.getPageBuffer(_channel); | ||||
bb.clear(); | bb.clear(); | ||||
simpleEncode(page.array(), bb.array(), pageNumber, pageOffset, | |||||
simpleEncode(page.array(), bb.array(), pageNumber, pageOffset, | |||||
page.limit()); | page.limit()); | ||||
return bb; | return bb; | ||||
} | } | ||||
} | } | ||||
private static final class FullCodecHandler implements CodecHandler | |||||
private static final class FullCodecHandler implements CodecHandler | |||||
{ | { | ||||
private final TempBufferHolder _bufH = TempBufferHolder.newHolder( | private final TempBufferHolder _bufH = TempBufferHolder.newHolder( | ||||
TempBufferHolder.Type.HARD, true); | TempBufferHolder.Type.HARD, true); | ||||
private final PageChannel _channel; | private final PageChannel _channel; | ||||
private FullCodecHandler(PageChannel channel) { | private FullCodecHandler(PageChannel channel) { | ||||
_channel = channel; | _channel = channel; | ||||
} | } | ||||
public boolean canEncodePartialPage() { | public boolean canEncodePartialPage() { | ||||
return false; | return false; | ||||
} | } | ||||
public boolean canDecodeInline() { | public boolean canDecodeInline() { | ||||
return true; | 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(); | byte[] arr = inPage.array(); | ||||
fullDecode(arr, arr, pageNumber); | fullDecode(arr, arr, pageNumber); | ||||
} | } | ||||
public ByteBuffer encodePage(ByteBuffer page, int pageNumber, | |||||
int pageOffset) | |||||
public ByteBuffer encodePage(ByteBuffer page, int pageNumber, | |||||
int pageOffset) | |||||
throws IOException | throws IOException | ||||
{ | { | ||||
assertEquals(0, pageOffset); | assertEquals(0, pageOffset); |
/* | |||||
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(); | |||||
} | |||||
} | |||||
} |