git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1240 f203690c-595d-4dc9-a70b-905162fa7fd2tags/jackcess-2.2.1
Many of the financial functions have been originally copied from the Apache | |||||
POI project (Apache Software Foundation) and the UCanAccess Project. They | |||||
have been then modified and adapted so that they are integrated with Jackcess, | |||||
in a consistent manner. The Apache POI and UCanAccess projects are licensed | |||||
under Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0. |
<body> | <body> | ||||
<release version="2.2.1" date="TBD"> | <release version="2.2.1" date="TBD"> | ||||
<action dev="jahlborn" type="update"> | <action dev="jahlborn" type="update"> | ||||
Implement IsNumeric function. Add support for hex/oct integer | |||||
strings. | |||||
Implement the majority of the missing standard functions: | |||||
FormatCurrency, FormatDateTime, FormatNumber, FormatPercent, Val, | |||||
DateAdd, DateDiff, DatePart, MonthName, WeekdayName, DDB, IPmt, PPmt, | |||||
Rate, SLN, SYD, Format, Replace, StrConv. (Note that the internal API | |||||
for the expr package has changed in an incompatible way. However, | |||||
since the API is still experimental, this was deemed acceptable for a | |||||
minor version.). Note that many of the financial functions were | |||||
copied and adpated from the Apache POI and UCanAccess projects (which | |||||
are both under the Apache License 2.0). | |||||
</action> | |||||
<action dev="jahlborn" type="update"> | |||||
Implement more type coercion methods for expressions. Add support for | |||||
hex/oct integer strings. Add support for number strings with commas. | |||||
Add support for coercing numeric String to a date/time value. Add | |||||
support for date/time values with implicit (current) year. | |||||
</action> | |||||
<action dev="jahlborn" type="fix" system="SourceForge2" issue="150"> | |||||
Ignore column validators for read-only dbs. This will avoid | |||||
irrelevant failures when reading databases which have invalid column | |||||
properties. | |||||
</action> | </action> | ||||
</release> | </release> | ||||
<release version="2.2.0" date="2018-09-08" | <release version="2.2.0" date="2018-09-08" |
* | * | ||||
* <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></td></tr> | |||||
* <tr class="TableRowColor"><td>Format[$]</td><td>Partial</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> |
} | } | ||||
void initColumnValidator() throws IOException { | void initColumnValidator() throws IOException { | ||||
if(getDatabase().isReadOnly()) { | |||||
// validators are irrelevant for read-only databases | |||||
return; | |||||
} | |||||
// first initialize any "external" (user-defined) validator | // first initialize any "external" (user-defined) validator | ||||
setColumnValidator(null); | setColumnValidator(null); | ||||
private final File _file; | private final File _file; | ||||
/** the simple name of the database */ | /** the simple name of the database */ | ||||
private final String _name; | private final String _name; | ||||
/** whether or not this db is read-only */ | |||||
private final boolean _readOnly; | |||||
/** Buffer to hold database pages */ | /** Buffer to hold database pages */ | ||||
private ByteBuffer _buffer; | private ByteBuffer _buffer; | ||||
/** ID of the Tables system object */ | /** ID of the Tables system object */ | ||||
} | } | ||||
DatabaseImpl db = new DatabaseImpl(mdbFile, channel, closeChannel, autoSync, | DatabaseImpl db = new DatabaseImpl(mdbFile, channel, closeChannel, autoSync, | ||||
null, charset, timeZone, provider); | |||||
null, charset, timeZone, provider, | |||||
readOnly); | |||||
success = true; | success = true; | ||||
return db; | return db; | ||||
transferDbFrom(channel, getResourceAsStream(details.getEmptyFilePath())); | transferDbFrom(channel, getResourceAsStream(details.getEmptyFilePath())); | ||||
channel.force(true); | channel.force(true); | ||||
DatabaseImpl db = new DatabaseImpl(mdbFile, channel, closeChannel, autoSync, | DatabaseImpl db = new DatabaseImpl(mdbFile, channel, closeChannel, autoSync, | ||||
fileFormat, charset, timeZone, null); | |||||
fileFormat, charset, timeZone, null, | |||||
false); | |||||
success = true; | success = true; | ||||
return db; | return db; | ||||
} finally { | } finally { | ||||
*/ | */ | ||||
protected DatabaseImpl(File file, FileChannel channel, boolean closeChannel, | protected DatabaseImpl(File file, FileChannel channel, boolean closeChannel, | ||||
boolean autoSync, FileFormat fileFormat, Charset charset, | boolean autoSync, FileFormat fileFormat, Charset charset, | ||||
TimeZone timeZone, CodecProvider provider) | |||||
TimeZone timeZone, CodecProvider provider, | |||||
boolean readOnly) | |||||
throws IOException | throws IOException | ||||
{ | { | ||||
_file = file; | _file = file; | ||||
_name = getName(file); | _name = getName(file); | ||||
_readOnly = readOnly; | |||||
_format = JetFormat.getFormat(channel); | _format = JetFormat.getFormat(channel); | ||||
_charset = ((charset == null) ? getDefaultCharset(_format) : charset); | _charset = ((charset == null) ? getDefaultCharset(_format) : charset); | ||||
_columnOrder = getDefaultColumnOrder(); | _columnOrder = getDefaultColumnOrder(); | ||||
return _name; | return _name; | ||||
} | } | ||||
public boolean isReadOnly() { | |||||
return _readOnly; | |||||
} | |||||
/** | /** | ||||
* @usage _advanced_method_ | * @usage _advanced_method_ | ||||
*/ | */ |
} | } | ||||
}); | }); | ||||
public static final Function FORMAT = registerFunc(new FuncVar("Format", 1, 4) { | |||||
public static final Function FORMAT = registerStringFunc(new FuncVar("Format", 1, 4) { | |||||
@Override | @Override | ||||
protected Value evalVar(EvalContext ctx, Value[] params) { | protected Value evalVar(EvalContext ctx, Value[] params) { | ||||
String fmtStr = params[1].getAsString(ctx); | String fmtStr = params[1].getAsString(ctx); | ||||
int firstDay = DefaultDateFunctions.getFirstDayParam(ctx, params, 2); | int firstDay = DefaultDateFunctions.getFirstDayParam(ctx, params, 2); | ||||
int firstWeekType = DefaultDateFunctions.getFirstWeekTypeParam(ctx, params, 3); | int firstWeekType = DefaultDateFunctions.getFirstWeekTypeParam(ctx, params, 3); | ||||
return FormatUtil.format(ctx, expr, fmtStr, firstDay, firstWeekType); | return FormatUtil.format(ctx, expr, fmtStr, firstDay, firstWeekType); | ||||
} | } | ||||
}); | }); | ||||
private static String nchars(int num, char c) { | private static String nchars(int num, char c) { | ||||
StringBuilder sb = new StringBuilder(num); | StringBuilder sb = new StringBuilder(num); | ||||
nchars(sb, num, c); | nchars(sb, num, c); | ||||
sb.append(c); | sb.append(c); | ||||
} | } | ||||
} | } | ||||
private static String trim(String str, boolean doLeft, boolean doRight) { | private static String trim(String str, boolean doLeft, boolean doRight) { | ||||
int start = 0; | int start = 0; | ||||
int end = str.length(); | int end = str.length(); |
* <pre> | * <pre> | ||||
* // attempt to load the linkeeFileName as a custom file | * // attempt to load the linkeeFileName as a custom file | ||||
* Object customFile = loadCustomFile(linkerDb, linkeeFileName); | * Object customFile = loadCustomFile(linkerDb, linkeeFileName); | ||||
* | |||||
* | |||||
* if(customFile != null) { | * if(customFile != null) { | ||||
* // this is a custom file, create and return relevant temp db | * // this is a custom file, create and return relevant temp db | ||||
* return createTempDb(customFile, getDefaultFormat(), isDefaultInMemory(), | * return createTempDb(customFile, getDefaultFormat(), isDefaultInMemory(), | ||||
* getDefaultTempDirectory()); | * getDefaultTempDirectory()); | ||||
* } | * } | ||||
* | |||||
* | |||||
* // not a custmom file, load using the default behavior | * // not a custmom file, load using the default behavior | ||||
* return LinkResolver.DEFAULT.resolveLinkedDatabase(linkerDb, linkeeFileName); | * return LinkResolver.DEFAULT.resolveLinkedDatabase(linkerDb, linkeeFileName); | ||||
* </pre> | * </pre> | ||||
* | |||||
* | |||||
* @see #loadCustomFile | * @see #loadCustomFile | ||||
* @see #createTempDb | * @see #createTempDb | ||||
* @see LinkResolver#DEFAULT | * @see LinkResolver#DEFAULT | ||||
*/ | */ | ||||
public Database resolveLinkedDatabase(Database linkerDb, String linkeeFileName) | public Database resolveLinkedDatabase(Database linkerDb, String linkeeFileName) | ||||
throws IOException | |||||
throws IOException | |||||
{ | { | ||||
Object customFile = loadCustomFile(linkerDb, linkeeFileName); | Object customFile = loadCustomFile(linkerDb, linkeeFileName); | ||||
if(customFile != null) { | if(customFile != null) { | ||||
return createTempDb(customFile, getDefaultFormat(), isDefaultInMemory(), | |||||
getDefaultTempDirectory()); | |||||
// if linker is read-only, open linkee read-only | |||||
boolean readOnly = ((linkerDb instanceof DatabaseImpl) ? | |||||
((DatabaseImpl)linkerDb).isReadOnly() : false); | |||||
return createTempDb(customFile, getDefaultFormat(), isDefaultInMemory(), | |||||
getDefaultTempDirectory(), readOnly); | |||||
} | } | ||||
return LinkResolver.DEFAULT.resolveLinkedDatabase(linkerDb, linkeeFileName); | return LinkResolver.DEFAULT.resolveLinkedDatabase(linkerDb, linkeeFileName); | ||||
} | } | ||||
* | * | ||||
* @return the temp db for holding the linked table info | * @return the temp db for holding the linked table info | ||||
*/ | */ | ||||
protected Database createTempDb(Object customFile, FileFormat format, | |||||
boolean inMemory, File tempDir) | |||||
protected Database createTempDb(Object customFile, FileFormat format, | |||||
boolean inMemory, File tempDir, | |||||
boolean readOnly) | |||||
throws IOException | throws IOException | ||||
{ | { | ||||
File dbFile = null; | File dbFile = null; | ||||
} | } | ||||
TempDatabaseImpl.initDbChannel(channel, format); | TempDatabaseImpl.initDbChannel(channel, format); | ||||
TempDatabaseImpl db = new TempDatabaseImpl(this, customFile, dbFile, | |||||
channel, format); | |||||
TempDatabaseImpl db = new TempDatabaseImpl(this, customFile, dbFile, | |||||
channel, format, readOnly); | |||||
success = true; | success = true; | ||||
return db; | return db; | ||||
ByteUtil.closeQuietly((Closeable)customFile); | ByteUtil.closeQuietly((Closeable)customFile); | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Called by {@link #resolveLinkedDatabase} to determine whether the | * Called by {@link #resolveLinkedDatabase} to determine whether the | ||||
* linkeeFileName should be treated as a custom file (thus utiliziing a temp | * linkeeFileName should be treated as a custom file (thus utiliziing a temp | ||||
private final Object _customFile; | private final Object _customFile; | ||||
protected TempDatabaseImpl(CustomLinkResolver resolver, Object customFile, | protected TempDatabaseImpl(CustomLinkResolver resolver, Object customFile, | ||||
File file, FileChannel channel, | |||||
FileFormat fileFormat) | |||||
File file, FileChannel channel, | |||||
FileFormat fileFormat, boolean readOnly) | |||||
throws IOException | throws IOException | ||||
{ | { | ||||
super(file, channel, true, false, fileFormat, null, null, null); | |||||
super(file, channel, true, false, fileFormat, null, null, null, | |||||
readOnly); | |||||
_resolver = resolver; | _resolver = resolver; | ||||
_customFile = customFile; | _customFile = customFile; | ||||
} | } | ||||
@Override | @Override | ||||
protected TableImpl getTable(String name, boolean includeSystemTables) | |||||
throws IOException | |||||
protected TableImpl getTable(String name, boolean includeSystemTables) | |||||
throws IOException | |||||
{ | { | ||||
TableImpl table = super.getTable(name, includeSystemTables); | TableImpl table = super.getTable(name, includeSystemTables); | ||||
if((table == null) && | |||||
if((table == null) && | |||||
_resolver.loadCustomTable(this, _customFile, name)) { | _resolver.loadCustomTable(this, _customFile, name)) { | ||||
table = super.getTable(name, includeSystemTables); | table = super.getTable(name, includeSystemTables); | ||||
} | } |
import com.healthmarketscience.jackcess.Database; | import com.healthmarketscience.jackcess.Database; | ||||
import com.healthmarketscience.jackcess.DatabaseBuilder; | import com.healthmarketscience.jackcess.DatabaseBuilder; | ||||
import com.healthmarketscience.jackcess.impl.DatabaseImpl; | |||||
/** | /** | ||||
* Resolver for linked databases. | * Resolver for linked databases. | ||||
* @author James Ahlborn | * @author James Ahlborn | ||||
* @usage _intermediate_class_ | * @usage _intermediate_class_ | ||||
*/ | */ | ||||
public interface LinkResolver | |||||
public interface LinkResolver | |||||
{ | { | ||||
/** | /** | ||||
* default link resolver used if none provided | * default link resolver used if none provided | ||||
String linkeeFileName) | String linkeeFileName) | ||||
throws IOException | throws IOException | ||||
{ | { | ||||
return DatabaseBuilder.open(new File(linkeeFileName)); | |||||
// if linker is read-only, open linkee read-only | |||||
boolean readOnly = ((linkerDb instanceof DatabaseImpl) ? | |||||
((DatabaseImpl)linkerDb).isReadOnly() : false); | |||||
return new DatabaseBuilder(new File(linkeeFileName)) | |||||
.setReadOnly(readOnly).open(); | |||||
} | } | ||||
}; | }; | ||||
Table t1 = db.getTable("Table1"); | Table t1 = db.getTable("Table1"); | ||||
assertNotNull(t1); | assertNotNull(t1); | ||||
assertNotSame(db, t1.getDatabase()); | assertNotSame(db, t1.getDatabase()); | ||||
assertTable(createExpectedTable(createExpectedRow("id", 0, | assertTable(createExpectedTable(createExpectedRow("id", 0, | ||||
"data1", "row0"), | "data1", "row0"), | ||||
createExpectedRow("id", 1, | createExpectedRow("id", 1, | ||||
Database linkerDb, String linkeeFileName) throws IOException | Database linkerDb, String linkeeFileName) throws IOException | ||||
{ | { | ||||
return (("testFile1.txt".equals(linkeeFileName) || | return (("testFile1.txt".equals(linkeeFileName) || | ||||
"testFile2.txt".equals(linkeeFileName)) ? | |||||
"testFile2.txt".equals(linkeeFileName)) ? | |||||
linkeeFileName : null); | linkeeFileName : null); | ||||
} | } | ||||
for(int i = 0; i < 3; ++i) { | for(int i = 0; i < 3; ++i) { | ||||
t.addRow(i, "row" + i); | t.addRow(i, "row" + i); | ||||
} | } | ||||
return true; | return true; | ||||
} else if("OtherTable2".equals(tableName)) { | } else if("OtherTable2".equals(tableName)) { | ||||
for(int i = 3; i < 6; ++i) { | for(int i = 3; i < 6; ++i) { | ||||
t.addRow(i, "row" + i); | t.addRow(i, "row" + i); | ||||
} | } | ||||
return true; | return true; | ||||
} else if("Table4".equals(tableName)) { | } else if("Table4".equals(tableName)) { | ||||
@Override | @Override | ||||
protected Database createTempDb(Object customFile, FileFormat format, | protected Database createTempDb(Object customFile, FileFormat format, | ||||
boolean inMemory, File tempDir) | |||||
boolean inMemory, File tempDir, | |||||
boolean readOnly) | |||||
throws IOException | throws IOException | ||||
{ | { | ||||
inMemory = "testFile1.txt".equals(customFile); | inMemory = "testFile1.txt".equals(customFile); | ||||
return super.createTempDb(customFile, format, inMemory, tempDir); | |||||
return super.createTempDb(customFile, format, inMemory, tempDir, | |||||
readOnly); | |||||
} | } | ||||
} | } | ||||
} | } |