diff options
author | James Ahlborn <jtahlborn@yahoo.com> | 2017-05-24 03:26:58 +0000 |
---|---|---|
committer | James Ahlborn <jtahlborn@yahoo.com> | 2017-05-24 03:26:58 +0000 |
commit | 9ad8aaabc77b773bee4419edc794f63ee2a16dbb (patch) | |
tree | 5397bcbd54a681b98ed2a005357f3112354f9cb5 | |
parent | 9a7f57b361e43603888d91901987bbfff4b72695 (diff) | |
parent | 4c087727dc6fd9739a44b6874001304b629938f4 (diff) | |
download | jackcess-9ad8aaabc77b773bee4419edc794f63ee2a16dbb.tar.gz jackcess-9ad8aaabc77b773bee4419edc794f63ee2a16dbb.zip |
merge trunk changes through r1100
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/exprs@1101 f203690c-595d-4dc9-a70b-905162fa7fd2
12 files changed, 530 insertions, 40 deletions
@@ -9,7 +9,7 @@ <artifactId>jackcess</artifactId> <name>Jackcess</name> <description>A pure Java library for reading from and writing to MS Access databases.</description> - <version>2.1.7-SNAPSHOT</version> + <version>2.1.8-SNAPSHOT</version> <url>http://jackcess.sf.net</url> <inceptionYear>2005</inceptionYear> <developers> @@ -128,10 +128,6 @@ <value>log4j_test.properties</value> </property> <property> - <name>com.healthmarketscience.jackcess.bigIndex</name> - <value>${jackcess.bigIndex}</value> - </property> - <property> <name>com.healthmarketscience.jackcess.testFormats</name> <value>${jackcess.testFormats}</value> </property> @@ -247,6 +243,7 @@ <issueLinkTemplatePerSystem> <SourceForge2Features>http://sourceforge.net/p/jackcess/feature-requests/%ISSUE%</SourceForge2Features> <SourceForge2Patches>http://sourceforge.net/p/jackcess/patches/%ISSUE%</SourceForge2Patches> + <GitHubPullRequests>https://github.com/jahlborn/jackcess/pull/%ISSUE%</GitHubPullRequests> </issueLinkTemplatePerSystem> </configuration> </plugin> @@ -312,8 +309,4 @@ <url>scp://shell.sourceforge.net/home/project-web/jackcess/htdocs</url> </site> </distributionManagement> - <properties> - <jackcess.bigIndex>true</jackcess.bigIndex> - <jackcess.testFormats>V1997,V2000,V2003,V2007,V2010</jackcess.testFormats> - </properties> </project> diff --git a/src/changes/changes.xml b/src/changes/changes.xml index a172b92..f2c0e5e 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -4,12 +4,25 @@ <author email="javajedi@users.sf.net">Tim McCune</author> </properties> <body> - <release version="2.1.7" date="TBD"> + <release version="2.1.7" date="2017-05-17"> <action dev="jahlborn" type="update"> Implement support for partial index lookups. Efficient IndexCursor lookups can now be done with multi-column indexes using only some of the columns in the index. </action> + <action dev="jahlborn" type="update" system="GitHubPullRequests" + issue="2"> + Cover the GENERIC_JET4 format in unit tests, thanks to Gord Thompson. + </action> + <action dev="jahlborn" type="update" system="GitHubPullRequests" + issue="3"> + Allow inserting negative auto number fields, thanks to Gord Thompson. + </action> + <action dev="jahlborn" type="update" system="SourceForge2Features" + issue="36"> + Add CustomLinkResolver which facilitates loading linked tables from + files which are not access databases. + </action> </release> <release version="2.1.6" date="2016-11-29"> <action dev="jahlborn" type="update" system="SourceForge2Features" diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java index 203ad82..02b69ea 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java @@ -2015,7 +2015,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { throws IOException { int inAutoNum = toNumber(inRowValue).intValue(); - if(inAutoNum <= INVALID_AUTO_NUMBER) { + if(inAutoNum <= INVALID_AUTO_NUMBER && !getTable().isAllowAutoNumberInsert()) { throw new IOException(withErrorContext( "Invalid auto number value " + inAutoNum)); } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java index 319879b..1c66859 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java @@ -455,10 +455,10 @@ public class DatabaseImpl implements Database boolean success = false; try { channel.truncate(0); - transferFrom(channel, getResourceAsStream(details.getEmptyFilePath())); + transferDbFrom(channel, getResourceAsStream(details.getEmptyFilePath())); channel.force(true); DatabaseImpl db = new DatabaseImpl(mdbFile, channel, closeChannel, autoSync, - fileFormat, charset, timeZone, null); + fileFormat, charset, timeZone, null); success = true; return db; } finally { @@ -509,8 +509,8 @@ public class DatabaseImpl implements Database * @param timeZone TimeZone to use, if {@code null}, uses default */ protected DatabaseImpl(File file, FileChannel channel, boolean closeChannel, - boolean autoSync, FileFormat fileFormat, Charset charset, - TimeZone timeZone, CodecProvider provider) + boolean autoSync, FileFormat fileFormat, Charset charset, + TimeZone timeZone, CodecProvider provider) throws IOException { _file = file; @@ -984,7 +984,7 @@ public class DatabaseImpl implements Database * @param includeSystemTables whether to consider returning a system table * @return The table, or null if it doesn't exist */ - private TableImpl getTable(String name, boolean includeSystemTables) + protected TableImpl getTable(String name, boolean includeSystemTables) throws IOException { TableInfo tableInfo = getTableInfo(name, includeSystemTables); @@ -1961,10 +1961,10 @@ public class DatabaseImpl implements Database } /** - * Copies the given InputStream to the given channel using the most + * Copies the given db InputStream to the given channel using the most * efficient means possible. */ - private static void transferFrom(FileChannel channel, InputStream in) + protected static void transferDbFrom(FileChannel channel, InputStream in) throws IOException { ReadableByteChannel readChannel = Channels.newChannel(in); @@ -2004,7 +2004,7 @@ public class DatabaseImpl implements Database return pwdMask; } - static InputStream getResourceAsStream(String resourceName) + protected static InputStream getResourceAsStream(String resourceName) throws IOException { InputStream stream = DatabaseImpl.class.getClassLoader() diff --git a/src/main/java/com/healthmarketscience/jackcess/util/CustomLinkResolver.java b/src/main/java/com/healthmarketscience/jackcess/util/CustomLinkResolver.java new file mode 100644 index 0000000..7992d71 --- /dev/null +++ b/src/main/java/com/healthmarketscience/jackcess/util/CustomLinkResolver.java @@ -0,0 +1,295 @@ +/* +Copyright (c) 2017 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.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.util.Random; + +import com.healthmarketscience.jackcess.Database; +import com.healthmarketscience.jackcess.Database.FileFormat; +import com.healthmarketscience.jackcess.Table; +import com.healthmarketscience.jackcess.impl.ByteUtil; +import com.healthmarketscience.jackcess.impl.DatabaseImpl; +import com.healthmarketscience.jackcess.impl.TableImpl; + +/** + * Utility base implementaton of LinkResolver which facilitates loading linked + * tables from files which are not access databases. The LinkResolver API + * ultimately presents linked table information to the primary database using + * the jackcess {@link Database} and {@link Table} classes. In order to + * consume linked tables in non-mdb files, they need to somehow be coerced + * into the appropriate form. The approach taken by this utility is to make + * it easy to copy the external tables into a temporary mdb file for + * consumption by the primary database. + * <p> + * The primary features of this utility: + * <ul> + * <li>Supports custom behavior for non-mdb files and default behavior for mdb + * files, see {@link #loadCustomFile}</li> + * <li>Temp db can be an actual file or entirely in memory</li> + * <li>Linked tables are loaded on-demand, see {@link #loadCustomTable}</li> + * <li>Temp db files will be automatically deleted on close</li> + * </ul> + * + * @author James Ahlborn + * @usage _intermediate_class_ + */ +public abstract class CustomLinkResolver implements LinkResolver +{ + private static final Random DB_ID = new Random(); + + private static final String MEM_DB_PREFIX = "memdb_"; + private static final String FILE_DB_PREFIX = "linkeddb_"; + + /** the default file format used for temp dbs */ + public static final FileFormat DEFAULT_FORMAT = FileFormat.V2000; + /** temp dbs default to the filesystem, not in memory */ + public static final boolean DEFAULT_IN_MEMORY = false; + /** temp dbs end up in the system temp dir by default */ + public static final File DEFAULT_TEMP_DIR = null; + + private final FileFormat _defaultFormat; + private final boolean _defaultInMemory; + private final File _defaultTempDir; + + /** + * Creates a CustomLinkResolver using the default behavior for creating temp + * dbs, see {@link #DEFAULT_FORMAT}, {@link #DEFAULT_IN_MEMORY} and + * {@link #DEFAULT_TEMP_DIR}. + */ + protected CustomLinkResolver() { + this(DEFAULT_FORMAT, DEFAULT_IN_MEMORY, DEFAULT_TEMP_DIR); + } + + /** + * Creates a CustomLinkResolver with the given default behavior for creating + * temp dbs. + * + * @param defaultFormat the default format for the temp db + * @param defaultInMemory whether or not the temp db should be entirely in + * memory by default (while this will be faster, it + * should only be used if table data is expected to + * fit entirely in memory) + * @param defaultTempDir the default temp dir for a file based temp db + * ({@code null} for the system defaqult temp + * directory) + */ + protected CustomLinkResolver(FileFormat defaultFormat, boolean defaultInMemory, + File defaultTempDir) + { + _defaultFormat = defaultFormat; + _defaultInMemory = defaultInMemory; + _defaultTempDir = defaultTempDir; + } + + protected FileFormat getDefaultFormat() { + return _defaultFormat; + } + + protected boolean isDefaultInMemory() { + return _defaultInMemory; + } + + protected File getDefaultTempDirectory() { + return _defaultTempDir; + } + + /** + * Custom implementation is: + * <pre> + * // attempt to load the linkeeFileName as a custom file + * Object customFile = loadCustomFile(linkerDb, linkeeFileName); + * + * if(customFile != null) { + * // this is a custom file, create and return relevant temp db + * return createTempDb(customFile, getDefaultFormat(), isDefaultInMemory(), + * getDefaultTempDirectory()); + * } + * + * // not a custmom file, load using the default behavior + * return LinkResolver.DEFAULT.resolveLinkedDatabase(linkerDb, linkeeFileName); + * </pre> + * + * @see #loadCustomFile + * @see #createTempDb + * @see LinkResolver#DEFAULT + */ + public Database resolveLinkedDatabase(Database linkerDb, String linkeeFileName) + throws IOException + { + Object customFile = loadCustomFile(linkerDb, linkeeFileName); + if(customFile != null) { + return createTempDb(customFile, getDefaultFormat(), isDefaultInMemory(), + getDefaultTempDirectory()); + } + return LinkResolver.DEFAULT.resolveLinkedDatabase(linkerDb, linkeeFileName); + } + + /** + * Creates a temporary database for holding the table data from + * linkeeFileName. + * + * @param customFile custom file state returned from {@link #loadCustomFile} + * @param format the access format for the temp db + * @param inMemory whether or not the temp db should be entirely in memory + * (while this will be faster, it should only be used if + * table data is expected to fit entirely in memory) + * @param tempDir the temp dir for a file based temp db ({@code null} for + * the system default temp directory) + * + * @return the temp db for holding the linked table info + */ + protected Database createTempDb(Object customFile, FileFormat format, + boolean inMemory, File tempDir) + throws IOException + { + File dbFile = null; + FileChannel channel = null; + boolean success = false; + try { + + if(inMemory) { + dbFile = new File(MEM_DB_PREFIX + DB_ID.nextLong() + + format.getFileExtension()); + channel = MemFileChannel.newChannel(); + } else { + dbFile = File.createTempFile(FILE_DB_PREFIX, format.getFileExtension(), + tempDir); + channel = new RandomAccessFile(dbFile, DatabaseImpl.RW_CHANNEL_MODE) + .getChannel(); + } + + TempDatabaseImpl.initDbChannel(channel, format); + TempDatabaseImpl db = new TempDatabaseImpl(this, customFile, dbFile, + channel, format); + success = true; + return db; + + } finally { + if(!success) { + ByteUtil.closeQuietly(channel); + deleteDbFile(dbFile); + closeCustomFile(customFile); + } + } + } + + private static void deleteDbFile(File dbFile) { + if((dbFile != null) && (dbFile.getName().startsWith(FILE_DB_PREFIX))) { + dbFile.delete(); + } + } + + private static void closeCustomFile(Object customFile) { + if(customFile instanceof Closeable) { + ByteUtil.closeQuietly((Closeable)customFile); + } + } + + /** + * Called by {@link #resolveLinkedDatabase} to determine whether the + * linkeeFileName should be treated as a custom file (thus utiliziing a temp + * db) or a normal access db (loaded via the default behavior). Loads any + * state necessary for subsequently loading data from linkeeFileName. + * <p> + * The returned custom file state object will be maintained with the temp db + * and passed to {@link #loadCustomTable} whenever a new table needs to be + * loaded. Also, if this object is {@link Closeable}, it will be closed + * with the temp db. + * + * @param linkerDb the primary database in which the link is defined + * @param linkeeFileName the name of the linked file + * + * @return non-{@code null} if linkeeFileName should be treated as a custom + * file (using a temp db) or {@code null} if it should be treated as + * a normal access db. + */ + protected abstract Object loadCustomFile( + Database linkerDb, String linkeeFileName) throws IOException; + + /** + * Called by an instance of a temp db when a missing table is first requested. + * + * @param tempDb the temp db instance which should be populated with the + * relevant table info for the given tableName + * @param customFile custom file state returned from {@link #loadCustomFile} + * @param tableName the name of the table which is requested from the linked + * file + * + * @return {@code true} if the table was available in the linked file, + * {@code false} otherwise + */ + protected abstract boolean loadCustomTable( + Database tempDb, Object customFile, String tableName) + throws IOException; + + + /** + * Subclass of DatabaseImpl which allows us to load tables "on demand" as + * well as delete the temporary db on close. + */ + private static class TempDatabaseImpl extends DatabaseImpl + { + private final CustomLinkResolver _resolver; + private final Object _customFile; + + protected TempDatabaseImpl(CustomLinkResolver resolver, Object customFile, + File file, FileChannel channel, + FileFormat fileFormat) + throws IOException + { + super(file, channel, true, false, fileFormat, null, null, null); + _resolver = resolver; + _customFile = customFile; + } + + @Override + protected TableImpl getTable(String name, boolean includeSystemTables) + throws IOException + { + TableImpl table = super.getTable(name, includeSystemTables); + if((table == null) && + _resolver.loadCustomTable(this, _customFile, name)) { + table = super.getTable(name, includeSystemTables); + } + return table; + } + + @Override + public void close() throws IOException { + try { + super.close(); + } finally { + deleteDbFile(getFile()); + closeCustomFile(_customFile); + } + } + + static FileChannel initDbChannel(FileChannel channel, FileFormat format) + throws IOException + { + FileFormatDetails details = getFileFormatDetails(format); + transferDbFrom(channel, getResourceAsStream(details.getEmptyFilePath())); + return channel; + } + } + +} diff --git a/src/test/java/com/healthmarketscience/jackcess/DatabaseTest.java b/src/test/java/com/healthmarketscience/jackcess/DatabaseTest.java index 6a6fd34..025e180 100644 --- a/src/test/java/com/healthmarketscience/jackcess/DatabaseTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/DatabaseTest.java @@ -742,7 +742,9 @@ public class DatabaseTest extends TestCase Arrays.asList("MSysObjects", "MSysQueries", "MSysACES", "MSysRelationships")); - if (fileFormat.ordinal() < FileFormat.V2003.ordinal()) { + if (fileFormat == FileFormat.GENERIC_JET4) { + assertNull("file format: " + fileFormat, db.getSystemTable("MSysAccessObjects")); + } else if (fileFormat.ordinal() < FileFormat.V2003.ordinal()) { assertNotNull("file format: " + fileFormat, db.getSystemTable("MSysAccessObjects")); sysTables.add("MSysAccessObjects"); } else { diff --git a/src/test/java/com/healthmarketscience/jackcess/PropertiesTest.java b/src/test/java/com/healthmarketscience/jackcess/PropertiesTest.java index a5ec859..89ab4c4 100644 --- a/src/test/java/com/healthmarketscience/jackcess/PropertiesTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/PropertiesTest.java @@ -24,7 +24,6 @@ import java.util.List; import java.util.UUID; import static com.healthmarketscience.jackcess.Database.*; -import static com.healthmarketscience.jackcess.DatabaseTest.*; import com.healthmarketscience.jackcess.impl.DatabaseImpl; import static com.healthmarketscience.jackcess.impl.JetFormatTest.*; import com.healthmarketscience.jackcess.impl.PropertyMapImpl; @@ -349,6 +348,12 @@ public class PropertiesTest extends TestCase public void testCreateDbProperties() throws Exception { for(FileFormat ff : SUPPORTED_FILEFORMATS) { + + if(ff == FileFormat.GENERIC_JET4) { + // weirdo format, no properties + continue; + } + File file = TestUtil.createTempFile(false); Database db = new DatabaseBuilder(file) .setFileFormat(ff) diff --git a/src/test/java/com/healthmarketscience/jackcess/TestUtil.java b/src/test/java/com/healthmarketscience/jackcess/TestUtil.java index c6856bc..3317c7f 100644 --- a/src/test/java/com/healthmarketscience/jackcess/TestUtil.java +++ b/src/test/java/com/healthmarketscience/jackcess/TestUtil.java @@ -41,6 +41,7 @@ import com.healthmarketscience.jackcess.impl.ByteUtil; import com.healthmarketscience.jackcess.impl.DatabaseImpl; import com.healthmarketscience.jackcess.impl.IndexData; import com.healthmarketscience.jackcess.impl.IndexImpl; +import com.healthmarketscience.jackcess.impl.JetFormatTest; import com.healthmarketscience.jackcess.impl.JetFormatTest.TestDB; import com.healthmarketscience.jackcess.impl.RowIdImpl; import com.healthmarketscience.jackcess.impl.RowImpl; @@ -84,7 +85,8 @@ public class TestUtil public static Database open(FileFormat fileFormat, File file, boolean inMem) throws Exception { - FileChannel channel = (inMem ? MemFileChannel.newChannel(file, "rw") + FileChannel channel = (inMem ? MemFileChannel.newChannel( + file, DatabaseImpl.RW_CHANNEL_MODE) : null); final Database db = new DatabaseBuilder(file).setReadOnly(true) .setAutoSync(getTestAutoSync()).setChannel(channel).open(); @@ -122,8 +124,32 @@ public class TestUtil throws Exception { FileChannel channel = (inMem ? MemFileChannel.newChannel() : null); + + if (fileFormat == FileFormat.GENERIC_JET4) { + // while we don't support creating GENERIC_JET4 as a jackcess feature, + // we do want to be able to test these types of dbs + InputStream inStream = null; + OutputStream outStream = null; + try { + inStream = TestUtil.class.getClassLoader() + .getResourceAsStream("emptyJet4.mdb"); + File f = createTempFile(keep); + if (channel != null) { + JetFormatTest.transferDbFrom(channel, inStream); + } else { + ByteUtil.copy(inStream, outStream = new FileOutputStream(f)); + outStream.close(); + } + return new DatabaseBuilder(f) + .setAutoSync(getTestAutoSync()).setChannel(channel).open(); + } finally { + ByteUtil.closeQuietly(inStream); + ByteUtil.closeQuietly(outStream); + } + } + return new DatabaseBuilder(createTempFile(keep)).setFileFormat(fileFormat) - .setAutoSync(getTestAutoSync()).setChannel(channel).create(); + .setAutoSync(getTestAutoSync()).setChannel(channel).create(); } diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/AutoNumberTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/AutoNumberTest.java index 37eec77..eae2c25 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/AutoNumberTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/AutoNumberTest.java @@ -242,12 +242,7 @@ public class AutoNumberTest extends TestCase assertEquals(13, ((TableImpl)table).getLastLongAutoNumber()); - try { - table.addRow(-10, "uh-uh"); - fail("IOException should have been thrown"); - } catch(IOException e) { - // success - } + table.addRow(-10, "non-positives are now allowed"); row = table.addRow(Column.AUTO_NUMBER, "row14"); assertEquals(14, ((Integer)row[0]).intValue()); @@ -262,23 +257,18 @@ public class AutoNumberTest extends TestCase assertEquals(45, ((TableImpl)table).getLastLongAutoNumber()); - row13.put("a", -1); - - try { - table.updateRow(row13); - fail("IOException should have been thrown"); - } catch(IOException e) { - // success - } + row13.put("a", -1); // non-positives are now allowed + table.updateRow(row13); assertEquals(45, ((TableImpl)table).getLastLongAutoNumber()); row13.put("a", 55); - table.setAllowAutoNumberInsert(null); + // reset to db-level policy (which in this case is "false") + table.setAllowAutoNumberInsert(null); - row13 = table.updateRow(row13); - assertEquals(45, row13.get("a")); + row13 = table.updateRow(row13); // no change, as confirmed by... + assertEquals(-1, row13.get("a")); assertEquals(45, ((TableImpl)table).getLastLongAutoNumber()); diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/JetFormatTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/JetFormatTest.java index 4efbd60..b302985 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/JetFormatTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/JetFormatTest.java @@ -1,6 +1,8 @@ package com.healthmarketscience.jackcess.impl; import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.nio.channels.FileChannel; import java.nio.channels.NonWritableChannelException; import java.util.ArrayList; @@ -263,4 +265,9 @@ public class JetFormatTest extends TestCase { } } + public static void transferDbFrom(FileChannel channel, InputStream in) + throws IOException + { + DatabaseImpl.transferDbFrom(channel, in); + } } diff --git a/src/test/java/com/healthmarketscience/jackcess/util/CustomLinkResolverTest.java b/src/test/java/com/healthmarketscience/jackcess/util/CustomLinkResolverTest.java new file mode 100644 index 0000000..31a8853 --- /dev/null +++ b/src/test/java/com/healthmarketscience/jackcess/util/CustomLinkResolverTest.java @@ -0,0 +1,159 @@ +/* +Copyright (c) 2017 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.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +import com.healthmarketscience.jackcess.ColumnBuilder; +import com.healthmarketscience.jackcess.DataType; +import com.healthmarketscience.jackcess.Database; +import com.healthmarketscience.jackcess.Database.FileFormat; +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 CustomLinkResolverTest extends TestCase +{ + + public CustomLinkResolverTest(String name) { + super(name); + } + + public void testCustomLinkResolver() throws Exception { + for(final FileFormat fileFormat : SUPPORTED_FILEFORMATS) { + Database db = create(fileFormat); + + db.setLinkResolver(new TestLinkResolver()); + + db.createLinkedTable("Table1", "testFile1.txt", "Table1"); + db.createLinkedTable("Table2", "testFile2.txt", "OtherTable2"); + db.createLinkedTable("Table3", "missingFile3.txt", "MissingTable3"); + db.createLinkedTable("Table4", "testFile2.txt", "MissingTable4"); + + Table t1 = db.getTable("Table1"); + assertNotNull(t1); + assertNotSame(db, t1.getDatabase()); + + assertTable(createExpectedTable(createExpectedRow("id", 0, + "data1", "row0"), + createExpectedRow("id", 1, + "data1", "row1"), + createExpectedRow("id", 2, + "data1", "row2")), + t1); + + Table t2 = db.getTable("Table2"); + assertNotNull(t2); + assertNotSame(db, t2.getDatabase()); + + assertTable(createExpectedTable(createExpectedRow("id", 3, + "data2", "row3"), + createExpectedRow("id", 4, + "data2", "row4"), + createExpectedRow("id", 5, + "data2", "row5")), + t2); + + assertNull(db.getTable("Table4")); + + try { + db.getTable("Table3"); + fail("FileNotFoundException should have been thrown"); + } catch(FileNotFoundException e) { + // success + } + + db.close(); + } + } + + private static class TestLinkResolver extends CustomLinkResolver + { + private TestLinkResolver() + { + super(DEFAULT_FORMAT, true, DEFAULT_TEMP_DIR); + } + + @Override + protected Object loadCustomFile( + Database linkerDb, String linkeeFileName) throws IOException + { + return (("testFile1.txt".equals(linkeeFileName) || + "testFile2.txt".equals(linkeeFileName)) ? + linkeeFileName : null); + } + + @Override + protected boolean loadCustomTable( + Database tempDb, Object customFile, String tableName) + throws IOException + { + if("Table1".equals(tableName)) { + + assertEquals("testFile1.txt", customFile); + Table t = new TableBuilder(tableName) + .addColumn(new ColumnBuilder("id", DataType.LONG)) + .addColumn(new ColumnBuilder("data1", DataType.TEXT)) + .toTable(tempDb); + + for(int i = 0; i < 3; ++i) { + t.addRow(i, "row" + i); + } + + return true; + + } else if("OtherTable2".equals(tableName)) { + + assertEquals("testFile2.txt", customFile); + Table t = new TableBuilder(tableName) + .addColumn(new ColumnBuilder("id", DataType.LONG)) + .addColumn(new ColumnBuilder("data2", DataType.TEXT)) + .toTable(tempDb); + + for(int i = 3; i < 6; ++i) { + t.addRow(i, "row" + i); + } + + return true; + + } else if("Table4".equals(tableName)) { + + assertEquals("testFile2.txt", customFile); + return false; + } + + return false; + } + + @Override + protected Database createTempDb(Object customFile, FileFormat format, + boolean inMemory, File tempDir) + throws IOException + { + inMemory = "testFile1.txt".equals(customFile); + return super.createTempDb(customFile, format, inMemory, tempDir); + } + } +} diff --git a/src/test/resources/emptyJet4.mdb b/src/test/resources/emptyJet4.mdb Binary files differnew file mode 100644 index 0000000..7367d01 --- /dev/null +++ b/src/test/resources/emptyJet4.mdb |