diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/changes/changes.xml | 4 | ||||
-rw-r--r-- | src/java/com/healthmarketscience/jackcess/Database.java | 236 | ||||
-rw-r--r-- | src/java/com/healthmarketscience/jackcess/DatabaseBuilder.java | 163 | ||||
-rw-r--r-- | src/java/com/healthmarketscience/jackcess/PageChannel.java | 11 |
4 files changed, 343 insertions, 71 deletions
diff --git a/src/changes/changes.xml b/src/changes/changes.xml index b27fa1e..afd3161 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -9,6 +9,10 @@ Add some more functionality to Joiner to facilitate integrity enforcement (hasRows and deleteRows). </action> + <action dev="jahlborn" type="add"> + Added DatabaseBuilder in for more convenient and flexible Database + open/create. + </action> </release> <release version="1.2.8" date="2012-07-10"> <action dev="jahlborn" type="update" issue="3523179"> diff --git a/src/java/com/healthmarketscience/jackcess/Database.java b/src/java/com/healthmarketscience/jackcess/Database.java index 4ef116a..1c6f347 100644 --- a/src/java/com/healthmarketscience/jackcess/Database.java +++ b/src/java/com/healthmarketscience/jackcess/Database.java @@ -486,6 +486,7 @@ public class Database * @param mdbFile File containing the database * * @see #open(File,boolean) + * @see DatabaseBuilder for more flexible Database opening * @usage _general_method_ */ public static Database open(File mdbFile) throws IOException { @@ -494,17 +495,18 @@ public class Database /** * Open an existing Database. If the existing file is not writeable or the - * readOnly flag is <code>true</code>, the file will be opened read-only. + * readOnly flag is {@code true}, the file will be opened read-only. * Auto-syncing is enabled for the returned Database. * <p> * Equivalent to: * {@code open(mdbFile, readOnly, DEFAULT_AUTO_SYNC);} * * @param mdbFile File containing the database - * @param readOnly iff <code>true</code>, force opening file in read-only + * @param readOnly iff {@code true}, force opening file in read-only * mode * * @see #open(File,boolean,boolean) + * @see DatabaseBuilder for more flexible Database opening * @usage _general_method_ */ public static Database open(File mdbFile, boolean readOnly) @@ -515,9 +517,9 @@ public class Database /** * Open an existing Database. If the existing file is not writeable or the - * readOnly flag is <code>true</code>, the file will be opened read-only. + * readOnly flag is {@code true}, the file will be opened read-only. * @param mdbFile File containing the database - * @param readOnly iff <code>true</code>, force opening file in read-only + * @param readOnly iff {@code true}, force opening file in read-only * mode * @param autoSync whether or not to enable auto-syncing on write. if * {@code true}, writes will be immediately flushed to disk. @@ -526,7 +528,9 @@ public class Database * updates. if {@code false}, flushing to disk happens at * the jvm's leisure, which can be much faster, but may * leave the database in an inconsistent state if failures - * are encountered during writing. + * are encountered during writing. Writes may be flushed at + * any time using {@link #flush}. + * @see DatabaseBuilder for more flexible Database opening * @usage _general_method_ */ public static Database open(File mdbFile, boolean readOnly, boolean autoSync) @@ -537,9 +541,9 @@ public class Database /** * Open an existing Database. If the existing file is not writeable or the - * readOnly flag is <code>true</code>, the file will be opened read-only. + * readOnly flag is {@code true}, the file will be opened read-only. * @param mdbFile File containing the database - * @param readOnly iff <code>true</code>, force opening file in read-only + * @param readOnly iff {@code true}, force opening file in read-only * mode * @param autoSync whether or not to enable auto-syncing on write. if * {@code true}, writes will be immediately flushed to disk. @@ -548,9 +552,11 @@ public class Database * updates. if {@code false}, flushing to disk happens at * the jvm's leisure, which can be much faster, but may * leave the database in an inconsistent state if failures - * are encountered during writing. + * are encountered during writing. Writes may be flushed at + * any time using {@link #flush}. * @param charset Charset to use, if {@code null}, uses default * @param timeZone TimeZone to use, if {@code null}, uses default + * @see DatabaseBuilder for more flexible Database opening * @usage _intermediate_method_ */ public static Database open(File mdbFile, boolean readOnly, boolean autoSync, @@ -562,9 +568,9 @@ public class Database /** * Open an existing Database. If the existing file is not writeable or the - * readOnly flag is <code>true</code>, the file will be opened read-only. + * readOnly flag is {@code true}, the file will be opened read-only. * @param mdbFile File containing the database - * @param readOnly iff <code>true</code>, force opening file in read-only + * @param readOnly iff {@code true}, force opening file in read-only * mode * @param autoSync whether or not to enable auto-syncing on write. if * {@code true}, writes will be immediately flushed to disk. @@ -573,11 +579,13 @@ public class Database * updates. if {@code false}, flushing to disk happens at * the jvm's leisure, which can be much faster, but may * leave the database in an inconsistent state if failures - * are encountered during writing. + * are encountered during writing. Writes may be flushed at + * any time using {@link #flush}. * @param charset Charset to use, if {@code null}, uses default * @param timeZone TimeZone to use, if {@code null}, uses default * @param provider CodecProvider for handling page encoding/decoding, may be * {@code null} if no special encoding is necessary + * @see DatabaseBuilder for more flexible Database opening * @usage _intermediate_method_ */ public static Database open(File mdbFile, boolean readOnly, boolean autoSync, @@ -585,34 +593,82 @@ public class Database CodecProvider provider) throws IOException { - if(!mdbFile.exists() || !mdbFile.canRead()) { - throw new FileNotFoundException("given file does not exist: " + mdbFile); + return open(mdbFile, readOnly, null, autoSync, charset, timeZone, + provider); + } + + /** + * Open an existing Database. If the existing file is not writeable or the + * readOnly flag is {@code true}, the file will be opened read-only. + * @param mdbFile File containing the database + * @param readOnly iff {@code true}, force opening file in read-only + * mode + * @param channel pre-opened FileChannel. if provided explicitly, it will + * not be closed by this Database instance + * @param autoSync whether or not to enable auto-syncing on write. if + * {@code true}, writes will be immediately flushed to disk. + * This leaves the database in a (fairly) consistent state + * on each write, but can be very inefficient for many + * updates. if {@code false}, flushing to disk happens at + * the jvm's leisure, which can be much faster, but may + * leave the database in an inconsistent state if failures + * are encountered during writing. Writes may be flushed at + * any time using {@link #flush}. + * @param charset Charset to use, if {@code null}, uses default + * @param timeZone TimeZone to use, if {@code null}, uses default + * @param provider CodecProvider for handling page encoding/decoding, may be + * {@code null} if no special encoding is necessary + * @usage _advanced_method_ + */ + static Database open(File mdbFile, boolean readOnly, FileChannel channel, + boolean autoSync, Charset charset, TimeZone timeZone, + CodecProvider provider) + throws IOException + { + boolean closeChannel = false; + if(channel == null) { + if(!mdbFile.exists() || !mdbFile.canRead()) { + throw new FileNotFoundException("given file does not exist: " + + mdbFile); + } + + // force read-only for non-writable files + readOnly |= !mdbFile.canWrite(); + + // open file channel + channel = openChannel(mdbFile, readOnly); + closeChannel = true; } - // force read-only for non-writable files - readOnly |= !mdbFile.canWrite(); + boolean success = false; + try { + + if(!readOnly) { - // open file channel - FileChannel channel = openChannel(mdbFile, readOnly); + // verify that format supports writing + JetFormat jetFormat = JetFormat.getFormat(channel); - if(!readOnly) { + if(jetFormat.READ_ONLY) { + throw new IOException("jet format '" + jetFormat + + "' does not support writing"); + } + } - // verify that format supports writing - JetFormat jetFormat = JetFormat.getFormat(channel); + Database db = new Database(mdbFile, channel, closeChannel, autoSync, + null, charset, timeZone, provider); + success = true; + return db; - if(jetFormat.READ_ONLY) { - // shutdown the channel (quietly) + } finally { + if(!success && closeChannel) { + // something blew up, shutdown the channel (quietly) try { channel.close(); } catch(Exception ignored) { // we don't care } - throw new IOException("jet format '" + jetFormat + "' does not support writing"); } } - - return new Database(mdbFile, channel, autoSync, null, charset, timeZone, - provider); } /** @@ -625,6 +681,7 @@ public class Database * already exists, it will be overwritten.</b> * * @see #create(File,boolean) + * @see DatabaseBuilder for more flexible Database creation * @usage _general_method_ */ public static Database create(File mdbFile) throws IOException { @@ -642,6 +699,7 @@ public class Database * already exists, it will be overwritten.</b> * * @see #create(File,boolean) + * @see DatabaseBuilder for more flexible Database creation * @usage _general_method_ */ public static Database create(FileFormat fileFormat, File mdbFile) @@ -665,7 +723,9 @@ public class Database * updates. if {@code false}, flushing to disk happens at * the jvm's leisure, which can be much faster, but may * leave the database in an inconsistent state if failures - * are encountered during writing. + * are encountered during writing. Writes may be flushed at + * any time using {@link #flush}. + * @see DatabaseBuilder for more flexible Database creation * @usage _general_method_ */ public static Database create(File mdbFile, boolean autoSync) @@ -686,7 +746,9 @@ public class Database * updates. if {@code false}, flushing to disk happens at * the jvm's leisure, which can be much faster, but may * leave the database in an inconsistent state if failures - * are encountered during writing. + * are encountered during writing. Writes may be flushed at + * any time using {@link #flush}. + * @see DatabaseBuilder for more flexible Database creation * @usage _general_method_ */ public static Database create(FileFormat fileFormat, File mdbFile, @@ -708,9 +770,11 @@ public class Database * updates. if {@code false}, flushing to disk happens at * the jvm's leisure, which can be much faster, but may * leave the database in an inconsistent state if failures - * are encountered during writing. + * are encountered during writing. Writes may be flushed at + * any time using {@link #flush}. * @param charset Charset to use, if {@code null}, uses default * @param timeZone TimeZone to use, if {@code null}, uses default + * @see DatabaseBuilder for more flexible Database creation * @usage _intermediate_method_ */ public static Database create(FileFormat fileFormat, File mdbFile, @@ -718,16 +782,63 @@ public class Database TimeZone timeZone) throws IOException { + return create(fileFormat, mdbFile, null, autoSync, charset, timeZone); + } + + /** + * Create a new Database for the given fileFormat + * @param fileFormat version of new database. + * @param mdbFile Location to write the new database to. <b>If this file + * already exists, it will be overwritten.</b> + * @param channel pre-opened FileChannel. if provided explicitly, it will + * not be closed by this Database instance + * @param autoSync whether or not to enable auto-syncing on write. if + * {@code true}, writes will be immediately flushed to disk. + * This leaves the database in a (fairly) consistent state + * on each write, but can be very inefficient for many + * updates. if {@code false}, flushing to disk happens at + * the jvm's leisure, which can be much faster, but may + * leave the database in an inconsistent state if failures + * are encountered during writing. Writes may be flushed at + * any time using {@link #flush}. + * @param charset Charset to use, if {@code null}, uses default + * @param timeZone TimeZone to use, if {@code null}, uses default + * @usage _advanced_method_ + */ + static Database create(FileFormat fileFormat, File mdbFile, + FileChannel channel, boolean autoSync, + Charset charset, TimeZone timeZone) + throws IOException + { if (fileFormat.getJetFormat().READ_ONLY) { throw new IOException("jet format '" + fileFormat.getJetFormat() + "' does not support writing"); } - FileChannel channel = openChannel(mdbFile, false); - channel.truncate(0); - transferFrom(channel, getResourceAsStream(fileFormat._emptyFile)); - channel.force(true); - return new Database(mdbFile, channel, autoSync, fileFormat, charset, - timeZone, null); + boolean closeChannel = false; + if(channel == null) { + channel = openChannel(mdbFile, false); + closeChannel = true; + } + + boolean success = false; + try { + channel.truncate(0); + transferFrom(channel, getResourceAsStream(fileFormat._emptyFile)); + channel.force(true); + Database db = new Database(mdbFile, channel, closeChannel, autoSync, + fileFormat, charset, timeZone, null); + success = true; + return db; + } finally { + if(!success && closeChannel) { + // something blew up, shutdown the channel (quietly) + try { + channel.close(); + } catch(Exception ignored) { + // we don't care + } + } + } } /** @@ -763,46 +874,33 @@ public class Database * updates. if {@code false}, flushing to disk happens at * the jvm's leisure, which can be much faster, but may * leave the database in an inconsistent state if failures - * are encountered during writing. + * are encountered during writing. Writes may be flushed at + * any time using {@link #flush}. * @param fileFormat version of new database (if known) * @param charset Charset to use, if {@code null}, uses default * @param timeZone TimeZone to use, if {@code null}, uses default */ - protected Database(File file, FileChannel channel, boolean autoSync, - FileFormat fileFormat, Charset charset, TimeZone timeZone, - CodecProvider provider) + protected Database(File file, FileChannel channel, boolean closeChannel, + boolean autoSync, FileFormat fileFormat, Charset charset, + TimeZone timeZone, CodecProvider provider) throws IOException { - boolean success = false; - try { - _file = file; - _format = JetFormat.getFormat(channel); - _charset = ((charset == null) ? getDefaultCharset(_format) : charset); - _columnOrder = getDefaultColumnOrder(); - _fileFormat = fileFormat; - _pageChannel = new PageChannel(channel, _format, autoSync); - _timeZone = ((timeZone == null) ? getDefaultTimeZone() : timeZone); - if(provider == null) { - provider = DefaultCodecProvider.INSTANCE; - } - // note, it's slighly sketchy to pass ourselves along partially - // constructed, but only our _format and _pageChannel refs should be - // needed - _pageChannel.initialize(this, provider); - _buffer = _pageChannel.createPageBuffer(); - readSystemCatalog(); - success = true; - - } finally { - if(!success && (channel != null)) { - // something blew up, shutdown the channel (quietly) - try { - channel.close(); - } catch(Exception ignored) { - // we don't care - } - } - } + _file = file; + _format = JetFormat.getFormat(channel); + _charset = ((charset == null) ? getDefaultCharset(_format) : charset); + _columnOrder = getDefaultColumnOrder(); + _fileFormat = fileFormat; + _pageChannel = new PageChannel(channel, closeChannel, _format, autoSync); + _timeZone = ((timeZone == null) ? getDefaultTimeZone() : timeZone); + if(provider == null) { + provider = DefaultCodecProvider.INSTANCE; + } + // note, it's slighly sketchy to pass ourselves along partially + // constructed, but only our _format and _pageChannel refs should be + // needed + _pageChannel.initialize(this, provider); + _buffer = _pageChannel.createPageBuffer(); + readSystemCatalog(); } /** diff --git a/src/java/com/healthmarketscience/jackcess/DatabaseBuilder.java b/src/java/com/healthmarketscience/jackcess/DatabaseBuilder.java new file mode 100644 index 0000000..fa0a394 --- /dev/null +++ b/src/java/com/healthmarketscience/jackcess/DatabaseBuilder.java @@ -0,0 +1,163 @@ +/* +Copyright (c) 2012 James Ahlborn + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA +*/ + +package com.healthmarketscience.jackcess; + +import java.io.File; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; +import java.util.TimeZone; + +/** + * Builder style class for opening/creating a Database. + * + * @author James Ahlborn + */ +public class DatabaseBuilder +{ + /** the file name of the mdb to open/create */ + private File _mdbFile; + /** whether or not to open existing mdb read-only */ + private boolean _readOnly; + /** whether or not to auto-sync writes to the filesystem */ + private boolean _autoSync = Database.DEFAULT_AUTO_SYNC; + /** optional charset for mdbs with unspecified charsets */ + private Charset _charset; + /** optional timezone override for interpreting dates */ + private TimeZone _timeZone; + /** optional CodecProvider for handling encoded mdbs */ + private CodecProvider _codecProvider; + /** FileFormat to use when creating a new mdb */ + private Database.FileFormat _fileFormat; + /** optional pre-opened FileChannel, will _not_ be closed by Database + close */ + private FileChannel _channel; + + public DatabaseBuilder() { + this(null); + } + + public DatabaseBuilder(File mdbFile) { + _mdbFile = mdbFile; + } + + /** + * File containing an existing database for {@link #open} or target file for + * new database for {@link #create} (in which case, <b>tf this file already + * exists, it will be overwritten.</b>) + * @usage _general_method_ + */ + public DatabaseBuilder setFile(File mdbFile) { + _mdbFile = mdbFile; + return this; + } + + /** + * Sets flag which, iff {@code true}, will force opening file in + * read-only mode ({@link #open} only). + * @usage _general_method_ + */ + public DatabaseBuilder setReadOnly(boolean readOnly) { + _readOnly = readOnly; + return this; + } + + /** + * Sets whether or not to enable auto-syncing on write. if {@code true}, + * writes will be immediately flushed to disk. This leaves the database in + * a (fairly) consistent state on each write, but can be very inefficient + * for many updates. if {@code false}, flushing to disk happens at the + * jvm's leisure, which can be much faster, but may leave the database in an + * inconsistent state if failures are encountered during writing. Writes + * may be flushed at any time using {@link Database#flush}. + * @usage _intermediate_method_ + */ + public DatabaseBuilder setAutoSync(boolean autoSync) { + _autoSync = autoSync; + return this; + } + + /** + * Sets the Charset to use, if {@code null}, uses default. + * @usage _intermediate_method_ + */ + public DatabaseBuilder setCharset(Charset charset) { + _charset = charset; + return this; + } + + /** + * Sets the TimeZone to use for interpreting dates, if {@code null}, uses + * default + * @usage _intermediate_method_ + */ + public DatabaseBuilder setTimeZone(TimeZone timeZone) { + _timeZone = timeZone; + return this; + } + + /** + * Sets the CodecProvider for handling page encoding/decoding, may be + * {@code null} if no special encoding is necessary + * @usage _intermediate_method_ + */ + public DatabaseBuilder setCodecProvider(CodecProvider codecProvider) { + _codecProvider = codecProvider; + return this; + } + + /** + * Sets the version of new database ({@link #create} only). + * @usage _general_method_ + */ + public DatabaseBuilder setFileFormat(Database.FileFormat fileFormat) { + _fileFormat = fileFormat; + return this; + } + + /** + * Sets a pre-opened FileChannel. if provided explicitly, <i>it will not be + * closed by the Database instance</i>. This allows ultimate control of + * where the mdb file exists (which may not be on disk, e.g. + * {@link MemFileChannel}). If provided, the File parameter will be + * available from {@link Database#getFile}, but otherwise ignored. + * @usage _advanced_method_ + */ + public DatabaseBuilder setChannel(FileChannel channel) { + _channel = channel; + return this; + } + + /** + * Opens an existingnew Database using the configured information. + */ + public Database open() throws IOException { + return Database.open(_mdbFile, _readOnly, _channel, _autoSync, _charset, + _timeZone, _codecProvider); + } + + /** + * Creates a new Database using the configured information. + */ + public Database create() throws IOException { + return Database.create(_fileFormat, _mdbFile, _channel, _autoSync, _charset, + _timeZone); + } +} diff --git a/src/java/com/healthmarketscience/jackcess/PageChannel.java b/src/java/com/healthmarketscience/jackcess/PageChannel.java index ae30ba7..cd2a03c 100644 --- a/src/java/com/healthmarketscience/jackcess/PageChannel.java +++ b/src/java/com/healthmarketscience/jackcess/PageChannel.java @@ -62,6 +62,8 @@ public class PageChannel implements Channel, Flushable { /** Channel containing the database */ private final FileChannel _channel; + /** whether or not the _channel should be closed by this class */ + private final boolean _closeChannel; /** Format of the database in the channel */ private final JetFormat _format; /** whether or not to force all writes to disk immediately */ @@ -84,10 +86,12 @@ public class PageChannel implements Channel, Flushable { * @param channel Channel containing the database * @param format Format of the database in the channel */ - public PageChannel(FileChannel channel, JetFormat format, boolean autoSync) + public PageChannel(FileChannel channel, boolean closeChannel, + JetFormat format, boolean autoSync) throws IOException { _channel = channel; + _closeChannel = closeChannel; _format = format; _autoSync = autoSync; } @@ -115,6 +119,7 @@ public class PageChannel implements Channel, Flushable { throw new IllegalArgumentException(); } _channel = null; + _closeChannel = false; _format = JetFormat.VERSION_4; _autoSync = false; } @@ -338,7 +343,9 @@ public class PageChannel implements Channel, Flushable { public void close() throws IOException { flush(); - _channel.close(); + if(_closeChannel) { + _channel.close(); + } } public boolean isOpen() { |