]> source.dussan.org Git - jackcess.git/commitdiff
separate Database into internal and external; remove SimpleIndexData; make fk enforce...
authorJames Ahlborn <jtahlborn@yahoo.com>
Sat, 2 Mar 2013 15:09:44 +0000 (15:09 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Sat, 2 Mar 2013 15:09:44 +0000 (15:09 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/jackcess-2@667 f203690c-595d-4dc9-a70b-905162fa7fd2

25 files changed:
TODO.txt
src/java/com/healthmarketscience/jackcess/BigIndexData.java [deleted file]
src/java/com/healthmarketscience/jackcess/ByteUtil.java
src/java/com/healthmarketscience/jackcess/Column.java
src/java/com/healthmarketscience/jackcess/ColumnBuilder.java
src/java/com/healthmarketscience/jackcess/Database.java [deleted file]
src/java/com/healthmarketscience/jackcess/DatabaseBuilder.java
src/java/com/healthmarketscience/jackcess/DatabaseImpl.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/ExportUtil.java
src/java/com/healthmarketscience/jackcess/GeneralIndexCodes.java
src/java/com/healthmarketscience/jackcess/GeneralLegacyIndexCodes.java
src/java/com/healthmarketscience/jackcess/ImportUtil.java
src/java/com/healthmarketscience/jackcess/IndexData.java
src/java/com/healthmarketscience/jackcess/IndexPageCache.java
src/java/com/healthmarketscience/jackcess/JetFormat.java
src/java/com/healthmarketscience/jackcess/MemFileChannel.java
src/java/com/healthmarketscience/jackcess/PageChannel.java
src/java/com/healthmarketscience/jackcess/PropertyMap.java
src/java/com/healthmarketscience/jackcess/PropertyMaps.java
src/java/com/healthmarketscience/jackcess/SimpleIndexData.java [deleted file]
src/java/com/healthmarketscience/jackcess/Table.java
src/java/com/healthmarketscience/jackcess/TableBuilder.java
src/java/com/healthmarketscience/jackcess/TableCreator.java
src/java/com/healthmarketscience/jackcess/UsageMap.java
src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java

index d8472b8a0ddc40ce8f73f159383ddd224396b689..2157a0a9449d3c7064dbf4a7462759c8f6d1cdb2 100644 (file)
--- a/TODO.txt
+++ b/TODO.txt
@@ -20,3 +20,15 @@ Missing pieces:
   * EASY
 - figure out how msaccess manages page/row locks
   * MEDIUM
+
+Refactor goals:
+- simplify public API (separate "internal" and "external" api)
+- separate table creation objects from existing metadata objects
+- remove "simple" index support?
+- remove "table traversal methods" from Table?
+- enable integrity by default?
+- remove import/export methods from Database?
+- move database open/create options to DBBuilder
+- tweak how import filters work to make them more flexible?
+- tweak lookup apis (specify column vs column name)
+- separate classes into more packages (api,builder,util,impl)
diff --git a/src/java/com/healthmarketscience/jackcess/BigIndexData.java b/src/java/com/healthmarketscience/jackcess/BigIndexData.java
deleted file mode 100644 (file)
index c06af26..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
-Copyright (c) 2008 Health Market Science, Inc.
-
-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
-
-You can contact Health Market Science at info@healthmarketscience.com
-or at the following address:
-
-Health Market Science
-2700 Horizon Drive
-Suite 200
-King of Prussia, PA 19406
-*/
-
-package com.healthmarketscience.jackcess;
-
-import java.io.IOException;
-
-
-/**
- * Implementation of an Access table index which supports large indexes.
- * @author James Ahlborn
- */
-public class BigIndexData extends IndexData {
-
-  /** Cache which manages the index pages */
-  private final IndexPageCache _pageCache;
-  
-  public BigIndexData(Table table, int number, int uniqueEntryCount,
-                      int uniqueEntryCountOffset) {
-    super(table, number, uniqueEntryCount, uniqueEntryCountOffset);
-    _pageCache = new IndexPageCache(this);
-  }
-
-  @Override
-  protected void updateImpl() throws IOException {
-    _pageCache.write();
-  }
-
-  @Override
-  protected void readIndexEntries()
-    throws IOException
-  {
-    _pageCache.setRootPageNumber(getRootPageNumber());
-  }
-
-  @Override
-  protected DataPage findDataPage(Entry entry)
-    throws IOException
-  {
-    return _pageCache.findCacheDataPage(entry);
-  }
-
-  @Override
-  protected DataPage getDataPage(int pageNumber)
-    throws IOException
-  {
-    return _pageCache.getCacheDataPage(pageNumber);
-  }
-
-  @Override
-  public String toString() {
-    return super.toString() + "\n" + _pageCache.toString();
-  }
-
-  /**
-   * Used by unit tests to validate the internal status of the index.
-   */
-  void validate() throws IOException {
-    _pageCache.validate();
-  }
-  
-}
index b500268e225c266db29dd92757adc885ae40fdb0..b06f03360f8a459214c2a60c7a4bcd0b98c3e340 100644 (file)
@@ -486,7 +486,7 @@ public final class ByteUtil {
    * Convert the given number of bytes from the given database page to a
    * hexidecimal string for display.
    */
-  public static String toHexString(Database db, int pageNumber, int size)
+  public static String toHexString(DatabaseImpl db, int pageNumber, int size)
     throws IOException
   {
     ByteBuffer buffer = db.getPageChannel().createPageBuffer();
index 57df40e2c2f5edcf41b90b272ddd60a317f6adbc..d6f71c3463e2c336e55fb0e617d0660e8763db6a 100644 (file)
@@ -334,7 +334,7 @@ public class Column implements Comparable<Column> {
   /**
    * @usage _general_method_
    */
-  public Database getDatabase() {       
+  public DatabaseImpl getDatabase() {       
     return getTable().getDatabase();
   }
   
@@ -742,7 +742,7 @@ public class Column implements Comparable<Column> {
     if(getType() == null) {
       throw new IllegalArgumentException("must have type");
     }
-    Database.validateIdentifierName(getName(), format.MAX_COLUMN_NAME_LENGTH,
+    DatabaseImpl.validateIdentifierName(getName(), format.MAX_COLUMN_NAME_LENGTH,
                                     "column");
 
     if(getType().isUnsupported()) {
index befff676371718828e96b9bf438f3ecab1bb4c62..c09ec97536e885fbc17347360745ec92a4f1765e 100644 (file)
@@ -161,7 +161,7 @@ public class ColumnBuilder {
    */
   public ColumnBuilder escapeName()
   {
-    _name = Database.escapeIdentifier(_name);
+    _name = DatabaseImpl.escapeIdentifier(_name);
     return this;
   }
 
diff --git a/src/java/com/healthmarketscience/jackcess/Database.java b/src/java/com/healthmarketscience/jackcess/Database.java
deleted file mode 100644 (file)
index 2867587..0000000
+++ /dev/null
@@ -1,2735 +0,0 @@
-/*
-Copyright (c) 2005 Health Market Science, Inc.
-
-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
-
-You can contact Health Market Science at info@healthmarketscience.com
-or at the following address:
-
-Health Market Science
-2700 Horizon Drive
-Suite 200
-King of Prussia, PA 19406
-*/
-
-package com.healthmarketscience.jackcess;
-
-import java.io.BufferedReader;
-import java.io.Closeable;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.Flushable;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.RandomAccessFile;
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.WeakReference;
-import java.nio.ByteBuffer;
-import java.nio.channels.Channels;
-import java.nio.channels.FileChannel;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.charset.Charset;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.ConcurrentModificationException;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Set;
-import java.util.TimeZone;
-import java.util.TreeSet;
-
-import com.healthmarketscience.jackcess.query.Query;
-import org.apache.commons.lang.builder.ToStringBuilder;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-/**
- * An Access database.
- * <p>
- * There is optional support for large indexes (enabled by default).  This
- * optional support can be disabled via a few different means:
- * <ul>
- * <li>Setting the system property {@value #USE_BIG_INDEX_PROPERTY} to
- *     {@code "false"} will disable "large" index support across the jvm</li>
- * <li>Calling {@link #setUseBigIndex} on a Database instance will override
- *     any system property setting for "large" index support for all tables
- *     subsequently created from that instance</li>
- * <li>Calling {@link #getTable(String,boolean)} can selectively
- *     enable/disable "large" index support on a per-table basis (overriding
- *     any Database or system property setting)</li>
- * </ul>
- *
- * @author Tim McCune
- * @usage _general_class_
- */
-public class Database
-  implements Iterable<Table>, Closeable, Flushable
-{
-  
-  private static final Log LOG = LogFactory.getLog(Database.class);
-
-  /** this is the default "userId" used if we cannot find existing info.  this
-      seems to be some standard "Admin" userId for access files */
-  private static final byte[] SYS_DEFAULT_SID = new byte[2];
-  static {
-    SYS_DEFAULT_SID[0] = (byte) 0xA6;
-    SYS_DEFAULT_SID[1] = (byte) 0x33;
-  }
-
-  /** default value for the auto-sync value ({@code true}).  this is slower,
-   *  but leaves more chance of a useable database in the face of failures.
-   * @usage _general_field_
-   */
-  public static final boolean DEFAULT_AUTO_SYNC = true;
-
-  /** the default value for the resource path used to load classpath
-   *  resources.
-   * @usage _general_field_
-   */
-  public static final String DEFAULT_RESOURCE_PATH = 
-    "com/healthmarketscience/jackcess/";
-
-  /**
-   * the default sort order for table columns.
-   * @usage _intermediate_field_
-   */
-  public static final Table.ColumnOrder DEFAULT_COLUMN_ORDER = 
-    Table.ColumnOrder.DATA;
-
-  /** (boolean) system property which can be used to disable the default big
-   *  index support.
-   * @usage _general_field_
-   */
-  public static final String USE_BIG_INDEX_PROPERTY =
-    "com.healthmarketscience.jackcess.bigIndex";
-
-  /** system property which can be used to set the default TimeZone used for
-   *  date calculations.
-   * @usage _general_field_
-   */
-  public static final String TIMEZONE_PROPERTY =
-    "com.healthmarketscience.jackcess.timeZone";
-
-  /** system property prefix which can be used to set the default Charset
-   *  used for text data (full property includes the JetFormat version).
-   * @usage _general_field_
-   */
-  public static final String CHARSET_PROPERTY_PREFIX =
-    "com.healthmarketscience.jackcess.charset.";
-
-  /** system property which can be used to set the path from which classpath
-   *  resources are loaded (must end with a "/" if non-empty).  Default value
-   *  is {@link #DEFAULT_RESOURCE_PATH} if unspecified.
-   * @usage _general_field_
-   */
-  public static final String RESOURCE_PATH_PROPERTY = 
-    "com.healthmarketscience.jackcess.resourcePath";
-
-  /** (boolean) system property which can be used to indicate that the current
-   *  vm has a poor nio implementation (specifically for
-   *  FileChannel.transferFrom)
-   * @usage _intermediate_field_
-   */
-  public static final String BROKEN_NIO_PROPERTY = 
-    "com.healthmarketscience.jackcess.brokenNio";
-
-  /** system property which can be used to set the default sort order for
-   *  table columns.  Value should be one {@link Table.ColumnOrder} enum
-   *  values.
-   * @usage _intermediate_field_
-   */
-  public static final String COLUMN_ORDER_PROPERTY = 
-    "com.healthmarketscience.jackcess.columnOrder";
-
-  /** system property which can be used to set the default enforcement of
-   * foreign-key relationships.  Defaults to {@code false}.
-   * @usage _general_field_
-   */
-  public static final String FK_ENFORCE_PROPERTY = 
-    "com.healthmarketscience.jackcess.enforceForeignKeys";
-
-  /**
-   * default error handler used if none provided (just rethrows exception)
-   * @usage _general_field_
-   */
-  public static final ErrorHandler DEFAULT_ERROR_HANDLER = new ErrorHandler() {
-      public Object handleRowError(Column column,
-                                   byte[] columnData,
-                                   Table.RowState rowState,
-                                   Exception error)
-        throws IOException
-      {
-        // really can only be RuntimeException or IOException
-        if(error instanceof IOException) {
-          throw (IOException)error;
-        }
-        throw (RuntimeException)error;
-      }
-    };
-
-  /**
-   * default link resolver used if none provided
-   * @usage _general_field_
-   */
-  public static final LinkResolver DEFAULT_LINK_RESOLVER = new LinkResolver() {
-      public Database resolveLinkedDatabase(Database linkerDb, 
-                                            String linkeeFileName)
-        throws IOException
-      {
-        return Database.open(new File(linkeeFileName));
-      }
-    };
-
-  /** the resource path to be used when loading classpath resources */
-  static final String RESOURCE_PATH = 
-    System.getProperty(RESOURCE_PATH_PROPERTY, DEFAULT_RESOURCE_PATH);
-
-  /** whether or not this jvm has "broken" nio support */
-  static final boolean BROKEN_NIO = Boolean.TRUE.toString().equalsIgnoreCase(
-      System.getProperty(BROKEN_NIO_PROPERTY));
-  
-  /** System catalog always lives on page 2 */
-  private static final int PAGE_SYSTEM_CATALOG = 2;
-  /** Name of the system catalog */
-  private static final String TABLE_SYSTEM_CATALOG = "MSysObjects";
-
-  /** this is the access control bit field for created tables.  the value used
-      is equivalent to full access (Visual Basic DAO PermissionEnum constant:
-      dbSecFullAccess) */
-  private static final Integer SYS_FULL_ACCESS_ACM = 1048575;
-
-  /** ACE table column name of the actual access control entry */
-  private static final String ACE_COL_ACM = "ACM";
-  /** ACE table column name of the inheritable attributes flag */
-  private static final String ACE_COL_F_INHERITABLE = "FInheritable";
-  /** ACE table column name of the relevant objectId */
-  private static final String ACE_COL_OBJECT_ID = "ObjectId";
-  /** ACE table column name of the relevant userId */
-  private static final String ACE_COL_SID = "SID";
-
-  /** Relationship table column name of the column count */
-  private static final String REL_COL_COLUMN_COUNT = "ccolumn";
-  /** Relationship table column name of the flags */
-  private static final String REL_COL_FLAGS = "grbit";
-  /** Relationship table column name of the index of the columns */
-  private static final String REL_COL_COLUMN_INDEX = "icolumn";
-  /** Relationship table column name of the "to" column name */
-  private static final String REL_COL_TO_COLUMN = "szColumn";
-  /** Relationship table column name of the "to" table name */
-  private static final String REL_COL_TO_TABLE = "szObject";
-  /** Relationship table column name of the "from" column name */
-  private static final String REL_COL_FROM_COLUMN = "szReferencedColumn";
-  /** Relationship table column name of the "from" table name */
-  private static final String REL_COL_FROM_TABLE = "szReferencedObject";
-  /** Relationship table column name of the relationship */
-  private static final String REL_COL_NAME = "szRelationship";
-  
-  /** System catalog column name of the page on which system object definitions
-      are stored */
-  private static final String CAT_COL_ID = "Id";
-  /** System catalog column name of the name of a system object */
-  private static final String CAT_COL_NAME = "Name";
-  private static final String CAT_COL_OWNER = "Owner";
-  /** System catalog column name of a system object's parent's id */
-  private static final String CAT_COL_PARENT_ID = "ParentId";
-  /** System catalog column name of the type of a system object */
-  private static final String CAT_COL_TYPE = "Type";
-  /** System catalog column name of the date a system object was created */
-  private static final String CAT_COL_DATE_CREATE = "DateCreate";
-  /** System catalog column name of the date a system object was updated */
-  private static final String CAT_COL_DATE_UPDATE = "DateUpdate";
-  /** System catalog column name of the flags column */
-  private static final String CAT_COL_FLAGS = "Flags";
-  /** System catalog column name of the properties column */
-  private static final String CAT_COL_PROPS = "LvProp";
-  /** System catalog column name of the remote database */
-  private static final String CAT_COL_DATABASE = "Database";
-  /** System catalog column name of the remote table name */
-  private static final String CAT_COL_FOREIGN_NAME = "ForeignName";
-
-  /** top-level parentid for a database */
-  private static final int DB_PARENT_ID = 0xF000000;
-
-  /** the maximum size of any of the included "empty db" resources */
-  private static final long MAX_EMPTYDB_SIZE = 350000L;
-
-  /** this object is a "system" object */
-  static final int SYSTEM_OBJECT_FLAG = 0x80000000;
-  /** this object is another type of "system" object */
-  static final int ALT_SYSTEM_OBJECT_FLAG = 0x02;
-  /** this object is hidden */
-  static final int HIDDEN_OBJECT_FLAG = 0x08;
-  /** all flags which seem to indicate some type of system object */
-  static final int SYSTEM_OBJECT_FLAGS = 
-    SYSTEM_OBJECT_FLAG | ALT_SYSTEM_OBJECT_FLAG;
-
-  /** read-only channel access mode */
-  static final String RO_CHANNEL_MODE = "r";
-  /** read/write channel access mode */
-  static final String RW_CHANNEL_MODE = "rw";
-
-  /**
-   * Enum which indicates which version of Access created the database.
-   * @usage _general_class_
-   */
-  public static enum FileFormat {
-
-    V1997(null, JetFormat.VERSION_3),
-    V2000(RESOURCE_PATH + "empty.mdb", JetFormat.VERSION_4),
-    V2003(RESOURCE_PATH + "empty2003.mdb", JetFormat.VERSION_4),
-    V2007(RESOURCE_PATH + "empty2007.accdb", JetFormat.VERSION_12, ".accdb"),
-    V2010(RESOURCE_PATH + "empty2010.accdb", JetFormat.VERSION_14, ".accdb"),
-    MSISAM(null, JetFormat.VERSION_MSISAM, ".mny");
-
-    private final String _emptyFile;
-    private final JetFormat _format;
-    private final String _ext;
-
-    private FileFormat(String emptyDBFile, JetFormat jetFormat) {
-      this(emptyDBFile, jetFormat, ".mdb");
-    }
-
-    private FileFormat(String emptyDBFile, JetFormat jetFormat, String ext) {
-      _emptyFile = emptyDBFile;
-      _format = jetFormat;
-      _ext = ext;
-    }
-
-    public JetFormat getJetFormat() { return _format; }
-
-    public String getFileExtension() { return _ext; }
-
-    @Override
-    public String toString() { return name() + ", jetFormat: " + getJetFormat(); }
-  }
-
-  /** Prefix for column or table names that are reserved words */
-  private static final String ESCAPE_PREFIX = "x";
-  /** Name of the system object that is the parent of all tables */
-  private static final String SYSTEM_OBJECT_NAME_TABLES = "Tables";
-  /** Name of the system object that is the parent of all databases */
-  private static final String SYSTEM_OBJECT_NAME_DATABASES = "Databases";
-  /** Name of the system object that is the parent of all relationships */
-  private static final String SYSTEM_OBJECT_NAME_RELATIONSHIPS = 
-    "Relationships";
-  /** Name of the table that contains system access control entries */
-  private static final String TABLE_SYSTEM_ACES = "MSysACEs";
-  /** Name of the table that contains table relationships */
-  private static final String TABLE_SYSTEM_RELATIONSHIPS = "MSysRelationships";
-  /** Name of the table that contains queries */
-  private static final String TABLE_SYSTEM_QUERIES = "MSysQueries";
-  /** Name of the table that contains complex type information */
-  private static final String TABLE_SYSTEM_COMPLEX_COLS = "MSysComplexColumns";
-  /** Name of the main database properties object */
-  private static final String OBJECT_NAME_DB_PROPS = "MSysDb";
-  /** Name of the summary properties object */
-  private static final String OBJECT_NAME_SUMMARY_PROPS = "SummaryInfo";
-  /** Name of the user-defined properties object */
-  private static final String OBJECT_NAME_USERDEF_PROPS = "UserDefined";
-  /** System object type for table definitions */
-  static final Short TYPE_TABLE = 1;
-  /** System object type for query definitions */
-  private static final Short TYPE_QUERY = 5;
-  /** System object type for linked table definitions */
-  private static final Short TYPE_LINKED_TABLE = 6;
-
-  /** max number of table lookups to cache */
-  private static final int MAX_CACHED_LOOKUP_TABLES = 50;
-
-  /** the columns to read when reading system catalog normally */
-  private static Collection<String> SYSTEM_CATALOG_COLUMNS =
-    new HashSet<String>(Arrays.asList(CAT_COL_NAME, CAT_COL_TYPE, CAT_COL_ID,
-                                      CAT_COL_FLAGS, CAT_COL_DATABASE, 
-                                      CAT_COL_FOREIGN_NAME));
-  /** the columns to read when finding table names */
-  private static Collection<String> SYSTEM_CATALOG_TABLE_NAME_COLUMNS =
-    new HashSet<String>(Arrays.asList(CAT_COL_NAME, CAT_COL_TYPE, CAT_COL_ID, 
-                                      CAT_COL_FLAGS, CAT_COL_PARENT_ID));
-  /** the columns to read when getting object propertyes */
-  private static Collection<String> SYSTEM_CATALOG_PROPS_COLUMNS =
-    new HashSet<String>(Arrays.asList(CAT_COL_ID, CAT_COL_PROPS));
-  
-  
-  /**
-   * All of the reserved words in Access that should be escaped when creating
-   * table or column names
-   */
-  private static final Set<String> RESERVED_WORDS = new HashSet<String>();
-  static {
-    //Yup, there's a lot.
-    RESERVED_WORDS.addAll(Arrays.asList(
-       "add", "all", "alphanumeric", "alter", "and", "any", "application", "as",
-       "asc", "assistant", "autoincrement", "avg", "between", "binary", "bit",
-       "boolean", "by", "byte", "char", "character", "column", "compactdatabase",
-       "constraint", "container", "count", "counter", "create", "createdatabase",
-       "createfield", "creategroup", "createindex", "createobject", "createproperty",
-       "createrelation", "createtabledef", "createuser", "createworkspace",
-       "currency", "currentuser", "database", "date", "datetime", "delete",
-       "desc", "description", "disallow", "distinct", "distinctrow", "document",
-       "double", "drop", "echo", "else", "end", "eqv", "error", "exists", "exit",
-       "false", "field", "fields", "fillcache", "float", "float4", "float8",
-       "foreign", "form", "forms", "from", "full", "function", "general",
-       "getobject", "getoption", "gotopage", "group", "group by", "guid", "having",
-       "idle", "ieeedouble", "ieeesingle", "if", "ignore", "imp", "in", "index",
-       "indexes", "inner", "insert", "inserttext", "int", "integer", "integer1",
-       "integer2", "integer4", "into", "is", "join", "key", "lastmodified", "left",
-       "level", "like", "logical", "logical1", "long", "longbinary", "longtext",
-       "macro", "match", "max", "min", "mod", "memo", "module", "money", "move",
-       "name", "newpassword", "no", "not", "null", "number", "numeric", "object",
-       "oleobject", "off", "on", "openrecordset", "option", "or", "order", "outer",
-       "owneraccess", "parameter", "parameters", "partial", "percent", "pivot",
-       "primary", "procedure", "property", "queries", "query", "quit", "real",
-       "recalc", "recordset", "references", "refresh", "refreshlink",
-       "registerdatabase", "relation", "repaint", "repairdatabase", "report",
-       "reports", "requery", "right", "screen", "section", "select", "set",
-       "setfocus", "setoption", "short", "single", "smallint", "some", "sql",
-       "stdev", "stdevp", "string", "sum", "table", "tabledef", "tabledefs",
-       "tableid", "text", "time", "timestamp", "top", "transform", "true", "type",
-       "union", "unique", "update", "user", "value", "values", "var", "varp",
-       "varbinary", "varchar", "where", "with", "workspace", "xor", "year", "yes",
-       "yesno"
-    ));
-  }
-
-  /** the File of the database */
-  private final File _file;
-  /** Buffer to hold database pages */
-  private ByteBuffer _buffer;
-  /** ID of the Tables system object */
-  private Integer _tableParentId;
-  /** Format that the containing database is in */
-  private final JetFormat _format;
-  /**
-   * Cache map of UPPERCASE table names to page numbers containing their
-   * definition and their stored table name (max size
-   * MAX_CACHED_LOOKUP_TABLES).
-   */
-  private final Map<String, TableInfo> _tableLookup =
-    new LinkedHashMap<String, TableInfo>() {
-    private static final long serialVersionUID = 0L;
-    @Override
-    protected boolean removeEldestEntry(Map.Entry<String, TableInfo> e) {
-      return(size() > MAX_CACHED_LOOKUP_TABLES);
-    }
-  };
-  /** set of table names as stored in the mdb file, created on demand */
-  private Set<String> _tableNames;
-  /** Reads and writes database pages */
-  private final PageChannel _pageChannel;
-  /** System catalog table */
-  private Table _systemCatalog;
-  /** utility table finder */
-  private TableFinder _tableFinder;
-  /** System access control entries table (initialized on first use) */
-  private Table _accessControlEntries;
-  /** System relationships table (initialized on first use) */
-  private Table _relationships;
-  /** System queries table (initialized on first use) */
-  private Table _queries;
-  /** System complex columns table (initialized on first use) */
-  private Table _complexCols;
-  /** SIDs to use for the ACEs added for new tables */
-  private final List<byte[]> _newTableSIDs = new ArrayList<byte[]>();
-  /** "big index support" is optional, but enabled by default */
-  private Boolean _useBigIndex;
-  /** optional error handler to use when row errors are encountered */
-  private ErrorHandler _dbErrorHandler;
-  /** the file format of the database */
-  private FileFormat _fileFormat;
-  /** charset to use when handling text */
-  private Charset _charset;
-  /** timezone to use when handling dates */
-  private TimeZone _timeZone;
-  /** language sort order to be used for textual columns */
-  private Column.SortOrder _defaultSortOrder;
-  /** default code page to be used for textual columns (in some dbs) */
-  private Short _defaultCodePage;
-  /** the ordering used for table columns */
-  private Table.ColumnOrder _columnOrder;
-  /** whether or not enforcement of foreign-keys is enabled */
-  private boolean _enforceForeignKeys;
-  /** cache of in-use tables */
-  private final TableCache _tableCache = new TableCache();
-  /** handler for reading/writing properteies */
-  private PropertyMaps.Handler _propsHandler;
-  /** ID of the Databases system object */
-  private Integer _dbParentId;
-  /** core database properties */
-  private PropertyMaps _dbPropMaps;
-  /** summary properties */
-  private PropertyMaps _summaryPropMaps;
-  /** user-defined properties */
-  private PropertyMaps _userDefPropMaps;
-  /** linked table resolver */
-  private LinkResolver _linkResolver;
-  /** any linked databases which have been opened */
-  private Map<String,Database> _linkedDbs;
-  /** shared state used when enforcing foreign keys */
-  private final FKEnforcer.SharedState _fkEnforcerSharedState =
-    FKEnforcer.initSharedState();
-  /** Calendar for use interpreting dates/times in Columns */
-  private Calendar _calendar;
-
-  /**
-   * Open an existing Database.  If the existing file is not writeable, the
-   * file will be opened read-only.  Auto-syncing is enabled for the returned
-   * Database.
-   * <p>
-   * Equivalent to:
-   * {@code  open(mdbFile, false);}
-   * 
-   * @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 {
-    return open(mdbFile, false);
-  }
-  
-  /**
-   * 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.
-   * 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}, 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)
-    throws IOException
-  {
-    return open(mdbFile, readOnly, DEFAULT_AUTO_SYNC);
-  }
-  
-  /**
-   * 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 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}.
-   * @see DatabaseBuilder for more flexible Database opening
-   * @usage _general_method_
-   */
-  public static Database open(File mdbFile, boolean readOnly, boolean autoSync)
-    throws IOException
-  {    
-    return open(mdbFile, readOnly, autoSync, null, null);
-  }
-
-  /**
-   * 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 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
-   * @see DatabaseBuilder for more flexible Database opening
-   * @usage _intermediate_method_
-   */
-  public static Database open(File mdbFile, boolean readOnly, boolean autoSync,
-                              Charset charset, TimeZone timeZone)
-    throws IOException
-  {    
-    return open(mdbFile, readOnly, autoSync, charset, timeZone, null);
-  }
-
-  /**
-   * 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 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
-   * @see DatabaseBuilder for more flexible Database opening
-   * @usage _intermediate_method_
-   */
-  public static Database open(File mdbFile, boolean readOnly, boolean autoSync,
-                              Charset charset, TimeZone timeZone, 
-                              CodecProvider provider)
-    throws IOException
-  {
-    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;
-    }
-
-    boolean success = false;
-    try {
-
-      if(!readOnly) {
-
-        // verify that format supports writing
-        JetFormat jetFormat = JetFormat.getFormat(channel);
-
-        if(jetFormat.READ_ONLY) {
-          throw new IOException("jet format '" + jetFormat +
-                                "' does not support writing");
-        }
-      }
-
-      Database db = new Database(mdbFile, channel, closeChannel, autoSync, 
-                                 null, charset, timeZone, provider);
-      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
-        }
-      }
-    }
-  }
-  
-  /**
-   * Create a new Access 2000 Database 
-   * <p>
-   * Equivalent to:
-   * {@code  create(FileFormat.V2000, mdbFile, DEFAULT_AUTO_SYNC);}
-   * 
-   * @param mdbFile Location to write the new database to.  <b>If this file
-   *    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 {
-    return create(mdbFile, DEFAULT_AUTO_SYNC);
-  }
-  
-  /**
-   * Create a new Database for the given fileFormat
-   * <p>
-   * Equivalent to:
-   * {@code  create(fileFormat, mdbFile, DEFAULT_AUTO_SYNC);}
-   * 
-   * @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>
-   *
-   * @see #create(File,boolean)
-   * @see DatabaseBuilder for more flexible Database creation
-   * @usage _general_method_
-   */
-  public static Database create(FileFormat fileFormat, File mdbFile) 
-    throws IOException 
-  {
-    return create(fileFormat, mdbFile, DEFAULT_AUTO_SYNC);
-  }
-  
-  /**
-   * Create a new Access 2000 Database
-   * <p>
-   * Equivalent to:
-   * {@code  create(FileFormat.V2000, mdbFile, DEFAULT_AUTO_SYNC);}
-   * 
-   * @param mdbFile Location to write the new database to.  <b>If this file
-   *    already exists, it will be overwritten.</b>
-   * @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}.
-   * @see DatabaseBuilder for more flexible Database creation
-   * @usage _general_method_
-   */
-  public static Database create(File mdbFile, boolean autoSync)
-    throws IOException
-  {
-    return create(FileFormat.V2000, mdbFile, autoSync);
-  }
-
-  /**
-   * 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 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}.
-   * @see DatabaseBuilder for more flexible Database creation
-   * @usage _general_method_
-   */
-  public static Database create(FileFormat fileFormat, File mdbFile, 
-                                boolean autoSync)
-    throws IOException
-  {
-    return create(fileFormat, mdbFile, autoSync, null, null);
-  }
-
-  /**
-   * 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 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
-   * @see DatabaseBuilder for more flexible Database creation
-   * @usage _intermediate_method_
-   */
-  public static Database create(FileFormat fileFormat, File mdbFile, 
-                                boolean autoSync, Charset charset,
-                                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");
-    }
-
-    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
-        }
-      }
-    }
-  }
-
-  /**
-   * Package visible only to support unit tests via DatabaseTest.openChannel().
-   * @param mdbFile file to open
-   * @param readOnly true if read-only
-   * @return a FileChannel on the given file.
-   * @exception FileNotFoundException
-   *            if the mode is <tt>"r"</tt> but the given file object does
-   *            not denote an existing regular file, or if the mode begins
-   *            with <tt>"rw"</tt> but the given file object does not denote
-   *            an existing, writable regular file and a new regular file of
-   *            that name cannot be created, or if some other error occurs
-   *            while opening or creating the file
-   */
-  static FileChannel openChannel(final File mdbFile, final boolean readOnly)
-    throws FileNotFoundException
-  {
-    final String mode = (readOnly ? RO_CHANNEL_MODE : RW_CHANNEL_MODE);
-    return new RandomAccessFile(mdbFile, mode).getChannel();
-  }
-  
-  /**
-   * Create a new database by reading it in from a FileChannel.
-   * @param file the File to which the channel is connected 
-   * @param channel File channel of the database.  This needs to be a
-   *    FileChannel instead of a ReadableByteChannel because we need to
-   *    randomly jump around to various points in the file.
-   * @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 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 closeChannel,
-                     boolean autoSync, FileFormat fileFormat, Charset charset,
-                     TimeZone timeZone, CodecProvider provider)
-    throws IOException
-  {
-    _file = file;
-    _format = JetFormat.getFormat(channel);
-    _charset = ((charset == null) ? getDefaultCharset(_format) : charset);
-    _columnOrder = getDefaultColumnOrder();
-    _enforceForeignKeys = getDefaultEnforceForeignKeys();
-    _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();
-  }
-
-  /**
-   * Returns the File underlying this Database
-   */
-  public File getFile() {
-    return _file;
-  }
-
-  /**
-   * @usage _advanced_method_
-   */
-  public PageChannel getPageChannel() {
-    return _pageChannel;
-  }
-
-  /**
-   * @usage _advanced_method_
-   */
-  public JetFormat getFormat() {
-    return _format;
-  }
-  
-  /**
-   * @return The system catalog table
-   * @usage _advanced_method_
-   */
-  public Table getSystemCatalog() {
-    return _systemCatalog;
-  }
-  
-  /**
-   * @return The system Access Control Entries table (loaded on demand)
-   * @usage _advanced_method_
-   */
-  public Table getAccessControlEntries() throws IOException {
-    if(_accessControlEntries == null) {
-      _accessControlEntries = getSystemTable(TABLE_SYSTEM_ACES);
-      if(_accessControlEntries == null) {
-        throw new IOException("Could not find system table " +
-                              TABLE_SYSTEM_ACES);
-      }
-
-    }
-    return _accessControlEntries;
-  }
-
-  /**
-   * @return the complex column system table (loaded on demand)
-   * @usage _advanced_method_
-   */
-  public Table getSystemComplexColumns() throws IOException {
-    if(_complexCols == null) {
-      _complexCols = getSystemTable(TABLE_SYSTEM_COMPLEX_COLS);
-      if(_complexCols == null) {
-        throw new IOException("Could not find system table " +
-                              TABLE_SYSTEM_COMPLEX_COLS);
-      }
-    }
-    return _complexCols;
-  }
-    
-  /**
-   * Whether or not big index support is enabled for tables.
-   * @usage _advanced_method_
-   */
-  public boolean doUseBigIndex() {
-    return (_useBigIndex != null ? _useBigIndex : true);
-  }
-
-  /**
-   * Set whether or not big index support is enabled for tables.
-   * @usage _intermediate_method_
-   */
-  public void setUseBigIndex(boolean useBigIndex) {
-    _useBigIndex = useBigIndex;
-  }
-
-  /**
-   * Gets the currently configured ErrorHandler (always non-{@code null}).
-   * This will be used to handle all errors unless overridden at the Table or
-   * Cursor level.
-   * @usage _intermediate_method_
-   */
-  public ErrorHandler getErrorHandler() {
-    return((_dbErrorHandler != null) ? _dbErrorHandler :
-           DEFAULT_ERROR_HANDLER);
-  }
-
-  /**
-   * Sets a new ErrorHandler.  If {@code null}, resets to the
-   * {@link #DEFAULT_ERROR_HANDLER}.
-   * @usage _intermediate_method_
-   */
-  public void setErrorHandler(ErrorHandler newErrorHandler) {
-    _dbErrorHandler = newErrorHandler;
-  }    
-
-  /**
-   * Gets the currently configured LinkResolver (always non-{@code null}).
-   * This will be used to handle all linked database loading.
-   * @usage _intermediate_method_
-   */
-  public LinkResolver getLinkResolver() {
-    return((_linkResolver != null) ? _linkResolver : DEFAULT_LINK_RESOLVER);
-  }
-
-  /**
-   * Sets a new LinkResolver.  If {@code null}, resets to the
-   * {@link #DEFAULT_LINK_RESOLVER}.
-   * @usage _intermediate_method_
-   */
-  public void setLinkResolver(LinkResolver newLinkResolver) {
-    _linkResolver = newLinkResolver;
-  }    
-
-  /**
-   * Returns an unmodifiable view of the currently loaded linked databases,
-   * mapped from the linked database file name to the linked database.  This
-   * information may be useful for implementing a LinkResolver.
-   * @usage _intermediate_method_
-   */
-  public Map<String,Database> getLinkedDatabases() {
-    return ((_linkedDbs == null) ? Collections.<String,Database>emptyMap() : 
-            Collections.unmodifiableMap(_linkedDbs));
-  }
-
-  /**
-   * Gets currently configured TimeZone (always non-{@code null}).
-   * @usage _intermediate_method_
-   */
-  public TimeZone getTimeZone() {
-    return _timeZone;
-  }
-
-  /**
-   * Sets a new TimeZone.  If {@code null}, resets to the value returned by
-   * {@link #getDefaultTimeZone}.
-   * @usage _intermediate_method_
-   */
-  public void setTimeZone(TimeZone newTimeZone) {
-    if(newTimeZone == null) {
-      newTimeZone = getDefaultTimeZone();
-    }
-    _timeZone = newTimeZone;
-    // clear cached calendar when timezone is changed
-    _calendar = null;
-  }    
-
-  /**
-   * Gets currently configured Charset (always non-{@code null}).
-   * @usage _intermediate_method_
-   */
-  public Charset getCharset()
-  {
-    return _charset;
-  }
-
-  /**
-   * Sets a new Charset.  If {@code null}, resets to the value returned by
-   * {@link #getDefaultCharset}.
-   * @usage _intermediate_method_
-   */
-  public void setCharset(Charset newCharset) {
-    if(newCharset == null) {
-      newCharset = getDefaultCharset(getFormat());
-    }
-    _charset = newCharset;
-  }
-
-  /**
-   * Gets currently configured {@link Table.ColumnOrder} (always non-{@code
-   * null}).
-   * @usage _intermediate_method_
-   */
-  public Table.ColumnOrder getColumnOrder() {
-    return _columnOrder;
-  }
-
-  /**
-   * Sets a new Table.ColumnOrder.  If {@code null}, resets to the value
-   * returned by {@link #getDefaultColumnOrder}.
-   * @usage _intermediate_method_
-   */
-  public void setColumnOrder(Table.ColumnOrder newColumnOrder) {
-    if(newColumnOrder == null) {
-      newColumnOrder = getDefaultColumnOrder();
-    }
-    _columnOrder = newColumnOrder;
-  }
-
-  /**
-   * Gets currently foreign-key enforcement policy.
-   * @usage _intermediate_method_
-   */
-  public boolean isEnforceForeignKeys() {
-    return _enforceForeignKeys;
-  }
-
-  /**
-   * Sets a new foreign-key enforcement policy.  If {@code null}, resets to
-   * the value returned by {@link #isEnforceForeignKeys}.
-   * @usage _intermediate_method_
-   */
-  public void setEnforceForeignKeys(Boolean newEnforceForeignKeys) {
-    if(newEnforceForeignKeys == null) {
-      newEnforceForeignKeys = getDefaultEnforceForeignKeys();
-    }
-    _enforceForeignKeys = newEnforceForeignKeys;
-  }
-
-  /**
-   * @usage _advanced_method_
-   */
-  FKEnforcer.SharedState getFKEnforcerSharedState() {
-    return _fkEnforcerSharedState;
-  }
-
-  /**
-   * @usage _advanced_method_
-   */
-  Calendar getCalendar()
-  {
-    if(_calendar == null) {
-      _calendar = Calendar.getInstance(_timeZone);
-    }
-    return _calendar;
-  }
-
-  /**
-   * @returns the current handler for reading/writing properties, creating if
-   * necessary
-   */
-  private PropertyMaps.Handler getPropsHandler() {
-    if(_propsHandler == null) {
-      _propsHandler = new PropertyMaps.Handler(this);
-    }
-    return _propsHandler;
-  }
-
-  /**
-   * Returns the FileFormat of this database (which may involve inspecting the
-   * database itself).
-   * @throws IllegalStateException if the file format cannot be determined
-   * @usage _general_method_
-   */
-  public FileFormat getFileFormat() throws IOException {
-
-    if(_fileFormat == null) {
-
-      Map<String,Database.FileFormat> possibleFileFormats =
-        getFormat().getPossibleFileFormats();
-
-      if(possibleFileFormats.size() == 1) {
-
-        // single possible format (null key), easy enough
-        _fileFormat = possibleFileFormats.get(null);
-
-      } else {
-
-        // need to check the "AccessVersion" property
-        String accessVersion = (String)getDatabaseProperties().getValue(
-            PropertyMap.ACCESS_VERSION_PROP);
-        
-        _fileFormat = possibleFileFormats.get(accessVersion);
-        
-        if(_fileFormat == null) {
-          throw new IllegalStateException("Could not determine FileFormat");
-        }
-      }
-    }
-    return _fileFormat;
-  }
-
-  /**
-   * @return a (possibly cached) page ByteBuffer for internal use.  the
-   *         returned buffer should be released using
-   *         {@link #releaseSharedBuffer} when no longer in use
-   */
-  private ByteBuffer takeSharedBuffer() {
-    // we try to re-use a single shared _buffer, but occassionally, it may be
-    // needed by multiple operations at the same time (e.g. loading a
-    // secondary table while loading a primary table).  this method ensures
-    // that we don't corrupt the _buffer, but instead force the second caller
-    // to use a new buffer.
-    if(_buffer != null) {
-      ByteBuffer curBuffer = _buffer;
-      _buffer = null;
-      return curBuffer;
-    }
-    return _pageChannel.createPageBuffer();
-  }
-
-  /**
-   * Relinquishes use of a page ByteBuffer returned by
-   * {@link #takeSharedBuffer}.
-   */
-  private void releaseSharedBuffer(ByteBuffer buffer) {
-    // we always stuff the returned buffer back into _buffer.  it doesn't
-    // really matter if multiple values over-write, at the end of the day, we
-    // just need one shared buffer
-    _buffer = buffer;
-  }
-  
-  /**
-   * @return the currently configured database default language sort order for
-   *         textual columns
-   * @usage _intermediate_method_
-   */
-  public Column.SortOrder getDefaultSortOrder() throws IOException {
-
-    if(_defaultSortOrder == null) {
-      initRootPageInfo();
-    }
-    return _defaultSortOrder;
-  }
-
-  /**
-   * @return the currently configured database default code page for textual
-   *         data (may not be relevant to all database versions)
-   * @usage _intermediate_method_
-   */
-  public short getDefaultCodePage() throws IOException {
-
-    if(_defaultCodePage == null) {
-      initRootPageInfo();
-    }
-    return _defaultCodePage;
-  }
-
-  /**
-   * Reads various config info from the db page 0.
-   */
-  private void initRootPageInfo() throws IOException {
-    ByteBuffer buffer = takeSharedBuffer();
-    try {
-      _pageChannel.readPage(buffer, 0);
-      _defaultSortOrder = Column.readSortOrder(
-          buffer, _format.OFFSET_SORT_ORDER, _format);
-      _defaultCodePage = buffer.getShort(_format.OFFSET_CODE_PAGE);
-    } finally {
-      releaseSharedBuffer(buffer);
-    }
-  }
-  
-  /**
-   * @return a PropertyMaps instance decoded from the given bytes (always
-   *         returns non-{@code null} result).
-   * @usage _intermediate_method_
-   */
-  public PropertyMaps readProperties(byte[] propsBytes, int objectId)
-    throws IOException 
-  {
-    return getPropsHandler().read(propsBytes, objectId);
-  }
-  
-  /**
-   * Read the system catalog
-   */
-  private void readSystemCatalog() throws IOException {
-    _systemCatalog = readTable(TABLE_SYSTEM_CATALOG, PAGE_SYSTEM_CATALOG,
-                               SYSTEM_OBJECT_FLAGS, defaultUseBigIndex());
-
-    try {
-      _tableFinder = new DefaultTableFinder(
-          new CursorBuilder(_systemCatalog)
-            .setIndexByColumnNames(CAT_COL_PARENT_ID, CAT_COL_NAME)
-            .setColumnMatcher(CaseInsensitiveColumnMatcher.INSTANCE)
-            .toIndexCursor());
-    } catch(IllegalArgumentException e) {
-      LOG.info("Could not find expected index on table " + 
-               _systemCatalog.getName());
-      // use table scan instead
-      _tableFinder = new FallbackTableFinder(
-          new CursorBuilder(_systemCatalog)
-            .setColumnMatcher(CaseInsensitiveColumnMatcher.INSTANCE)
-            .toCursor());
-    }
-
-    _tableParentId = _tableFinder.findObjectId(DB_PARENT_ID, 
-                                               SYSTEM_OBJECT_NAME_TABLES);
-
-    if(_tableParentId == null) {  
-      throw new IOException("Did not find required parent table id");
-    }
-
-    if (LOG.isDebugEnabled()) {
-      LOG.debug("Finished reading system catalog.  Tables: " +
-                getTableNames());
-    }
-  }
-  
-  /**
-   * @return The names of all of the user tables (String)
-   * @usage _general_method_
-   */
-  public Set<String> getTableNames() throws IOException {
-    if(_tableNames == null) {
-      Set<String> tableNames =
-        new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
-      _tableFinder.getTableNames(tableNames, false);
-      _tableNames = tableNames;
-    }
-    return _tableNames;
-  }
-
-  /**
-   * @return The names of all of the system tables (String).  Note, in order
-   *         to read these tables, you must use {@link #getSystemTable}.
-   *         <i>Extreme care should be taken if modifying these tables
-   *         directly!</i>.
-   * @usage _intermediate_method_
-   */
-  public Set<String> getSystemTableNames() throws IOException {
-    Set<String> sysTableNames =
-      new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
-    _tableFinder.getTableNames(sysTableNames, true);
-    return sysTableNames;
-  }
-
-  /**
-   * @return an unmodifiable Iterator of the user Tables in this Database.
-   * @throws IllegalStateException if an IOException is thrown by one of the
-   *         operations, the actual exception will be contained within
-   * @throws ConcurrentModificationException if a table is added to the
-   *         database while an Iterator is in use.
-   * @usage _general_method_
-   */
-  public Iterator<Table> iterator() {
-    return new TableIterator();
-  }
-
-  /**
-   * @param name Table name
-   * @return The table, or null if it doesn't exist
-   * @usage _general_method_
-   */
-  public Table getTable(String name) throws IOException {
-    return getTable(name, defaultUseBigIndex());
-  }
-  
-  /**
-   * @param name Table name
-   * @param useBigIndex whether or not "big index support" should be enabled
-   *                    for the table (this value will override any other
-   *                    settings)
-   * @return The table, or null if it doesn't exist
-   * @usage _intermediate_method_
-   */
-  public Table getTable(String name, boolean useBigIndex) throws IOException {
-    return getTable(name, false, useBigIndex);
-  }
-
-  /**
-   * @param tableDefPageNumber the page number of a table definition
-   * @return The table, or null if it doesn't exist
-   * @usage _advanced_method_
-   */
-  public Table getTable(int tableDefPageNumber) throws IOException {
-
-    // first, check for existing table
-    Table table = _tableCache.get(tableDefPageNumber);
-    if(table != null) {
-      return table;
-    }
-    
-    // lookup table info from system catalog
-    Map<String,Object> objectRow = _tableFinder.getObjectRow(
-        tableDefPageNumber, SYSTEM_CATALOG_COLUMNS);
-    if(objectRow == null) {
-      return null;
-    }
-
-    String name = (String)objectRow.get(CAT_COL_NAME);
-    int flags = (Integer)objectRow.get(CAT_COL_FLAGS);
-
-    return readTable(name, tableDefPageNumber, flags, defaultUseBigIndex());
-  }
-
-  /**
-   * @param name Table name
-   * @param includeSystemTables whether to consider returning a system table
-   * @param useBigIndex whether or not "big index support" should be enabled
-   *                    for the table (this value will override any other
-   *                    settings)
-   * @return The table, or null if it doesn't exist
-   */
-  private Table getTable(String name, boolean includeSystemTables, 
-                         boolean useBigIndex) 
-    throws IOException 
-  {
-    TableInfo tableInfo = lookupTable(name);
-    
-    if ((tableInfo == null) || (tableInfo.pageNumber == null)) {
-      return null;
-    }
-    if(!includeSystemTables && isSystemObject(tableInfo.flags)) {
-      return null;
-    }
-
-    if(tableInfo.isLinked()) {
-
-      if(_linkedDbs == null) {
-        _linkedDbs = new HashMap<String,Database>();
-      }
-
-      String linkedDbName = ((LinkedTableInfo)tableInfo).linkedDbName;
-      String linkedTableName = ((LinkedTableInfo)tableInfo).linkedTableName;
-      Database linkedDb = _linkedDbs.get(linkedDbName);
-      if(linkedDb == null) {
-        linkedDb = getLinkResolver().resolveLinkedDatabase(this, linkedDbName);
-        _linkedDbs.put(linkedDbName, linkedDb);
-      }
-      
-      return linkedDb.getTable(linkedTableName, includeSystemTables, 
-                               useBigIndex);
-    }
-
-    return readTable(tableInfo.tableName, tableInfo.pageNumber,
-                     tableInfo.flags, useBigIndex);
-  }
-  
-  /**
-   * Create a new table in this database
-   * @param name Name of the table to create
-   * @param columns List of Columns in the table
-   * @usage _general_method_
-   */
-  public void createTable(String name, List<Column> columns)
-    throws IOException
-  {
-    createTable(name, columns, null);
-  }
-
-  /**
-   * Create a new table in this database
-   * @param name Name of the table to create
-   * @param columns List of Columns in the table
-   * @param indexes List of IndexBuilders describing indexes for the table
-   * @usage _general_method_
-   */
-  public void createTable(String name, List<Column> columns,
-                          List<IndexBuilder> indexes)
-    throws IOException
-  {
-    if(lookupTable(name) != null) {
-      throw new IllegalArgumentException(
-          "Cannot create table with name of existing table");
-    }
-
-    new TableCreator(this, name, columns, indexes).createTable();
-  }
-
-  /**
-   * Create a new table in this database
-   * @param name Name of the table to create
-   * @usage _general_method_
-   */
-  public void createLinkedTable(String name, String linkedDbName, 
-                                String linkedTableName)
-    throws IOException
-  {
-    if(lookupTable(name) != null) {
-      throw new IllegalArgumentException(
-          "Cannot create linked table with name of existing table");
-    }
-
-    validateIdentifierName(name, getFormat().MAX_TABLE_NAME_LENGTH, "table");
-    validateIdentifierName(linkedDbName, DataType.MEMO.getMaxSize(), 
-                           "linked database");
-    validateIdentifierName(linkedTableName, getFormat().MAX_TABLE_NAME_LENGTH, 
-                           "linked table");
-
-    int linkedTableId = _tableFinder.getNextFreeSyntheticId();
-
-    addNewTable(name, linkedTableId, TYPE_LINKED_TABLE, linkedDbName, 
-                linkedTableName);
-  }
-
-  /**
-   * Adds a newly created table to the relevant internal database structures.
-   */
-  void addNewTable(String name, int tdefPageNumber, Short type, 
-                   String linkedDbName, String linkedTableName) 
-    throws IOException 
-  {
-    //Add this table to our internal list.
-    addTable(name, Integer.valueOf(tdefPageNumber), type, linkedDbName,
-             linkedTableName);
-    
-    //Add this table to system tables
-    addToSystemCatalog(name, tdefPageNumber, type, linkedDbName, 
-                       linkedTableName);
-    addToAccessControlEntries(tdefPageNumber);
-  }
-
-  /**
-   * Finds all the relationships in the database between the given tables.
-   * @usage _intermediate_method_
-   */
-  public List<Relationship> getRelationships(Table table1, Table table2)
-    throws IOException
-  {
-    // the relationships table does not get loaded until first accessed
-    if(_relationships == null) {
-      _relationships = getSystemTable(TABLE_SYSTEM_RELATIONSHIPS);
-      if(_relationships == null) {
-        throw new IOException("Could not find system relationships table");
-      }
-    }
-
-    int nameCmp = table1.getName().compareTo(table2.getName());
-    if(nameCmp == 0) {
-      throw new IllegalArgumentException("Must provide two different tables");
-    }
-    if(nameCmp > 0) {
-      // we "order" the two tables given so that we will return a collection
-      // of relationships in the same order regardless of whether we are given
-      // (TableFoo, TableBar) or (TableBar, TableFoo).
-      Table tmp = table1;
-      table1 = table2;
-      table2 = tmp;
-    }
-      
-
-    List<Relationship> relationships = new ArrayList<Relationship>();
-    Cursor cursor = createCursorWithOptionalIndex(
-        _relationships, REL_COL_FROM_TABLE, table1.getName());
-    collectRelationships(cursor, table1, table2, relationships);
-    cursor = createCursorWithOptionalIndex(
-        _relationships, REL_COL_TO_TABLE, table1.getName());
-    collectRelationships(cursor, table2, table1, relationships);
-    
-    return relationships;
-  }
-
-  /**
-   * Finds all the queries in the database.
-   * @usage _intermediate_method_
-   */
-  public List<Query> getQueries()
-    throws IOException
-  {
-    // the queries table does not get loaded until first accessed
-    if(_queries == null) {
-      _queries = getSystemTable(TABLE_SYSTEM_QUERIES);
-      if(_queries == null) {
-        throw new IOException("Could not find system queries table");
-      }
-    }
-
-    // find all the queries from the system catalog
-    List<Map<String,Object>> queryInfo = new ArrayList<Map<String,Object>>();
-    Map<Integer,List<Query.Row>> queryRowMap = 
-      new HashMap<Integer,List<Query.Row>>();
-    for(Map<String,Object> row :
-          Cursor.createCursor(_systemCatalog).iterable(SYSTEM_CATALOG_COLUMNS))
-    {
-      String name = (String) row.get(CAT_COL_NAME);
-      if (name != null && TYPE_QUERY.equals(row.get(CAT_COL_TYPE))) {
-        queryInfo.add(row);
-        Integer id = (Integer)row.get(CAT_COL_ID);
-        queryRowMap.put(id, new ArrayList<Query.Row>());
-      }
-    }
-
-    // find all the query rows
-    for(Map<String,Object> row : Cursor.createCursor(_queries)) {
-      Query.Row queryRow = new Query.Row(row);
-      List<Query.Row> queryRows = queryRowMap.get(queryRow.objectId);
-      if(queryRows == null) {
-        LOG.warn("Found rows for query with id " + queryRow.objectId +
-                 " missing from system catalog");
-        continue;
-      }
-      queryRows.add(queryRow);
-    }
-
-    // lastly, generate all the queries
-    List<Query> queries = new ArrayList<Query>();
-    for(Map<String,Object> row : queryInfo) {
-      String name = (String) row.get(CAT_COL_NAME);
-      Integer id = (Integer)row.get(CAT_COL_ID);
-      int flags = (Integer)row.get(CAT_COL_FLAGS);
-      List<Query.Row> queryRows = queryRowMap.get(id);
-      queries.add(Query.create(flags, name, queryRows, id));
-    }
-
-    return queries;
-  }
-
-  /**
-   * Returns a reference to <i>any</i> available table in this access
-   * database, including system tables.
-   * <p>
-   * Warning, this method is not designed for common use, only for the
-   * occassional time when access to a system table is necessary.  Messing
-   * with system tables can strip the paint off your house and give your whole
-   * family a permanent, orange afro.  You have been warned.
-   * 
-   * @param tableName Table name, may be a system table
-   * @return The table, or {@code null} if it doesn't exist
-   * @usage _intermediate_method_
-   */
-  public Table getSystemTable(String tableName)
-    throws IOException
-  {
-    return getTable(tableName, true, defaultUseBigIndex());
-  }
-
-  /**
-   * @return the core properties for the database
-   * @usage _general_method_
-   */
-  public PropertyMap getDatabaseProperties() throws IOException {
-    if(_dbPropMaps == null) {
-      _dbPropMaps = getPropertiesForDbObject(OBJECT_NAME_DB_PROPS);
-    }
-    return _dbPropMaps.getDefault();
-  }
-
-  /**
-   * @return the summary properties for the database
-   * @usage _general_method_
-   */
-  public PropertyMap getSummaryProperties() throws IOException {
-    if(_summaryPropMaps == null) {
-      _summaryPropMaps = getPropertiesForDbObject(OBJECT_NAME_SUMMARY_PROPS);
-    }
-    return _summaryPropMaps.getDefault();
-  }
-
-  /**
-   * @return the user-defined properties for the database
-   * @usage _general_method_
-   */
-  public PropertyMap getUserDefinedProperties() throws IOException {
-    if(_userDefPropMaps == null) {
-      _userDefPropMaps = getPropertiesForDbObject(OBJECT_NAME_USERDEF_PROPS);
-    }
-    return _userDefPropMaps.getDefault();
-  }
-
-  /**
-   * @return the PropertyMaps for the object with the given id
-   * @usage _advanced_method_
-   */
-  public PropertyMaps getPropertiesForObject(int objectId)
-    throws IOException
-  {
-    Map<String,Object> objectRow = _tableFinder.getObjectRow(
-        objectId, SYSTEM_CATALOG_PROPS_COLUMNS);
-    byte[] propsBytes = null;
-    if(objectRow != null) {
-      propsBytes = (byte[])objectRow.get(CAT_COL_PROPS);
-    }
-    return readProperties(propsBytes, objectId);
-  }
-
-  /**
-   * @return property group for the given "database" object
-   */
-  private PropertyMaps getPropertiesForDbObject(String dbName)
-    throws IOException
-  {
-    if(_dbParentId == null) {
-      // need the parent if of the databases objects
-      _dbParentId = _tableFinder.findObjectId(DB_PARENT_ID, 
-                                              SYSTEM_OBJECT_NAME_DATABASES);
-      if(_dbParentId == null) {  
-        throw new IOException("Did not find required parent db id");
-      }
-    }
-
-    Map<String,Object> objectRow = _tableFinder.getObjectRow(
-        _dbParentId, dbName, SYSTEM_CATALOG_PROPS_COLUMNS);
-    byte[] propsBytes = null;
-    int objectId = -1;
-    if(objectRow != null) {
-      propsBytes = (byte[])objectRow.get(CAT_COL_PROPS);
-      objectId = (Integer)objectRow.get(CAT_COL_ID);
-    }
-    return readProperties(propsBytes, objectId);
-  }
-
-  /**
-   * @return the current database password, or {@code null} if none set.
-   * @usage _general_method_
-   */
-  public String getDatabasePassword() throws IOException
-  {
-    ByteBuffer buffer = takeSharedBuffer();
-    try {
-      _pageChannel.readPage(buffer, 0);
-
-      byte[] pwdBytes = new byte[_format.SIZE_PASSWORD];
-      buffer.position(_format.OFFSET_PASSWORD);
-      buffer.get(pwdBytes);
-
-      // de-mask password using extra password mask if necessary (the extra
-      // password mask is generated from the database creation date stored in
-      // the header)
-      byte[] pwdMask = getPasswordMask(buffer, _format);
-      if(pwdMask != null) {
-        for(int i = 0; i < pwdBytes.length; ++i) {
-          pwdBytes[i] ^= pwdMask[i % pwdMask.length];
-        }
-      }
-    
-      boolean hasPassword = false;
-      for(int i = 0; i < pwdBytes.length; ++i) {
-        if(pwdBytes[i] != 0) {
-          hasPassword = true;
-          break;
-        }
-      }
-
-      if(!hasPassword) {
-        return null;
-      }
-
-      String pwd = Column.decodeUncompressedText(pwdBytes, getCharset());
-
-      // remove any trailing null chars
-      int idx = pwd.indexOf('\0');
-      if(idx >= 0) {
-        pwd = pwd.substring(0, idx);
-      }
-
-      return pwd;
-    } finally {
-      releaseSharedBuffer(buffer);
-    }
-  }
-
-  /**
-   * Finds the relationships matching the given from and to tables from the
-   * given cursor and adds them to the given list.
-   */
-  private static void collectRelationships(
-      Cursor cursor, Table fromTable, Table toTable,
-      List<Relationship> relationships)
-  {
-    for(Map<String,Object> row : cursor) {
-      String fromName = (String)row.get(REL_COL_FROM_TABLE);
-      String toName = (String)row.get(REL_COL_TO_TABLE);
-      
-      if(fromTable.getName().equalsIgnoreCase(fromName) &&
-         toTable.getName().equalsIgnoreCase(toName))
-      {
-
-        String relName = (String)row.get(REL_COL_NAME);
-        
-        // found more info for a relationship.  see if we already have some
-        // info for this relationship
-        Relationship rel = null;
-        for(Relationship tmp : relationships) {
-          if(tmp.getName().equalsIgnoreCase(relName)) {
-            rel = tmp;
-            break;
-          }
-        }
-
-        if(rel == null) {
-          // new relationship
-          int numCols = (Integer)row.get(REL_COL_COLUMN_COUNT);
-          int flags = (Integer)row.get(REL_COL_FLAGS);
-          rel = new Relationship(relName, fromTable, toTable,
-                                 flags, numCols);
-          relationships.add(rel);
-        }
-
-        // add column info
-        int colIdx = (Integer)row.get(REL_COL_COLUMN_INDEX);
-        Column fromCol = fromTable.getColumn(
-            (String)row.get(REL_COL_FROM_COLUMN));
-        Column toCol = toTable.getColumn(
-            (String)row.get(REL_COL_TO_COLUMN));
-
-        rel.getFromColumns().set(colIdx, fromCol);
-        rel.getToColumns().set(colIdx, toCol);
-      }
-    }    
-  }
-  
-  /**
-   * Add a new table to the system catalog
-   * @param name Table name
-   * @param pageNumber Page number that contains the table definition
-   */
-  private void addToSystemCatalog(String name, int pageNumber, Short type, 
-                                  String linkedDbName, String linkedTableName)
-    throws IOException
-  {
-    Object[] catalogRow = new Object[_systemCatalog.getColumnCount()];
-    int idx = 0;
-    Date creationTime = new Date();
-    for (Iterator<Column> iter = _systemCatalog.getColumns().iterator();
-         iter.hasNext(); idx++)
-    {
-      Column col = iter.next();
-      if (CAT_COL_ID.equals(col.getName())) {
-        catalogRow[idx] = Integer.valueOf(pageNumber);
-      } else if (CAT_COL_NAME.equals(col.getName())) {
-        catalogRow[idx] = name;
-      } else if (CAT_COL_TYPE.equals(col.getName())) {
-        catalogRow[idx] = type;
-      } else if (CAT_COL_DATE_CREATE.equals(col.getName()) ||
-                 CAT_COL_DATE_UPDATE.equals(col.getName())) {
-        catalogRow[idx] = creationTime;
-      } else if (CAT_COL_PARENT_ID.equals(col.getName())) {
-        catalogRow[idx] = _tableParentId;
-      } else if (CAT_COL_FLAGS.equals(col.getName())) {
-        catalogRow[idx] = Integer.valueOf(0);
-      } else if (CAT_COL_OWNER.equals(col.getName())) {
-        byte[] owner = new byte[2];
-        catalogRow[idx] = owner;
-        owner[0] = (byte) 0xcf;
-        owner[1] = (byte) 0x5f;
-      } else if (CAT_COL_DATABASE.equals(col.getName())) {
-        catalogRow[idx] = linkedDbName;
-      } else if (CAT_COL_FOREIGN_NAME.equals(col.getName())) {
-        catalogRow[idx] = linkedTableName;
-      }
-    }
-    _systemCatalog.addRow(catalogRow);
-  }
-  
-  /**
-   * Add a new table to the system's access control entries
-   * @param pageNumber Page number that contains the table definition
-   */
-  private void addToAccessControlEntries(int pageNumber) throws IOException {
-
-    if(_newTableSIDs.isEmpty()) {
-      initNewTableSIDs();
-    }
-
-    Table acEntries = getAccessControlEntries();
-    Column acmCol = acEntries.getColumn(ACE_COL_ACM);
-    Column inheritCol = acEntries.getColumn(ACE_COL_F_INHERITABLE);
-    Column objIdCol = acEntries.getColumn(ACE_COL_OBJECT_ID);
-    Column sidCol = acEntries.getColumn(ACE_COL_SID);
-
-    // construct a collection of ACE entries mimicing those of our parent, the
-    // "Tables" system object
-    List<Object[]> aceRows = new ArrayList<Object[]>(_newTableSIDs.size());
-    for(byte[] sid : _newTableSIDs) {
-      Object[] aceRow = new Object[acEntries.getColumnCount()];
-      acmCol.setRowValue(aceRow, SYS_FULL_ACCESS_ACM);
-      inheritCol.setRowValue(aceRow, Boolean.FALSE);
-      objIdCol.setRowValue(aceRow, Integer.valueOf(pageNumber));
-      sidCol.setRowValue(aceRow, sid);
-      aceRows.add(aceRow);
-    }
-    acEntries.addRows(aceRows);  
-  }
-
-  /**
-   * Determines the collection of SIDs which need to be added to new tables.
-   */
-  private void initNewTableSIDs() throws IOException
-  {
-    // search for ACEs matching the tableParentId.  use the index on the
-    // objectId column if found (should be there)
-    Cursor cursor = createCursorWithOptionalIndex(
-        getAccessControlEntries(), ACE_COL_OBJECT_ID, _tableParentId);
-    
-    for(Map<String, Object> row : cursor) {
-      Integer objId = (Integer)row.get(ACE_COL_OBJECT_ID);
-      if(_tableParentId.equals(objId)) {
-        _newTableSIDs.add((byte[])row.get(ACE_COL_SID));
-      }
-    }
-
-    if(_newTableSIDs.isEmpty()) {
-      // if all else fails, use the hard-coded default
-      _newTableSIDs.add(SYS_DEFAULT_SID);
-    }
-  }
-
-  /**
-   * Reads a table with the given name from the given pageNumber.
-   */
-  private Table readTable(String name, int pageNumber, int flags,
-                          boolean useBigIndex)
-    throws IOException
-  {
-    // first, check for existing table
-    Table table = _tableCache.get(pageNumber);
-    if(table != null) {
-      return table;
-    }
-    
-    ByteBuffer buffer = takeSharedBuffer();
-    try {
-      // need to load table from db
-      _pageChannel.readPage(buffer, pageNumber);
-      byte pageType = buffer.get(0);
-      if (pageType != PageTypes.TABLE_DEF) {
-        throw new IOException(
-            "Looking for " + name + " at page " + pageNumber +
-            ", but page type is " + pageType);
-      }
-      return _tableCache.put(
-          new Table(this, buffer, pageNumber, name, flags, useBigIndex));
-    } finally {
-      releaseSharedBuffer(buffer);
-    }
-  }
-
-  /**
-   * Creates a Cursor restricted to the given column value if possible (using
-   * an existing index), otherwise a simple table cursor.
-   */
-  private static Cursor createCursorWithOptionalIndex(
-      Table table, String colName, Object colValue)
-    throws IOException
-  {
-    try {
-      return new CursorBuilder(table)
-        .setIndexByColumns(table.getColumn(colName))
-        .setSpecificEntry(colValue)
-        .toCursor();
-    } catch(IllegalArgumentException e) {
-      LOG.info("Could not find expected index on table " + table.getName());
-    }
-    // use table scan instead
-    return Cursor.createCursor(table);
-  }
-  
-  /**
-   * Copy an existing JDBC ResultSet into a new table in this database
-   * 
-   * @param name Name of the new table to create
-   * @param source ResultSet to copy from
-   *
-   * @return the name of the copied table
-   *
-   * @see ImportUtil#importResultSet(ResultSet,Database,String)
-   * @usage _general_method_
-   */
-  public String copyTable(String name, ResultSet source)
-    throws SQLException, IOException
-  {
-    return ImportUtil.importResultSet(source, this, name);
-  }
-  
-  /**
-   * Copy an existing JDBC ResultSet into a new table in this database
-   * 
-   * @param name Name of the new table to create
-   * @param source ResultSet to copy from
-   * @param filter valid import filter
-   *
-   * @return the name of the imported table
-   *
-   * @see ImportUtil#importResultSet(ResultSet,Database,String,ImportFilter)
-   * @usage _general_method_
-   */
-  public String copyTable(String name, ResultSet source, ImportFilter filter)
-    throws SQLException, IOException
-  {
-    return ImportUtil.importResultSet(source, this, name, filter);
-  }
-  
-  /**
-   * Copy a delimited text file into a new table in this database
-   * 
-   * @param name Name of the new table to create
-   * @param f Source file to import
-   * @param delim Regular expression representing the delimiter string.
-   *
-   * @return the name of the imported table
-   *
-   * @see ImportUtil#importFile(File,Database,String,String)
-   * @usage _general_method_
-   */
-  public String importFile(String name, File f, String delim)
-    throws IOException
-  {
-    return ImportUtil.importFile(f, this, name, delim);
-  }
-
-  /**
-   * Copy a delimited text file into a new table in this database
-   * 
-   * @param name Name of the new table to create
-   * @param f Source file to import
-   * @param delim Regular expression representing the delimiter string.
-   * @param filter valid import filter
-   *
-   * @return the name of the imported table
-   *
-   * @see ImportUtil#importFile(File,Database,String,String,ImportFilter)
-   * @usage _general_method_
-   */
-  public String importFile(String name, File f, String delim,
-                           ImportFilter filter)
-    throws IOException
-  {
-    return ImportUtil.importFile(f, this, name, delim, filter);
-  }
-
-  /**
-   * Copy a delimited text file into a new table in this database
-   * 
-   * @param name Name of the new table to create
-   * @param in Source reader to import
-   * @param delim Regular expression representing the delimiter string.
-   *
-   * @return the name of the imported table
-   *
-   * @see ImportUtil#importReader(BufferedReader,Database,String,String)
-   * @usage _general_method_
-   */
-  public String importReader(String name, BufferedReader in, String delim)
-    throws IOException
-  {
-    return ImportUtil.importReader(in, this, name, delim);
-  }
-  
-  /**
-   * Copy a delimited text file into a new table in this database
-   * @param name Name of the new table to create
-   * @param in Source reader to import
-   * @param delim Regular expression representing the delimiter string.
-   * @param filter valid import filter
-   *
-   * @return the name of the imported table
-   *
-   * @see ImportUtil#importReader(BufferedReader,Database,String,String,ImportFilter)
-   * @usage _general_method_
-   */
-  public String importReader(String name, BufferedReader in, String delim,
-                             ImportFilter filter)
-    throws IOException
-  {
-    return ImportUtil.importReader(in, this, name, delim, filter);
-  }
-
-  /**
-   * Flushes any current changes to the database file (and any linked
-   * databases) to disk.
-   * @usage _general_method_
-   */
-  public void flush() throws IOException {
-    if(_linkedDbs != null) {
-      for(Database linkedDb : _linkedDbs.values()) {
-        linkedDb.flush();
-      }
-    }
-    _pageChannel.flush();
-  }
-  
-  /**
-   * Close the database file (and any linked databases)
-   * @usage _general_method_
-   */
-  public void close() throws IOException {
-    if(_linkedDbs != null) {
-      for(Database linkedDb : _linkedDbs.values()) {
-        linkedDb.close();
-      }
-    }
-    _pageChannel.close();
-  }
-  
-  /**
-   * @return A table or column name escaped for Access
-   * @usage _general_method_
-   */
-  public static String escapeIdentifier(String s) {
-    if (isReservedWord(s)) {
-      return ESCAPE_PREFIX + s; 
-    }
-    return s;
-  }
-
-  /**
-   * @return {@code true} if the given string is a reserved word,
-   *         {@code false} otherwise
-   * @usage _general_method_
-   */
-  public static boolean isReservedWord(String s) {
-    return RESERVED_WORDS.contains(s.toLowerCase());
-  }
-
-  /**
-   * Validates an identifier name.
-   * @usage _advanced_method_
-   */
-  public static void validateIdentifierName(String name,
-                                            int maxLength,
-                                            String identifierType)
-  {
-    if((name == null) || (name.trim().length() == 0)) {
-      throw new IllegalArgumentException(
-          identifierType + " must have non-empty name");
-    }
-    if(name.length() > maxLength) {
-      throw new IllegalArgumentException(
-          identifierType + " name is longer than max length of " + maxLength +
-          ": " + name);
-    }
-  }
-  
-  @Override
-  public String toString() {
-    return ToStringBuilder.reflectionToString(this);
-  }
-
-  /**
-   * Adds a table to the _tableLookup and resets the _tableNames set
-   */
-  private void addTable(String tableName, Integer pageNumber, Short type, 
-                        String linkedDbName, String linkedTableName)
-  {
-    _tableLookup.put(toLookupName(tableName),
-                     createTableInfo(tableName, pageNumber, 0, type, 
-                                     linkedDbName, linkedTableName));
-    // clear this, will be created next time needed
-    _tableNames = null;
-  }
-
-  /**
-   * Creates a TableInfo instance appropriate for the given table data.
-   */
-  private static TableInfo createTableInfo(
-      String tableName, Integer pageNumber, int flags, Short type, 
-      String linkedDbName, String linkedTableName)
-  {
-    if(TYPE_LINKED_TABLE.equals(type)) {
-      return new LinkedTableInfo(pageNumber, tableName, flags, linkedDbName,
-                                 linkedTableName);
-    }
-    return new TableInfo(pageNumber, tableName, flags);
-  }
-
-  /**
-   * @return the tableInfo of the given table, if any
-   */
-  private TableInfo lookupTable(String tableName) throws IOException {
-
-    String lookupTableName = toLookupName(tableName);
-    TableInfo tableInfo = _tableLookup.get(lookupTableName);
-    if(tableInfo != null) {
-      return tableInfo;
-    }
-
-    tableInfo = _tableFinder.lookupTable(tableName);
-
-    if(tableInfo != null) {
-      // cache for later
-      _tableLookup.put(lookupTableName, tableInfo);
-    }
-
-    return tableInfo;
-  }
-
-  /**
-   * @return a string usable in the _tableLookup map.
-   */
-  static String toLookupName(String name) {
-    return ((name != null) ? name.toUpperCase() : null);
-  }
-
-  /**
-   * @return {@code true} if the given flags indicate that an object is some
-   *         sort of system object, {@code false} otherwise.
-   */
-  private static boolean isSystemObject(int flags) {
-    return ((flags & SYSTEM_OBJECT_FLAGS) != 0);
-  }
-
-  /**
-   * Returns {@code false} if "big index support" has been disabled explicity
-   * on the this Database or via a system property, {@code true} otherwise.
-   * @usage _advanced_method_
-   */
-  public boolean defaultUseBigIndex() {
-    if(_useBigIndex != null) {
-      return _useBigIndex;
-    }
-    String prop = System.getProperty(USE_BIG_INDEX_PROPERTY);
-    if(prop != null) {
-      return Boolean.TRUE.toString().equalsIgnoreCase(prop);
-    }
-    return true;
-  }
-
-  /**
-   * Returns the default TimeZone.  This is normally the platform default
-   * TimeZone as returned by {@link TimeZone#getDefault}, but can be
-   * overridden using the system property {@value #TIMEZONE_PROPERTY}.
-   * @usage _advanced_method_
-   */
-  public static TimeZone getDefaultTimeZone()
-  {
-    String tzProp = System.getProperty(TIMEZONE_PROPERTY);
-    if(tzProp != null) {
-      tzProp = tzProp.trim();
-      if(tzProp.length() > 0) {
-        return TimeZone.getTimeZone(tzProp);
-      }
-    }
-
-    // use system default
-    return TimeZone.getDefault();
-  }
-  
-  /**
-   * Returns the default Charset for the given JetFormat.  This may or may not
-   * be platform specific, depending on the format, but can be overridden
-   * using a system property composed of the prefix
-   * {@value #CHARSET_PROPERTY_PREFIX} followed by the JetFormat version to
-   * which the charset should apply, e.g. {@code
-   * "com.healthmarketscience.jackcess.charset.VERSION_3"}.
-   * @usage _advanced_method_
-   */
-  public static Charset getDefaultCharset(JetFormat format)
-  {
-    String csProp = System.getProperty(CHARSET_PROPERTY_PREFIX + format);
-    if(csProp != null) {
-      csProp = csProp.trim();
-      if(csProp.length() > 0) {
-        return Charset.forName(csProp);
-      }
-    }
-
-    // use format default
-    return format.CHARSET;
-  }
-  
-  /**
-   * Returns the default Table.ColumnOrder.  This defaults to
-   * {@link #DEFAULT_COLUMN_ORDER}, but can be overridden using the system
-   * property {@value #COLUMN_ORDER_PROPERTY}.
-   * @usage _advanced_method_
-   */
-  public static Table.ColumnOrder getDefaultColumnOrder()
-  {
-    String coProp = System.getProperty(COLUMN_ORDER_PROPERTY);
-    if(coProp != null) {
-      coProp = coProp.trim();
-      if(coProp.length() > 0) {
-        return Table.ColumnOrder.valueOf(coProp);
-      }
-    }
-
-    // use default order
-    return DEFAULT_COLUMN_ORDER;
-  }
-  
-  /**
-   * Returns the default enforce foreign-keys policy.  This defaults to
-   * {@code false}, but can be overridden using the system
-   * property {@value #FK_ENFORCE_PROPERTY}.
-   * @usage _advanced_method_
-   */
-  public static boolean getDefaultEnforceForeignKeys()
-  {
-    String prop = System.getProperty(FK_ENFORCE_PROPERTY);
-    if(prop != null) {
-      return Boolean.TRUE.toString().equalsIgnoreCase(prop);
-    }
-    return false;
-  }
-  
-  /**
-   * Copies the given InputStream to the given channel using the most
-   * efficient means possible.
-   */
-  private static void transferFrom(FileChannel channel, InputStream in)
-    throws IOException
-  {
-    ReadableByteChannel readChannel = Channels.newChannel(in);
-    if(!BROKEN_NIO) {
-      // sane implementation
-      channel.transferFrom(readChannel, 0, MAX_EMPTYDB_SIZE);    
-    } else {
-      // do things the hard way for broken vms
-      ByteBuffer bb = ByteBuffer.allocate(8096);
-      while(readChannel.read(bb) >= 0) {
-        bb.flip();
-        channel.write(bb);
-        bb.clear();
-      }
-    }
-  }
-
-  /**
-   * Returns the password mask retrieved from the given header page and
-   * format, or {@code null} if this format does not use a password mask.
-   */
-  static byte[] getPasswordMask(ByteBuffer buffer, JetFormat format)
-  {
-    // get extra password mask if necessary (the extra password mask is
-    // generated from the database creation date stored in the header)
-    int pwdMaskPos = format.OFFSET_HEADER_DATE;
-    if(pwdMaskPos < 0) {
-      return null;
-    }
-
-    buffer.position(pwdMaskPos);
-    double dateVal = Double.longBitsToDouble(buffer.getLong());
-
-    byte[] pwdMask = new byte[4];
-    ByteBuffer.wrap(pwdMask).order(PageChannel.DEFAULT_BYTE_ORDER)
-      .putInt((int)dateVal);
-
-    return pwdMask;
-  }
-
-  static InputStream getResourceAsStream(String resourceName)
-    throws IOException
-  {
-    InputStream stream = Database.class.getClassLoader()
-      .getResourceAsStream(resourceName);
-    
-    if(stream == null) {
-      
-      stream = Thread.currentThread().getContextClassLoader()
-        .getResourceAsStream(resourceName);
-      
-      if(stream == null) {
-        throw new IOException("Could not load jackcess resource " +
-                              resourceName);
-      }
-    }
-
-    return stream;
-  }
-
-  private static boolean isTableType(Short objType) {
-    return(TYPE_TABLE.equals(objType) || TYPE_LINKED_TABLE.equals(objType));
-  }
-
-  /**
-   * Utility class for storing table page number and actual name.
-   */
-  private static class TableInfo
-  {
-    public final Integer pageNumber;
-    public final String tableName;
-    public final int flags;
-
-    private TableInfo(Integer newPageNumber, String newTableName, int newFlags) {
-      pageNumber = newPageNumber;
-      tableName = newTableName;
-      flags = newFlags;
-    }
-
-    public boolean isLinked() {
-      return false;
-    }
-  }
-
-  /**
-   * Utility class for storing linked table info
-   */
-  private static class LinkedTableInfo extends TableInfo
-  {
-    private final String linkedDbName;
-    private final String linkedTableName;
-
-    private LinkedTableInfo(Integer newPageNumber, String newTableName, 
-                            int newFlags, String newLinkedDbName, 
-                            String newLinkedTableName) {
-      super(newPageNumber, newTableName, newFlags);
-      linkedDbName = newLinkedDbName;
-      linkedTableName = newLinkedTableName;
-    }
-
-    @Override
-    public boolean isLinked() {
-      return true;
-    }
-  }
-
-  /**
-   * Table iterator for this database, unmodifiable.
-   */
-  private class TableIterator implements Iterator<Table>
-  {
-    private Iterator<String> _tableNameIter;
-
-    private TableIterator() {
-      try {
-        _tableNameIter = getTableNames().iterator();
-      } catch(IOException e) {
-        throw new IllegalStateException(e);
-      }
-    }
-
-    public boolean hasNext() {
-      return _tableNameIter.hasNext();
-    }
-
-    public void remove() {
-      throw new UnsupportedOperationException();
-    }
-
-    public Table next() {
-      if(!hasNext()) {
-        throw new NoSuchElementException();
-      }
-      try {
-        return getTable(_tableNameIter.next());
-      } catch(IOException e) {
-        throw new IllegalStateException(e);
-      }
-    }
-  }
-
-  /**
-   * Utility class for handling table lookups.
-   */
-  private abstract class TableFinder
-  {
-    public Integer findObjectId(Integer parentId, String name) 
-      throws IOException 
-    {
-      Cursor cur = findRow(parentId, name);
-      if(cur == null) {  
-        return null;
-      }
-      Column idCol = _systemCatalog.getColumn(CAT_COL_ID);
-      return (Integer)cur.getCurrentRowValue(idCol);
-    }
-
-    public Map<String,Object> getObjectRow(Integer parentId, String name,
-                                           Collection<String> columns) 
-      throws IOException 
-    {
-      Cursor cur = findRow(parentId, name);
-      return ((cur != null) ? cur.getCurrentRow(columns) : null);
-    }
-
-    public Map<String,Object> getObjectRow(
-        Integer objectId, Collection<String> columns)
-      throws IOException
-    {
-      Cursor cur = findRow(objectId);
-      return ((cur != null) ? cur.getCurrentRow(columns) : null);
-    }
-
-    public void getTableNames(Set<String> tableNames,
-                              boolean systemTables)
-      throws IOException
-    {
-      for(Map<String,Object> row : getTableNamesCursor().iterable(
-              SYSTEM_CATALOG_TABLE_NAME_COLUMNS)) {
-
-        String tableName = (String)row.get(CAT_COL_NAME);
-        int flags = (Integer)row.get(CAT_COL_FLAGS);
-        Short type = (Short)row.get(CAT_COL_TYPE);
-        int parentId = (Integer)row.get(CAT_COL_PARENT_ID);
-
-        if((parentId == _tableParentId) && isTableType(type) && 
-           (isSystemObject(flags) == systemTables)) {
-          tableNames.add(tableName);
-        }
-      }
-    }
-
-    protected abstract Cursor findRow(Integer parentId, String name)
-      throws IOException;
-
-    protected abstract Cursor findRow(Integer objectId) 
-      throws IOException;
-
-    protected abstract Cursor getTableNamesCursor() throws IOException;
-
-    public abstract TableInfo lookupTable(String tableName)
-      throws IOException;
-
-    protected abstract int findMaxSyntheticId() throws IOException;
-
-    public int getNextFreeSyntheticId() throws IOException
-    {
-      int maxSynthId = findMaxSyntheticId();
-      if(maxSynthId >= -1) {
-        // bummer, no more ids available
-        throw new IllegalStateException("Too many database objects!");
-      }
-      return maxSynthId + 1;
-    }
-  }
-
-  /**
-   * Normal table lookup handler, using catalog table index.
-   */
-  private final class DefaultTableFinder extends TableFinder
-  {
-    private final IndexCursor _systemCatalogCursor;
-    private IndexCursor _systemCatalogIdCursor;
-
-    private DefaultTableFinder(IndexCursor systemCatalogCursor) {
-      _systemCatalogCursor = systemCatalogCursor;
-    }
-    
-    private void initIdCursor() throws IOException {
-      if(_systemCatalogIdCursor == null) {
-        _systemCatalogIdCursor = new CursorBuilder(_systemCatalog)
-          .setIndexByColumnNames(CAT_COL_ID)
-          .toIndexCursor();
-      }
-    }
-
-    @Override
-    protected Cursor findRow(Integer parentId, String name) 
-      throws IOException 
-    {
-      return (_systemCatalogCursor.findFirstRowByEntry(parentId, name) ?
-              _systemCatalogCursor : null);
-    }
-
-    @Override
-    protected Cursor findRow(Integer objectId) throws IOException 
-    {
-      initIdCursor();
-      return (_systemCatalogIdCursor.findFirstRowByEntry(objectId) ?
-              _systemCatalogIdCursor : null);
-    }
-
-    @Override
-    public TableInfo lookupTable(String tableName) throws IOException {
-
-      if(findRow(_tableParentId, tableName) == null) {
-        return null;
-      }
-
-      Map<String,Object> row = _systemCatalogCursor.getCurrentRow(
-          SYSTEM_CATALOG_COLUMNS);
-      Integer pageNumber = (Integer)row.get(CAT_COL_ID);
-      String realName = (String)row.get(CAT_COL_NAME);
-      int flags = (Integer)row.get(CAT_COL_FLAGS);
-      Short type = (Short)row.get(CAT_COL_TYPE);
-
-      if(!isTableType(type)) {
-        return null;
-      }
-
-      String linkedDbName = (String)row.get(CAT_COL_DATABASE);
-      String linkedTableName = (String)row.get(CAT_COL_FOREIGN_NAME);
-
-      return createTableInfo(realName, pageNumber, flags, type, linkedDbName,
-                             linkedTableName);
-    }
-    
-    @Override
-    protected Cursor getTableNamesCursor() throws IOException {
-      return new CursorBuilder(_systemCatalog)
-        .setIndex(_systemCatalogCursor.getIndex())
-        .setStartEntry(_tableParentId, IndexData.MIN_VALUE)
-        .setEndEntry(_tableParentId, IndexData.MAX_VALUE)
-        .toIndexCursor();
-    }
-
-    @Override
-    protected int findMaxSyntheticId() throws IOException {
-      initIdCursor();
-      _systemCatalogIdCursor.reset();
-
-      // synthetic ids count up from min integer.  so the current, highest,
-      // in-use synthetic id is the max id < 0.
-      _systemCatalogIdCursor.findClosestRowByEntry(0);
-      if(!_systemCatalogIdCursor.moveToPreviousRow()) {
-        return Integer.MIN_VALUE;
-      }
-      Column idCol = _systemCatalog.getColumn(CAT_COL_ID);
-      return (Integer)_systemCatalogIdCursor.getCurrentRowValue(idCol);
-    }
-  }
-  
-  /**
-   * Fallback table lookup handler, using catalog table scans.
-   */
-  private final class FallbackTableFinder extends TableFinder
-  {
-    private final Cursor _systemCatalogCursor;
-
-    private FallbackTableFinder(Cursor systemCatalogCursor) {
-      _systemCatalogCursor = systemCatalogCursor;
-    }
-
-    @Override
-    protected Cursor findRow(Integer parentId, String name) 
-      throws IOException 
-    {
-      Map<String,Object> rowPat = new HashMap<String,Object>();
-      rowPat.put(CAT_COL_PARENT_ID, parentId);  
-      rowPat.put(CAT_COL_NAME, name);
-      return (_systemCatalogCursor.findFirstRow(rowPat) ?
-              _systemCatalogCursor : null);
-    }
-
-    @Override
-    protected Cursor findRow(Integer objectId) throws IOException 
-    {
-      Column idCol = _systemCatalog.getColumn(CAT_COL_ID);
-      return (_systemCatalogCursor.findFirstRow(idCol, objectId) ?
-              _systemCatalogCursor : null);
-    }
-
-    @Override
-    public TableInfo lookupTable(String tableName) throws IOException {
-
-      for(Map<String,Object> row : _systemCatalogCursor.iterable(
-              SYSTEM_CATALOG_TABLE_NAME_COLUMNS)) {
-
-        Short type = (Short)row.get(CAT_COL_TYPE);
-        if(!isTableType(type)) {
-          continue;
-        }
-
-        int parentId = (Integer)row.get(CAT_COL_PARENT_ID);
-        if(parentId != _tableParentId) {
-          continue;
-        }
-
-        String realName = (String)row.get(CAT_COL_NAME);
-        if(!tableName.equalsIgnoreCase(realName)) {
-          continue;
-        }
-
-        Integer pageNumber = (Integer)row.get(CAT_COL_ID);
-        int flags = (Integer)row.get(CAT_COL_FLAGS);
-        String linkedDbName = (String)row.get(CAT_COL_DATABASE);
-        String linkedTableName = (String)row.get(CAT_COL_FOREIGN_NAME);
-
-        return createTableInfo(realName, pageNumber, flags, type, linkedDbName,
-                               linkedTableName);
-      }
-
-      return null;
-    }
-    
-    @Override
-    protected Cursor getTableNamesCursor() throws IOException {
-      return _systemCatalogCursor;
-    }
-
-    @Override
-    protected int findMaxSyntheticId() throws IOException {
-      // find max id < 0
-      Column idCol = _systemCatalog.getColumn(CAT_COL_ID);
-      _systemCatalogCursor.reset();
-      int curMaxSynthId = Integer.MIN_VALUE;
-      while(_systemCatalogCursor.moveToNextRow()) {
-        int id = (Integer)_systemCatalogCursor.getCurrentRowValue(idCol);
-        if((id > curMaxSynthId) && (id < 0)) {
-          curMaxSynthId = id;
-        }
-      }
-      return curMaxSynthId;
-    }
-  }
-
-  /**
-   * WeakReference for a Table which holds the table pageNumber (for later
-   * cache purging).
-   */
-  private static final class WeakTableReference extends WeakReference<Table>
-  {
-    private final Integer _pageNumber;
-
-    private WeakTableReference(Integer pageNumber, Table table, 
-                               ReferenceQueue<Table> queue) {
-      super(table, queue);
-      _pageNumber = pageNumber;
-    }
-
-    public Integer getPageNumber() {
-      return _pageNumber;
-    }
-  }
-
-  /**
-   * Cache of currently in-use tables, allows re-use of existing tables.
-   */
-  private static final class TableCache
-  {
-    private final Map<Integer,WeakTableReference> _tables = 
-      new HashMap<Integer,WeakTableReference>();
-    private final ReferenceQueue<Table> _queue = new ReferenceQueue<Table>();
-
-    public Table get(Integer pageNumber) {
-      WeakTableReference ref = _tables.get(pageNumber);
-      return ((ref != null) ? ref.get() : null);
-    }
-
-    public Table put(Table table) {
-      purgeOldRefs();
-  
-      Integer pageNumber = table.getTableDefPageNumber();
-      WeakTableReference ref = new WeakTableReference(
-          pageNumber, table, _queue);
-      _tables.put(pageNumber, ref);
-
-      return table;
-    }
-
-    private void purgeOldRefs() {
-      WeakTableReference oldRef = null;
-      while((oldRef = (WeakTableReference)_queue.poll()) != null) {
-        _tables.remove(oldRef.getPageNumber());
-      }
-    }
-  }  
-}
index fa0a394142a110ca2501362a15c3a2914d983a3b..0b43657d5ec82a261494fcde3eb2c808391e1975 100644 (file)
@@ -149,15 +149,15 @@ public class DatabaseBuilder
    * Opens an existingnew Database using the configured information.
    */
   public Database open() throws IOException {
-    return Database.open(_mdbFile, _readOnly, _channel, _autoSync, _charset,
-                         _timeZone, _codecProvider);
+    return DatabaseImpl.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);
+    return DatabaseImpl.create(_fileFormat, _mdbFile, _channel, _autoSync, 
+                               _charset, _timeZone);
   }
 }
diff --git a/src/java/com/healthmarketscience/jackcess/DatabaseImpl.java b/src/java/com/healthmarketscience/jackcess/DatabaseImpl.java
new file mode 100644 (file)
index 0000000..4854fd6
--- /dev/null
@@ -0,0 +1,2057 @@
+/*
+Copyright (c) 2005 Health Market Science, Inc.
+
+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
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.ConcurrentModificationException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+import com.healthmarketscience.jackcess.query.Query;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ *
+ * @author Tim McCune
+ * @usage _general_class_
+ */
+public class DatabaseImpl extends Database
+{  
+  private static final Log LOG = LogFactory.getLog(DatabaseImpl.class);
+
+  /** this is the default "userId" used if we cannot find existing info.  this
+      seems to be some standard "Admin" userId for access files */
+  private static final byte[] SYS_DEFAULT_SID = new byte[2];
+  static {
+    SYS_DEFAULT_SID[0] = (byte) 0xA6;
+    SYS_DEFAULT_SID[1] = (byte) 0x33;
+  }
+
+  /** the resource path to be used when loading classpath resources */
+  static final String RESOURCE_PATH = 
+    System.getProperty(RESOURCE_PATH_PROPERTY, DEFAULT_RESOURCE_PATH);
+
+  /** whether or not this jvm has "broken" nio support */
+  static final boolean BROKEN_NIO = Boolean.TRUE.toString().equalsIgnoreCase(
+      System.getProperty(BROKEN_NIO_PROPERTY));
+  
+  /** System catalog always lives on page 2 */
+  private static final int PAGE_SYSTEM_CATALOG = 2;
+  /** Name of the system catalog */
+  private static final String TABLE_SYSTEM_CATALOG = "MSysObjects";
+
+  /** this is the access control bit field for created tables.  the value used
+      is equivalent to full access (Visual Basic DAO PermissionEnum constant:
+      dbSecFullAccess) */
+  private static final Integer SYS_FULL_ACCESS_ACM = 1048575;
+
+  /** ACE table column name of the actual access control entry */
+  private static final String ACE_COL_ACM = "ACM";
+  /** ACE table column name of the inheritable attributes flag */
+  private static final String ACE_COL_F_INHERITABLE = "FInheritable";
+  /** ACE table column name of the relevant objectId */
+  private static final String ACE_COL_OBJECT_ID = "ObjectId";
+  /** ACE table column name of the relevant userId */
+  private static final String ACE_COL_SID = "SID";
+
+  /** Relationship table column name of the column count */
+  private static final String REL_COL_COLUMN_COUNT = "ccolumn";
+  /** Relationship table column name of the flags */
+  private static final String REL_COL_FLAGS = "grbit";
+  /** Relationship table column name of the index of the columns */
+  private static final String REL_COL_COLUMN_INDEX = "icolumn";
+  /** Relationship table column name of the "to" column name */
+  private static final String REL_COL_TO_COLUMN = "szColumn";
+  /** Relationship table column name of the "to" table name */
+  private static final String REL_COL_TO_TABLE = "szObject";
+  /** Relationship table column name of the "from" column name */
+  private static final String REL_COL_FROM_COLUMN = "szReferencedColumn";
+  /** Relationship table column name of the "from" table name */
+  private static final String REL_COL_FROM_TABLE = "szReferencedObject";
+  /** Relationship table column name of the relationship */
+  private static final String REL_COL_NAME = "szRelationship";
+  
+  /** System catalog column name of the page on which system object definitions
+      are stored */
+  private static final String CAT_COL_ID = "Id";
+  /** System catalog column name of the name of a system object */
+  private static final String CAT_COL_NAME = "Name";
+  private static final String CAT_COL_OWNER = "Owner";
+  /** System catalog column name of a system object's parent's id */
+  private static final String CAT_COL_PARENT_ID = "ParentId";
+  /** System catalog column name of the type of a system object */
+  private static final String CAT_COL_TYPE = "Type";
+  /** System catalog column name of the date a system object was created */
+  private static final String CAT_COL_DATE_CREATE = "DateCreate";
+  /** System catalog column name of the date a system object was updated */
+  private static final String CAT_COL_DATE_UPDATE = "DateUpdate";
+  /** System catalog column name of the flags column */
+  private static final String CAT_COL_FLAGS = "Flags";
+  /** System catalog column name of the properties column */
+  private static final String CAT_COL_PROPS = "LvProp";
+  /** System catalog column name of the remote database */
+  private static final String CAT_COL_DATABASE = "Database";
+  /** System catalog column name of the remote table name */
+  private static final String CAT_COL_FOREIGN_NAME = "ForeignName";
+
+  /** top-level parentid for a database */
+  private static final int DB_PARENT_ID = 0xF000000;
+
+  /** the maximum size of any of the included "empty db" resources */
+  private static final long MAX_EMPTYDB_SIZE = 350000L;
+
+  /** this object is a "system" object */
+  static final int SYSTEM_OBJECT_FLAG = 0x80000000;
+  /** this object is another type of "system" object */
+  static final int ALT_SYSTEM_OBJECT_FLAG = 0x02;
+  /** this object is hidden */
+  static final int HIDDEN_OBJECT_FLAG = 0x08;
+  /** all flags which seem to indicate some type of system object */
+  static final int SYSTEM_OBJECT_FLAGS = 
+    SYSTEM_OBJECT_FLAG | ALT_SYSTEM_OBJECT_FLAG;
+
+  /** read-only channel access mode */
+  static final String RO_CHANNEL_MODE = "r";
+  /** read/write channel access mode */
+  static final String RW_CHANNEL_MODE = "rw";
+
+  /** Prefix for column or table names that are reserved words */
+  private static final String ESCAPE_PREFIX = "x";
+  /** Name of the system object that is the parent of all tables */
+  private static final String SYSTEM_OBJECT_NAME_TABLES = "Tables";
+  /** Name of the system object that is the parent of all databases */
+  private static final String SYSTEM_OBJECT_NAME_DATABASES = "Databases";
+  /** Name of the system object that is the parent of all relationships */
+  private static final String SYSTEM_OBJECT_NAME_RELATIONSHIPS = 
+    "Relationships";
+  /** Name of the table that contains system access control entries */
+  private static final String TABLE_SYSTEM_ACES = "MSysACEs";
+  /** Name of the table that contains table relationships */
+  private static final String TABLE_SYSTEM_RELATIONSHIPS = "MSysRelationships";
+  /** Name of the table that contains queries */
+  private static final String TABLE_SYSTEM_QUERIES = "MSysQueries";
+  /** Name of the table that contains complex type information */
+  private static final String TABLE_SYSTEM_COMPLEX_COLS = "MSysComplexColumns";
+  /** Name of the main database properties object */
+  private static final String OBJECT_NAME_DB_PROPS = "MSysDb";
+  /** Name of the summary properties object */
+  private static final String OBJECT_NAME_SUMMARY_PROPS = "SummaryInfo";
+  /** Name of the user-defined properties object */
+  private static final String OBJECT_NAME_USERDEF_PROPS = "UserDefined";
+  /** System object type for table definitions */
+  static final Short TYPE_TABLE = 1;
+  /** System object type for query definitions */
+  private static final Short TYPE_QUERY = 5;
+  /** System object type for linked table definitions */
+  private static final Short TYPE_LINKED_TABLE = 6;
+
+  /** max number of table lookups to cache */
+  private static final int MAX_CACHED_LOOKUP_TABLES = 50;
+
+  /** the columns to read when reading system catalog normally */
+  private static Collection<String> SYSTEM_CATALOG_COLUMNS =
+    new HashSet<String>(Arrays.asList(CAT_COL_NAME, CAT_COL_TYPE, CAT_COL_ID,
+                                      CAT_COL_FLAGS, CAT_COL_DATABASE, 
+                                      CAT_COL_FOREIGN_NAME));
+  /** the columns to read when finding table names */
+  private static Collection<String> SYSTEM_CATALOG_TABLE_NAME_COLUMNS =
+    new HashSet<String>(Arrays.asList(CAT_COL_NAME, CAT_COL_TYPE, CAT_COL_ID, 
+                                      CAT_COL_FLAGS, CAT_COL_PARENT_ID));
+  /** the columns to read when getting object propertyes */
+  private static Collection<String> SYSTEM_CATALOG_PROPS_COLUMNS =
+    new HashSet<String>(Arrays.asList(CAT_COL_ID, CAT_COL_PROPS));
+  
+  
+  /**
+   * All of the reserved words in Access that should be escaped when creating
+   * table or column names
+   */
+  private static final Set<String> RESERVED_WORDS = new HashSet<String>();
+  static {
+    //Yup, there's a lot.
+    RESERVED_WORDS.addAll(Arrays.asList(
+       "add", "all", "alphanumeric", "alter", "and", "any", "application", "as",
+       "asc", "assistant", "autoincrement", "avg", "between", "binary", "bit",
+       "boolean", "by", "byte", "char", "character", "column", "compactdatabase",
+       "constraint", "container", "count", "counter", "create", "createdatabase",
+       "createfield", "creategroup", "createindex", "createobject", "createproperty",
+       "createrelation", "createtabledef", "createuser", "createworkspace",
+       "currency", "currentuser", "database", "date", "datetime", "delete",
+       "desc", "description", "disallow", "distinct", "distinctrow", "document",
+       "double", "drop", "echo", "else", "end", "eqv", "error", "exists", "exit",
+       "false", "field", "fields", "fillcache", "float", "float4", "float8",
+       "foreign", "form", "forms", "from", "full", "function", "general",
+       "getobject", "getoption", "gotopage", "group", "group by", "guid", "having",
+       "idle", "ieeedouble", "ieeesingle", "if", "ignore", "imp", "in", "index",
+       "indexes", "inner", "insert", "inserttext", "int", "integer", "integer1",
+       "integer2", "integer4", "into", "is", "join", "key", "lastmodified", "left",
+       "level", "like", "logical", "logical1", "long", "longbinary", "longtext",
+       "macro", "match", "max", "min", "mod", "memo", "module", "money", "move",
+       "name", "newpassword", "no", "not", "null", "number", "numeric", "object",
+       "oleobject", "off", "on", "openrecordset", "option", "or", "order", "outer",
+       "owneraccess", "parameter", "parameters", "partial", "percent", "pivot",
+       "primary", "procedure", "property", "queries", "query", "quit", "real",
+       "recalc", "recordset", "references", "refresh", "refreshlink",
+       "registerdatabase", "relation", "repaint", "repairdatabase", "report",
+       "reports", "requery", "right", "screen", "section", "select", "set",
+       "setfocus", "setoption", "short", "single", "smallint", "some", "sql",
+       "stdev", "stdevp", "string", "sum", "table", "tabledef", "tabledefs",
+       "tableid", "text", "time", "timestamp", "top", "transform", "true", "type",
+       "union", "unique", "update", "user", "value", "values", "var", "varp",
+       "varbinary", "varchar", "where", "with", "workspace", "xor", "year", "yes",
+       "yesno"
+    ));
+  }
+
+  /** the File of the database */
+  private final File _file;
+  /** Buffer to hold database pages */
+  private ByteBuffer _buffer;
+  /** ID of the Tables system object */
+  private Integer _tableParentId;
+  /** Format that the containing database is in */
+  private final JetFormat _format;
+  /**
+   * Cache map of UPPERCASE table names to page numbers containing their
+   * definition and their stored table name (max size
+   * MAX_CACHED_LOOKUP_TABLES).
+   */
+  private final Map<String, TableInfo> _tableLookup =
+    new LinkedHashMap<String, TableInfo>() {
+    private static final long serialVersionUID = 0L;
+    @Override
+    protected boolean removeEldestEntry(Map.Entry<String, TableInfo> e) {
+      return(size() > MAX_CACHED_LOOKUP_TABLES);
+    }
+  };
+  /** set of table names as stored in the mdb file, created on demand */
+  private Set<String> _tableNames;
+  /** Reads and writes database pages */
+  private final PageChannel _pageChannel;
+  /** System catalog table */
+  private Table _systemCatalog;
+  /** utility table finder */
+  private TableFinder _tableFinder;
+  /** System access control entries table (initialized on first use) */
+  private Table _accessControlEntries;
+  /** System relationships table (initialized on first use) */
+  private Table _relationships;
+  /** System queries table (initialized on first use) */
+  private Table _queries;
+  /** System complex columns table (initialized on first use) */
+  private Table _complexCols;
+  /** SIDs to use for the ACEs added for new tables */
+  private final List<byte[]> _newTableSIDs = new ArrayList<byte[]>();
+  /** optional error handler to use when row errors are encountered */
+  private ErrorHandler _dbErrorHandler;
+  /** the file format of the database */
+  private FileFormat _fileFormat;
+  /** charset to use when handling text */
+  private Charset _charset;
+  /** timezone to use when handling dates */
+  private TimeZone _timeZone;
+  /** language sort order to be used for textual columns */
+  private Column.SortOrder _defaultSortOrder;
+  /** default code page to be used for textual columns (in some dbs) */
+  private Short _defaultCodePage;
+  /** the ordering used for table columns */
+  private Table.ColumnOrder _columnOrder;
+  /** whether or not enforcement of foreign-keys is enabled */
+  private boolean _enforceForeignKeys;
+  /** cache of in-use tables */
+  private final TableCache _tableCache = new TableCache();
+  /** handler for reading/writing properteies */
+  private PropertyMaps.Handler _propsHandler;
+  /** ID of the Databases system object */
+  private Integer _dbParentId;
+  /** core database properties */
+  private PropertyMaps _dbPropMaps;
+  /** summary properties */
+  private PropertyMaps _summaryPropMaps;
+  /** user-defined properties */
+  private PropertyMaps _userDefPropMaps;
+  /** linked table resolver */
+  private LinkResolver _linkResolver;
+  /** any linked databases which have been opened */
+  private Map<String,Database> _linkedDbs;
+  /** shared state used when enforcing foreign keys */
+  private final FKEnforcer.SharedState _fkEnforcerSharedState =
+    FKEnforcer.initSharedState();
+  /** Calendar for use interpreting dates/times in Columns */
+  private Calendar _calendar;
+
+  /**
+   * 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 DatabaseImpl 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;
+    }
+
+    boolean success = false;
+    try {
+
+      if(!readOnly) {
+
+        // verify that format supports writing
+        JetFormat jetFormat = JetFormat.getFormat(channel);
+
+        if(jetFormat.READ_ONLY) {
+          throw new IOException("jet format '" + jetFormat +
+                                "' does not support writing");
+        }
+      }
+
+      DatabaseImpl db = new DatabaseImpl(mdbFile, channel, closeChannel, autoSync, 
+                                 null, charset, timeZone, provider);
+      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
+        }
+      }
+    }
+  }
+  
+  /**
+   * 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 DatabaseImpl 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");
+    }
+
+    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);
+      DatabaseImpl db = new DatabaseImpl(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
+        }
+      }
+    }
+  }
+
+  /**
+   * Package visible only to support unit tests via DatabaseTest.openChannel().
+   * @param mdbFile file to open
+   * @param readOnly true if read-only
+   * @return a FileChannel on the given file.
+   * @exception FileNotFoundException
+   *            if the mode is <tt>"r"</tt> but the given file object does
+   *            not denote an existing regular file, or if the mode begins
+   *            with <tt>"rw"</tt> but the given file object does not denote
+   *            an existing, writable regular file and a new regular file of
+   *            that name cannot be created, or if some other error occurs
+   *            while opening or creating the file
+   */
+  static FileChannel openChannel(final File mdbFile, final boolean readOnly)
+    throws FileNotFoundException
+  {
+    final String mode = (readOnly ? RO_CHANNEL_MODE : RW_CHANNEL_MODE);
+    return new RandomAccessFile(mdbFile, mode).getChannel();
+  }
+  
+  /**
+   * Create a new database by reading it in from a FileChannel.
+   * @param file the File to which the channel is connected 
+   * @param channel File channel of the database.  This needs to be a
+   *    FileChannel instead of a ReadableByteChannel because we need to
+   *    randomly jump around to various points in the file.
+   * @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 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 DatabaseImpl(File file, FileChannel channel, boolean closeChannel,
+                     boolean autoSync, FileFormat fileFormat, Charset charset,
+                     TimeZone timeZone, CodecProvider provider)
+    throws IOException
+  {
+    _file = file;
+    _format = JetFormat.getFormat(channel);
+    _charset = ((charset == null) ? getDefaultCharset(_format) : charset);
+    _columnOrder = getDefaultColumnOrder();
+    _enforceForeignKeys = getDefaultEnforceForeignKeys();
+    _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();
+  }
+
+  @Override
+  public File getFile() {
+    return _file;
+  }
+
+  /**
+   * @usage _advanced_method_
+   */
+  public PageChannel getPageChannel() {
+    return _pageChannel;
+  }
+
+  /**
+   * @usage _advanced_method_
+   */
+  public JetFormat getFormat() {
+    return _format;
+  }
+  
+  @Override
+  public Table getSystemCatalog() {
+    return _systemCatalog;
+  }
+  
+  @Override
+  public Table getAccessControlEntries() throws IOException {
+    if(_accessControlEntries == null) {
+      _accessControlEntries = getSystemTable(TABLE_SYSTEM_ACES);
+      if(_accessControlEntries == null) {
+        throw new IOException("Could not find system table " +
+                              TABLE_SYSTEM_ACES);
+      }
+
+    }
+    return _accessControlEntries;
+  }
+
+  /**
+   * @return the complex column system table (loaded on demand)
+   * @usage _advanced_method_
+   */
+  public Table getSystemComplexColumns() throws IOException {
+    if(_complexCols == null) {
+      _complexCols = getSystemTable(TABLE_SYSTEM_COMPLEX_COLS);
+      if(_complexCols == null) {
+        throw new IOException("Could not find system table " +
+                              TABLE_SYSTEM_COMPLEX_COLS);
+      }
+    }
+    return _complexCols;
+  }
+
+  @Override
+  public ErrorHandler getErrorHandler() {
+    return((_dbErrorHandler != null) ? _dbErrorHandler :
+           DEFAULT_ERROR_HANDLER);
+  }
+
+  @Override
+  public void setErrorHandler(ErrorHandler newErrorHandler) {
+    _dbErrorHandler = newErrorHandler;
+  }    
+
+  @Override
+  public LinkResolver getLinkResolver() {
+    return((_linkResolver != null) ? _linkResolver : DEFAULT_LINK_RESOLVER);
+  }
+
+  @Override
+  public void setLinkResolver(LinkResolver newLinkResolver) {
+    _linkResolver = newLinkResolver;
+  }    
+
+  @Override
+  public Map<String,Database> getLinkedDatabases() {
+    return ((_linkedDbs == null) ? Collections.<String,Database>emptyMap() : 
+            Collections.unmodifiableMap(_linkedDbs));
+  }
+
+  @Override
+  public TimeZone getTimeZone() {
+    return _timeZone;
+  }
+
+  @Override
+  public void setTimeZone(TimeZone newTimeZone) {
+    if(newTimeZone == null) {
+      newTimeZone = getDefaultTimeZone();
+    }
+    _timeZone = newTimeZone;
+    // clear cached calendar when timezone is changed
+    _calendar = null;
+  }    
+
+  @Override
+  public Charset getCharset()
+  {
+    return _charset;
+  }
+
+  @Override
+  public void setCharset(Charset newCharset) {
+    if(newCharset == null) {
+      newCharset = getDefaultCharset(getFormat());
+    }
+    _charset = newCharset;
+  }
+
+  @Override
+  public Table.ColumnOrder getColumnOrder() {
+    return _columnOrder;
+  }
+
+  @Override
+  public void setColumnOrder(Table.ColumnOrder newColumnOrder) {
+    if(newColumnOrder == null) {
+      newColumnOrder = getDefaultColumnOrder();
+    }
+    _columnOrder = newColumnOrder;
+  }
+
+  @Override
+  public boolean isEnforceForeignKeys() {
+    return _enforceForeignKeys;
+  }
+
+  @Override
+  public void setEnforceForeignKeys(Boolean newEnforceForeignKeys) {
+    if(newEnforceForeignKeys == null) {
+      newEnforceForeignKeys = getDefaultEnforceForeignKeys();
+    }
+    _enforceForeignKeys = newEnforceForeignKeys;
+  }
+
+  /**
+   * @usage _advanced_method_
+   */
+  FKEnforcer.SharedState getFKEnforcerSharedState() {
+    return _fkEnforcerSharedState;
+  }
+
+  /**
+   * @usage _advanced_method_
+   */
+  Calendar getCalendar()
+  {
+    if(_calendar == null) {
+      _calendar = Calendar.getInstance(_timeZone);
+    }
+    return _calendar;
+  }
+
+  /**
+   * @returns the current handler for reading/writing properties, creating if
+   * necessary
+   */
+  private PropertyMaps.Handler getPropsHandler() {
+    if(_propsHandler == null) {
+      _propsHandler = new PropertyMaps.Handler(this);
+    }
+    return _propsHandler;
+  }
+
+  @Override
+  public FileFormat getFileFormat() throws IOException {
+
+    if(_fileFormat == null) {
+
+      Map<String,FileFormat> possibleFileFormats =
+        getFormat().getPossibleFileFormats();
+
+      if(possibleFileFormats.size() == 1) {
+
+        // single possible format (null key), easy enough
+        _fileFormat = possibleFileFormats.get(null);
+
+      } else {
+
+        // need to check the "AccessVersion" property
+        String accessVersion = (String)getDatabaseProperties().getValue(
+            PropertyMap.ACCESS_VERSION_PROP);
+        
+        _fileFormat = possibleFileFormats.get(accessVersion);
+        
+        if(_fileFormat == null) {
+          throw new IllegalStateException("Could not determine FileFormat");
+        }
+      }
+    }
+    return _fileFormat;
+  }
+
+  /**
+   * @return a (possibly cached) page ByteBuffer for internal use.  the
+   *         returned buffer should be released using
+   *         {@link #releaseSharedBuffer} when no longer in use
+   */
+  private ByteBuffer takeSharedBuffer() {
+    // we try to re-use a single shared _buffer, but occassionally, it may be
+    // needed by multiple operations at the same time (e.g. loading a
+    // secondary table while loading a primary table).  this method ensures
+    // that we don't corrupt the _buffer, but instead force the second caller
+    // to use a new buffer.
+    if(_buffer != null) {
+      ByteBuffer curBuffer = _buffer;
+      _buffer = null;
+      return curBuffer;
+    }
+    return _pageChannel.createPageBuffer();
+  }
+
+  /**
+   * Relinquishes use of a page ByteBuffer returned by
+   * {@link #takeSharedBuffer}.
+   */
+  private void releaseSharedBuffer(ByteBuffer buffer) {
+    // we always stuff the returned buffer back into _buffer.  it doesn't
+    // really matter if multiple values over-write, at the end of the day, we
+    // just need one shared buffer
+    _buffer = buffer;
+  }
+  
+  /**
+   * @return the currently configured database default language sort order for
+   *         textual columns
+   * @usage _intermediate_method_
+   */
+  public Column.SortOrder getDefaultSortOrder() throws IOException {
+
+    if(_defaultSortOrder == null) {
+      initRootPageInfo();
+    }
+    return _defaultSortOrder;
+  }
+
+  /**
+   * @return the currently configured database default code page for textual
+   *         data (may not be relevant to all database versions)
+   * @usage _intermediate_method_
+   */
+  public short getDefaultCodePage() throws IOException {
+
+    if(_defaultCodePage == null) {
+      initRootPageInfo();
+    }
+    return _defaultCodePage;
+  }
+
+  /**
+   * Reads various config info from the db page 0.
+   */
+  private void initRootPageInfo() throws IOException {
+    ByteBuffer buffer = takeSharedBuffer();
+    try {
+      _pageChannel.readPage(buffer, 0);
+      _defaultSortOrder = Column.readSortOrder(
+          buffer, _format.OFFSET_SORT_ORDER, _format);
+      _defaultCodePage = buffer.getShort(_format.OFFSET_CODE_PAGE);
+    } finally {
+      releaseSharedBuffer(buffer);
+    }
+  }
+  
+  /**
+   * @return a PropertyMaps instance decoded from the given bytes (always
+   *         returns non-{@code null} result).
+   * @usage _intermediate_method_
+   */
+  public PropertyMaps readProperties(byte[] propsBytes, int objectId)
+    throws IOException 
+  {
+    return getPropsHandler().read(propsBytes, objectId);
+  }
+  
+  /**
+   * Read the system catalog
+   */
+  private void readSystemCatalog() throws IOException {
+    _systemCatalog = readTable(TABLE_SYSTEM_CATALOG, PAGE_SYSTEM_CATALOG,
+                               SYSTEM_OBJECT_FLAGS);
+
+    try {
+      _tableFinder = new DefaultTableFinder(
+          new CursorBuilder(_systemCatalog)
+            .setIndexByColumnNames(CAT_COL_PARENT_ID, CAT_COL_NAME)
+            .setColumnMatcher(CaseInsensitiveColumnMatcher.INSTANCE)
+            .toIndexCursor());
+    } catch(IllegalArgumentException e) {
+      LOG.info("Could not find expected index on table " + 
+               _systemCatalog.getName());
+      // use table scan instead
+      _tableFinder = new FallbackTableFinder(
+          new CursorBuilder(_systemCatalog)
+            .setColumnMatcher(CaseInsensitiveColumnMatcher.INSTANCE)
+            .toCursor());
+    }
+
+    _tableParentId = _tableFinder.findObjectId(DB_PARENT_ID, 
+                                               SYSTEM_OBJECT_NAME_TABLES);
+
+    if(_tableParentId == null) {  
+      throw new IOException("Did not find required parent table id");
+    }
+
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Finished reading system catalog.  Tables: " +
+                getTableNames());
+    }
+  }
+  
+  @Override
+  public Set<String> getTableNames() throws IOException {
+    if(_tableNames == null) {
+      Set<String> tableNames =
+        new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
+      _tableFinder.getTableNames(tableNames, false);
+      _tableNames = tableNames;
+    }
+    return _tableNames;
+  }
+
+  @Override
+  public Set<String> getSystemTableNames() throws IOException {
+    Set<String> sysTableNames =
+      new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
+    _tableFinder.getTableNames(sysTableNames, true);
+    return sysTableNames;
+  }
+
+  @Override
+  public Iterator<Table> iterator() {
+    return new TableIterator();
+  }
+
+  @Override
+  public Table getTable(String name) throws IOException {
+    return getTable(name, false);
+  }
+
+  /**
+   * @param tableDefPageNumber the page number of a table definition
+   * @return The table, or null if it doesn't exist
+   * @usage _advanced_method_
+   */
+  public Table getTable(int tableDefPageNumber) throws IOException {
+
+    // first, check for existing table
+    Table table = _tableCache.get(tableDefPageNumber);
+    if(table != null) {
+      return table;
+    }
+    
+    // lookup table info from system catalog
+    Map<String,Object> objectRow = _tableFinder.getObjectRow(
+        tableDefPageNumber, SYSTEM_CATALOG_COLUMNS);
+    if(objectRow == null) {
+      return null;
+    }
+
+    String name = (String)objectRow.get(CAT_COL_NAME);
+    int flags = (Integer)objectRow.get(CAT_COL_FLAGS);
+
+    return readTable(name, tableDefPageNumber, flags);
+  }
+
+  /**
+   * @param name Table name
+   * @param includeSystemTables whether to consider returning a system table
+   * @return The table, or null if it doesn't exist
+   */
+  private Table getTable(String name, boolean includeSystemTables) 
+    throws IOException 
+  {
+    TableInfo tableInfo = lookupTable(name);
+    
+    if ((tableInfo == null) || (tableInfo.pageNumber == null)) {
+      return null;
+    }
+    if(!includeSystemTables && isSystemObject(tableInfo.flags)) {
+      return null;
+    }
+
+    if(tableInfo.isLinked()) {
+
+      if(_linkedDbs == null) {
+        _linkedDbs = new HashMap<String,Database>();
+      }
+
+      String linkedDbName = ((LinkedTableInfo)tableInfo).linkedDbName;
+      String linkedTableName = ((LinkedTableInfo)tableInfo).linkedTableName;
+      Database linkedDb = _linkedDbs.get(linkedDbName);
+      if(linkedDb == null) {
+        linkedDb = getLinkResolver().resolveLinkedDatabase(this, linkedDbName);
+        _linkedDbs.put(linkedDbName, linkedDb);
+      }
+      
+      return ((DatabaseImpl)linkedDb).getTable(linkedTableName, 
+                                               includeSystemTables);
+    }
+
+    return readTable(tableInfo.tableName, tableInfo.pageNumber,
+                     tableInfo.flags);
+  }
+  
+  /**
+   * Create a new table in this database
+   * @param name Name of the table to create
+   * @param columns List of Columns in the table
+   * @usage _general_method_
+   */
+  public void createTable(String name, List<Column> columns)
+    throws IOException
+  {
+    createTable(name, columns, null);
+  }
+
+  /**
+   * Create a new table in this database
+   * @param name Name of the table to create
+   * @param columns List of Columns in the table
+   * @param indexes List of IndexBuilders describing indexes for the table
+   * @usage _general_method_
+   */
+  public void createTable(String name, List<Column> columns,
+                          List<IndexBuilder> indexes)
+    throws IOException
+  {
+    // FIXME, rework table creation
+    if(lookupTable(name) != null) {
+      throw new IllegalArgumentException(
+          "Cannot create table with name of existing table");
+    }
+
+    new TableCreator(this, name, columns, indexes).createTable();
+  }
+
+  /**
+   * Create a new table in this database
+   * @param name Name of the table to create
+   * @usage _general_method_
+   */
+  public void createLinkedTable(String name, String linkedDbName, 
+                                String linkedTableName)
+    throws IOException
+  {
+    // FIXME, rework table creation
+    if(lookupTable(name) != null) {
+      throw new IllegalArgumentException(
+          "Cannot create linked table with name of existing table");
+    }
+
+    validateIdentifierName(name, getFormat().MAX_TABLE_NAME_LENGTH, "table");
+    validateIdentifierName(linkedDbName, DataType.MEMO.getMaxSize(), 
+                           "linked database");
+    validateIdentifierName(linkedTableName, getFormat().MAX_TABLE_NAME_LENGTH, 
+                           "linked table");
+
+    int linkedTableId = _tableFinder.getNextFreeSyntheticId();
+
+    addNewTable(name, linkedTableId, TYPE_LINKED_TABLE, linkedDbName, 
+                linkedTableName);
+  }
+
+  /**
+   * Adds a newly created table to the relevant internal database structures.
+   */
+  void addNewTable(String name, int tdefPageNumber, Short type, 
+                   String linkedDbName, String linkedTableName) 
+    throws IOException 
+  {
+    //Add this table to our internal list.
+    addTable(name, Integer.valueOf(tdefPageNumber), type, linkedDbName,
+             linkedTableName);
+    
+    //Add this table to system tables
+    addToSystemCatalog(name, tdefPageNumber, type, linkedDbName, 
+                       linkedTableName);
+    addToAccessControlEntries(tdefPageNumber);
+  }
+
+  @Override
+  public List<Relationship> getRelationships(Table table1, Table table2)
+    throws IOException
+  {
+    // the relationships table does not get loaded until first accessed
+    if(_relationships == null) {
+      _relationships = getSystemTable(TABLE_SYSTEM_RELATIONSHIPS);
+      if(_relationships == null) {
+        throw new IOException("Could not find system relationships table");
+      }
+    }
+
+    int nameCmp = table1.getName().compareTo(table2.getName());
+    if(nameCmp == 0) {
+      throw new IllegalArgumentException("Must provide two different tables");
+    }
+    if(nameCmp > 0) {
+      // we "order" the two tables given so that we will return a collection
+      // of relationships in the same order regardless of whether we are given
+      // (TableFoo, TableBar) or (TableBar, TableFoo).
+      Table tmp = table1;
+      table1 = table2;
+      table2 = tmp;
+    }
+      
+
+    List<Relationship> relationships = new ArrayList<Relationship>();
+    Cursor cursor = createCursorWithOptionalIndex(
+        _relationships, REL_COL_FROM_TABLE, table1.getName());
+    collectRelationships(cursor, table1, table2, relationships);
+    cursor = createCursorWithOptionalIndex(
+        _relationships, REL_COL_TO_TABLE, table1.getName());
+    collectRelationships(cursor, table2, table1, relationships);
+    
+    return relationships;
+  }
+
+  @Override
+  public List<Query> getQueries() throws IOException
+  {
+    // the queries table does not get loaded until first accessed
+    if(_queries == null) {
+      _queries = getSystemTable(TABLE_SYSTEM_QUERIES);
+      if(_queries == null) {
+        throw new IOException("Could not find system queries table");
+      }
+    }
+
+    // find all the queries from the system catalog
+    List<Map<String,Object>> queryInfo = new ArrayList<Map<String,Object>>();
+    Map<Integer,List<Query.Row>> queryRowMap = 
+      new HashMap<Integer,List<Query.Row>>();
+    for(Map<String,Object> row :
+          Cursor.createCursor(_systemCatalog).iterable(SYSTEM_CATALOG_COLUMNS))
+    {
+      String name = (String) row.get(CAT_COL_NAME);
+      if (name != null && TYPE_QUERY.equals(row.get(CAT_COL_TYPE))) {
+        queryInfo.add(row);
+        Integer id = (Integer)row.get(CAT_COL_ID);
+        queryRowMap.put(id, new ArrayList<Query.Row>());
+      }
+    }
+
+    // find all the query rows
+    for(Map<String,Object> row : Cursor.createCursor(_queries)) {
+      Query.Row queryRow = new Query.Row(row);
+      List<Query.Row> queryRows = queryRowMap.get(queryRow.objectId);
+      if(queryRows == null) {
+        LOG.warn("Found rows for query with id " + queryRow.objectId +
+                 " missing from system catalog");
+        continue;
+      }
+      queryRows.add(queryRow);
+    }
+
+    // lastly, generate all the queries
+    List<Query> queries = new ArrayList<Query>();
+    for(Map<String,Object> row : queryInfo) {
+      String name = (String) row.get(CAT_COL_NAME);
+      Integer id = (Integer)row.get(CAT_COL_ID);
+      int flags = (Integer)row.get(CAT_COL_FLAGS);
+      List<Query.Row> queryRows = queryRowMap.get(id);
+      queries.add(Query.create(flags, name, queryRows, id));
+    }
+
+    return queries;
+  }
+
+  @Override
+  public Table getSystemTable(String tableName) throws IOException
+  {
+    return getTable(tableName, true);
+  }
+
+  @Override
+  public PropertyMap getDatabaseProperties() throws IOException {
+    if(_dbPropMaps == null) {
+      _dbPropMaps = getPropertiesForDbObject(OBJECT_NAME_DB_PROPS);
+    }
+    return _dbPropMaps.getDefault();
+  }
+
+  @Override
+  public PropertyMap getSummaryProperties() throws IOException {
+    if(_summaryPropMaps == null) {
+      _summaryPropMaps = getPropertiesForDbObject(OBJECT_NAME_SUMMARY_PROPS);
+    }
+    return _summaryPropMaps.getDefault();
+  }
+
+  @Override
+  public PropertyMap getUserDefinedProperties() throws IOException {
+    if(_userDefPropMaps == null) {
+      _userDefPropMaps = getPropertiesForDbObject(OBJECT_NAME_USERDEF_PROPS);
+    }
+    return _userDefPropMaps.getDefault();
+  }
+
+  /**
+   * @return the PropertyMaps for the object with the given id
+   * @usage _advanced_method_
+   */
+  public PropertyMaps getPropertiesForObject(int objectId)
+    throws IOException
+  {
+    Map<String,Object> objectRow = _tableFinder.getObjectRow(
+        objectId, SYSTEM_CATALOG_PROPS_COLUMNS);
+    byte[] propsBytes = null;
+    if(objectRow != null) {
+      propsBytes = (byte[])objectRow.get(CAT_COL_PROPS);
+    }
+    return readProperties(propsBytes, objectId);
+  }
+
+  /**
+   * @return property group for the given "database" object
+   */
+  private PropertyMaps getPropertiesForDbObject(String dbName)
+    throws IOException
+  {
+    if(_dbParentId == null) {
+      // need the parent if of the databases objects
+      _dbParentId = _tableFinder.findObjectId(DB_PARENT_ID, 
+                                              SYSTEM_OBJECT_NAME_DATABASES);
+      if(_dbParentId == null) {  
+        throw new IOException("Did not find required parent db id");
+      }
+    }
+
+    Map<String,Object> objectRow = _tableFinder.getObjectRow(
+        _dbParentId, dbName, SYSTEM_CATALOG_PROPS_COLUMNS);
+    byte[] propsBytes = null;
+    int objectId = -1;
+    if(objectRow != null) {
+      propsBytes = (byte[])objectRow.get(CAT_COL_PROPS);
+      objectId = (Integer)objectRow.get(CAT_COL_ID);
+    }
+    return readProperties(propsBytes, objectId);
+  }
+
+  @Override
+  public String getDatabasePassword() throws IOException
+  {
+    ByteBuffer buffer = takeSharedBuffer();
+    try {
+      _pageChannel.readPage(buffer, 0);
+
+      byte[] pwdBytes = new byte[_format.SIZE_PASSWORD];
+      buffer.position(_format.OFFSET_PASSWORD);
+      buffer.get(pwdBytes);
+
+      // de-mask password using extra password mask if necessary (the extra
+      // password mask is generated from the database creation date stored in
+      // the header)
+      byte[] pwdMask = getPasswordMask(buffer, _format);
+      if(pwdMask != null) {
+        for(int i = 0; i < pwdBytes.length; ++i) {
+          pwdBytes[i] ^= pwdMask[i % pwdMask.length];
+        }
+      }
+    
+      boolean hasPassword = false;
+      for(int i = 0; i < pwdBytes.length; ++i) {
+        if(pwdBytes[i] != 0) {
+          hasPassword = true;
+          break;
+        }
+      }
+
+      if(!hasPassword) {
+        return null;
+      }
+
+      String pwd = Column.decodeUncompressedText(pwdBytes, getCharset());
+
+      // remove any trailing null chars
+      int idx = pwd.indexOf('\0');
+      if(idx >= 0) {
+        pwd = pwd.substring(0, idx);
+      }
+
+      return pwd;
+    } finally {
+      releaseSharedBuffer(buffer);
+    }
+  }
+
+  /**
+   * Finds the relationships matching the given from and to tables from the
+   * given cursor and adds them to the given list.
+   */
+  private static void collectRelationships(
+      Cursor cursor, Table fromTable, Table toTable,
+      List<Relationship> relationships)
+  {
+    for(Map<String,Object> row : cursor) {
+      String fromName = (String)row.get(REL_COL_FROM_TABLE);
+      String toName = (String)row.get(REL_COL_TO_TABLE);
+      
+      if(fromTable.getName().equalsIgnoreCase(fromName) &&
+         toTable.getName().equalsIgnoreCase(toName))
+      {
+
+        String relName = (String)row.get(REL_COL_NAME);
+        
+        // found more info for a relationship.  see if we already have some
+        // info for this relationship
+        Relationship rel = null;
+        for(Relationship tmp : relationships) {
+          if(tmp.getName().equalsIgnoreCase(relName)) {
+            rel = tmp;
+            break;
+          }
+        }
+
+        if(rel == null) {
+          // new relationship
+          int numCols = (Integer)row.get(REL_COL_COLUMN_COUNT);
+          int flags = (Integer)row.get(REL_COL_FLAGS);
+          rel = new Relationship(relName, fromTable, toTable,
+                                 flags, numCols);
+          relationships.add(rel);
+        }
+
+        // add column info
+        int colIdx = (Integer)row.get(REL_COL_COLUMN_INDEX);
+        Column fromCol = fromTable.getColumn(
+            (String)row.get(REL_COL_FROM_COLUMN));
+        Column toCol = toTable.getColumn(
+            (String)row.get(REL_COL_TO_COLUMN));
+
+        rel.getFromColumns().set(colIdx, fromCol);
+        rel.getToColumns().set(colIdx, toCol);
+      }
+    }    
+  }
+  
+  /**
+   * Add a new table to the system catalog
+   * @param name Table name
+   * @param pageNumber Page number that contains the table definition
+   */
+  private void addToSystemCatalog(String name, int pageNumber, Short type, 
+                                  String linkedDbName, String linkedTableName)
+    throws IOException
+  {
+    Object[] catalogRow = new Object[_systemCatalog.getColumnCount()];
+    int idx = 0;
+    Date creationTime = new Date();
+    for (Iterator<Column> iter = _systemCatalog.getColumns().iterator();
+         iter.hasNext(); idx++)
+    {
+      Column col = iter.next();
+      if (CAT_COL_ID.equals(col.getName())) {
+        catalogRow[idx] = Integer.valueOf(pageNumber);
+      } else if (CAT_COL_NAME.equals(col.getName())) {
+        catalogRow[idx] = name;
+      } else if (CAT_COL_TYPE.equals(col.getName())) {
+        catalogRow[idx] = type;
+      } else if (CAT_COL_DATE_CREATE.equals(col.getName()) ||
+                 CAT_COL_DATE_UPDATE.equals(col.getName())) {
+        catalogRow[idx] = creationTime;
+      } else if (CAT_COL_PARENT_ID.equals(col.getName())) {
+        catalogRow[idx] = _tableParentId;
+      } else if (CAT_COL_FLAGS.equals(col.getName())) {
+        catalogRow[idx] = Integer.valueOf(0);
+      } else if (CAT_COL_OWNER.equals(col.getName())) {
+        byte[] owner = new byte[2];
+        catalogRow[idx] = owner;
+        owner[0] = (byte) 0xcf;
+        owner[1] = (byte) 0x5f;
+      } else if (CAT_COL_DATABASE.equals(col.getName())) {
+        catalogRow[idx] = linkedDbName;
+      } else if (CAT_COL_FOREIGN_NAME.equals(col.getName())) {
+        catalogRow[idx] = linkedTableName;
+      }
+    }
+    _systemCatalog.addRow(catalogRow);
+  }
+  
+  /**
+   * Add a new table to the system's access control entries
+   * @param pageNumber Page number that contains the table definition
+   */
+  private void addToAccessControlEntries(int pageNumber) throws IOException {
+
+    if(_newTableSIDs.isEmpty()) {
+      initNewTableSIDs();
+    }
+
+    Table acEntries = getAccessControlEntries();
+    Column acmCol = acEntries.getColumn(ACE_COL_ACM);
+    Column inheritCol = acEntries.getColumn(ACE_COL_F_INHERITABLE);
+    Column objIdCol = acEntries.getColumn(ACE_COL_OBJECT_ID);
+    Column sidCol = acEntries.getColumn(ACE_COL_SID);
+
+    // construct a collection of ACE entries mimicing those of our parent, the
+    // "Tables" system object
+    List<Object[]> aceRows = new ArrayList<Object[]>(_newTableSIDs.size());
+    for(byte[] sid : _newTableSIDs) {
+      Object[] aceRow = new Object[acEntries.getColumnCount()];
+      acmCol.setRowValue(aceRow, SYS_FULL_ACCESS_ACM);
+      inheritCol.setRowValue(aceRow, Boolean.FALSE);
+      objIdCol.setRowValue(aceRow, Integer.valueOf(pageNumber));
+      sidCol.setRowValue(aceRow, sid);
+      aceRows.add(aceRow);
+    }
+    acEntries.addRows(aceRows);  
+  }
+
+  /**
+   * Determines the collection of SIDs which need to be added to new tables.
+   */
+  private void initNewTableSIDs() throws IOException
+  {
+    // search for ACEs matching the tableParentId.  use the index on the
+    // objectId column if found (should be there)
+    Cursor cursor = createCursorWithOptionalIndex(
+        getAccessControlEntries(), ACE_COL_OBJECT_ID, _tableParentId);
+    
+    for(Map<String, Object> row : cursor) {
+      Integer objId = (Integer)row.get(ACE_COL_OBJECT_ID);
+      if(_tableParentId.equals(objId)) {
+        _newTableSIDs.add((byte[])row.get(ACE_COL_SID));
+      }
+    }
+
+    if(_newTableSIDs.isEmpty()) {
+      // if all else fails, use the hard-coded default
+      _newTableSIDs.add(SYS_DEFAULT_SID);
+    }
+  }
+
+  /**
+   * Reads a table with the given name from the given pageNumber.
+   */
+  private Table readTable(String name, int pageNumber, int flags)
+    throws IOException
+  {
+    // first, check for existing table
+    Table table = _tableCache.get(pageNumber);
+    if(table != null) {
+      return table;
+    }
+    
+    ByteBuffer buffer = takeSharedBuffer();
+    try {
+      // need to load table from db
+      _pageChannel.readPage(buffer, pageNumber);
+      byte pageType = buffer.get(0);
+      if (pageType != PageTypes.TABLE_DEF) {
+        throw new IOException(
+            "Looking for " + name + " at page " + pageNumber +
+            ", but page type is " + pageType);
+      }
+      return _tableCache.put(
+          new Table(this, buffer, pageNumber, name, flags));
+    } finally {
+      releaseSharedBuffer(buffer);
+    }
+  }
+
+  /**
+   * Creates a Cursor restricted to the given column value if possible (using
+   * an existing index), otherwise a simple table cursor.
+   */
+  private static Cursor createCursorWithOptionalIndex(
+      Table table, String colName, Object colValue)
+    throws IOException
+  {
+    try {
+      return new CursorBuilder(table)
+        .setIndexByColumns(table.getColumn(colName))
+        .setSpecificEntry(colValue)
+        .toCursor();
+    } catch(IllegalArgumentException e) {
+      LOG.info("Could not find expected index on table " + table.getName());
+    }
+    // use table scan instead
+    return Cursor.createCursor(table);
+  }
+  
+  @Override
+  public void flush() throws IOException {
+    if(_linkedDbs != null) {
+      for(Database linkedDb : _linkedDbs.values()) {
+        linkedDb.flush();
+      }
+    }
+    _pageChannel.flush();
+  }
+  
+  @Override
+  public void close() throws IOException {
+    if(_linkedDbs != null) {
+      for(Database linkedDb : _linkedDbs.values()) {
+        linkedDb.close();
+      }
+    }
+    _pageChannel.close();
+  }
+  
+  /**
+   * @return A table or column name escaped for Access
+   * @usage _general_method_
+   */
+  public static String escapeIdentifier(String s) {
+    if (isReservedWord(s)) {
+      return ESCAPE_PREFIX + s; 
+    }
+    return s;
+  }
+
+  /**
+   * @return {@code true} if the given string is a reserved word,
+   *         {@code false} otherwise
+   * @usage _general_method_
+   */
+  public static boolean isReservedWord(String s) {
+    return RESERVED_WORDS.contains(s.toLowerCase());
+  }
+
+  /**
+   * Validates an identifier name.
+   * @usage _advanced_method_
+   */
+  public static void validateIdentifierName(String name,
+                                            int maxLength,
+                                            String identifierType)
+  {
+    if((name == null) || (name.trim().length() == 0)) {
+      throw new IllegalArgumentException(
+          identifierType + " must have non-empty name");
+    }
+    if(name.length() > maxLength) {
+      throw new IllegalArgumentException(
+          identifierType + " name is longer than max length of " + maxLength +
+          ": " + name);
+    }
+  }
+  
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
+
+  /**
+   * Adds a table to the _tableLookup and resets the _tableNames set
+   */
+  private void addTable(String tableName, Integer pageNumber, Short type, 
+                        String linkedDbName, String linkedTableName)
+  {
+    _tableLookup.put(toLookupName(tableName),
+                     createTableInfo(tableName, pageNumber, 0, type, 
+                                     linkedDbName, linkedTableName));
+    // clear this, will be created next time needed
+    _tableNames = null;
+  }
+
+  /**
+   * Creates a TableInfo instance appropriate for the given table data.
+   */
+  private static TableInfo createTableInfo(
+      String tableName, Integer pageNumber, int flags, Short type, 
+      String linkedDbName, String linkedTableName)
+  {
+    if(TYPE_LINKED_TABLE.equals(type)) {
+      return new LinkedTableInfo(pageNumber, tableName, flags, linkedDbName,
+                                 linkedTableName);
+    }
+    return new TableInfo(pageNumber, tableName, flags);
+  }
+
+  /**
+   * @return the tableInfo of the given table, if any
+   */
+  private TableInfo lookupTable(String tableName) throws IOException {
+
+    String lookupTableName = toLookupName(tableName);
+    TableInfo tableInfo = _tableLookup.get(lookupTableName);
+    if(tableInfo != null) {
+      return tableInfo;
+    }
+
+    tableInfo = _tableFinder.lookupTable(tableName);
+
+    if(tableInfo != null) {
+      // cache for later
+      _tableLookup.put(lookupTableName, tableInfo);
+    }
+
+    return tableInfo;
+  }
+
+  /**
+   * @return a string usable in the _tableLookup map.
+   */
+  static String toLookupName(String name) {
+    return ((name != null) ? name.toUpperCase() : null);
+  }
+
+  /**
+   * @return {@code true} if the given flags indicate that an object is some
+   *         sort of system object, {@code false} otherwise.
+   */
+  private static boolean isSystemObject(int flags) {
+    return ((flags & SYSTEM_OBJECT_FLAGS) != 0);
+  }
+
+  /**
+   * Returns the default TimeZone.  This is normally the platform default
+   * TimeZone as returned by {@link TimeZone#getDefault}, but can be
+   * overridden using the system property {@value #TIMEZONE_PROPERTY}.
+   * @usage _advanced_method_
+   */
+  public static TimeZone getDefaultTimeZone()
+  {
+    String tzProp = System.getProperty(TIMEZONE_PROPERTY);
+    if(tzProp != null) {
+      tzProp = tzProp.trim();
+      if(tzProp.length() > 0) {
+        return TimeZone.getTimeZone(tzProp);
+      }
+    }
+
+    // use system default
+    return TimeZone.getDefault();
+  }
+  
+  /**
+   * Returns the default Charset for the given JetFormat.  This may or may not
+   * be platform specific, depending on the format, but can be overridden
+   * using a system property composed of the prefix
+   * {@value #CHARSET_PROPERTY_PREFIX} followed by the JetFormat version to
+   * which the charset should apply, e.g. {@code
+   * "com.healthmarketscience.jackcess.charset.VERSION_3"}.
+   * @usage _advanced_method_
+   */
+  public static Charset getDefaultCharset(JetFormat format)
+  {
+    String csProp = System.getProperty(CHARSET_PROPERTY_PREFIX + format);
+    if(csProp != null) {
+      csProp = csProp.trim();
+      if(csProp.length() > 0) {
+        return Charset.forName(csProp);
+      }
+    }
+
+    // use format default
+    return format.CHARSET;
+  }
+  
+  /**
+   * Returns the default Table.ColumnOrder.  This defaults to
+   * {@link #DEFAULT_COLUMN_ORDER}, but can be overridden using the system
+   * property {@value #COLUMN_ORDER_PROPERTY}.
+   * @usage _advanced_method_
+   */
+  public static Table.ColumnOrder getDefaultColumnOrder()
+  {
+    String coProp = System.getProperty(COLUMN_ORDER_PROPERTY);
+    if(coProp != null) {
+      coProp = coProp.trim();
+      if(coProp.length() > 0) {
+        return Table.ColumnOrder.valueOf(coProp);
+      }
+    }
+
+    // use default order
+    return DEFAULT_COLUMN_ORDER;
+  }
+  
+  /**
+   * Returns the default enforce foreign-keys policy.  This defaults to
+   * {@code true}, but can be overridden using the system
+   * property {@value #FK_ENFORCE_PROPERTY}.
+   * @usage _advanced_method_
+   */
+  public static boolean getDefaultEnforceForeignKeys()
+  {
+    String prop = System.getProperty(FK_ENFORCE_PROPERTY);
+    if(prop != null) {
+      return Boolean.TRUE.toString().equalsIgnoreCase(prop);
+    }
+    return true;
+  }
+  
+  /**
+   * Copies the given InputStream to the given channel using the most
+   * efficient means possible.
+   */
+  private static void transferFrom(FileChannel channel, InputStream in)
+    throws IOException
+  {
+    ReadableByteChannel readChannel = Channels.newChannel(in);
+    if(!BROKEN_NIO) {
+      // sane implementation
+      channel.transferFrom(readChannel, 0, MAX_EMPTYDB_SIZE);    
+    } else {
+      // do things the hard way for broken vms
+      ByteBuffer bb = ByteBuffer.allocate(8096);
+      while(readChannel.read(bb) >= 0) {
+        bb.flip();
+        channel.write(bb);
+        bb.clear();
+      }
+    }
+  }
+
+  /**
+   * Returns the password mask retrieved from the given header page and
+   * format, or {@code null} if this format does not use a password mask.
+   */
+  static byte[] getPasswordMask(ByteBuffer buffer, JetFormat format)
+  {
+    // get extra password mask if necessary (the extra password mask is
+    // generated from the database creation date stored in the header)
+    int pwdMaskPos = format.OFFSET_HEADER_DATE;
+    if(pwdMaskPos < 0) {
+      return null;
+    }
+
+    buffer.position(pwdMaskPos);
+    double dateVal = Double.longBitsToDouble(buffer.getLong());
+
+    byte[] pwdMask = new byte[4];
+    ByteBuffer.wrap(pwdMask).order(PageChannel.DEFAULT_BYTE_ORDER)
+      .putInt((int)dateVal);
+
+    return pwdMask;
+  }
+
+  static InputStream getResourceAsStream(String resourceName)
+    throws IOException
+  {
+    InputStream stream = DatabaseImpl.class.getClassLoader()
+      .getResourceAsStream(resourceName);
+    
+    if(stream == null) {
+      
+      stream = Thread.currentThread().getContextClassLoader()
+        .getResourceAsStream(resourceName);
+      
+      if(stream == null) {
+        throw new IOException("Could not load jackcess resource " +
+                              resourceName);
+      }
+    }
+
+    return stream;
+  }
+
+  private static boolean isTableType(Short objType) {
+    return(TYPE_TABLE.equals(objType) || TYPE_LINKED_TABLE.equals(objType));
+  }
+
+  /**
+   * Utility class for storing table page number and actual name.
+   */
+  private static class TableInfo
+  {
+    public final Integer pageNumber;
+    public final String tableName;
+    public final int flags;
+
+    private TableInfo(Integer newPageNumber, String newTableName, int newFlags) {
+      pageNumber = newPageNumber;
+      tableName = newTableName;
+      flags = newFlags;
+    }
+
+    public boolean isLinked() {
+      return false;
+    }
+  }
+
+  /**
+   * Utility class for storing linked table info
+   */
+  private static class LinkedTableInfo extends TableInfo
+  {
+    private final String linkedDbName;
+    private final String linkedTableName;
+
+    private LinkedTableInfo(Integer newPageNumber, String newTableName, 
+                            int newFlags, String newLinkedDbName, 
+                            String newLinkedTableName) {
+      super(newPageNumber, newTableName, newFlags);
+      linkedDbName = newLinkedDbName;
+      linkedTableName = newLinkedTableName;
+    }
+
+    @Override
+    public boolean isLinked() {
+      return true;
+    }
+  }
+
+  /**
+   * Table iterator for this database, unmodifiable.
+   */
+  private class TableIterator implements Iterator<Table>
+  {
+    private Iterator<String> _tableNameIter;
+
+    private TableIterator() {
+      try {
+        _tableNameIter = getTableNames().iterator();
+      } catch(IOException e) {
+        throw new IllegalStateException(e);
+      }
+    }
+
+    public boolean hasNext() {
+      return _tableNameIter.hasNext();
+    }
+
+    public void remove() {
+      throw new UnsupportedOperationException();
+    }
+
+    public Table next() {
+      if(!hasNext()) {
+        throw new NoSuchElementException();
+      }
+      try {
+        return getTable(_tableNameIter.next());
+      } catch(IOException e) {
+        throw new IllegalStateException(e);
+      }
+    }
+  }
+
+  /**
+   * Utility class for handling table lookups.
+   */
+  private abstract class TableFinder
+  {
+    public Integer findObjectId(Integer parentId, String name) 
+      throws IOException 
+    {
+      Cursor cur = findRow(parentId, name);
+      if(cur == null) {  
+        return null;
+      }
+      Column idCol = _systemCatalog.getColumn(CAT_COL_ID);
+      return (Integer)cur.getCurrentRowValue(idCol);
+    }
+
+    public Map<String,Object> getObjectRow(Integer parentId, String name,
+                                           Collection<String> columns) 
+      throws IOException 
+    {
+      Cursor cur = findRow(parentId, name);
+      return ((cur != null) ? cur.getCurrentRow(columns) : null);
+    }
+
+    public Map<String,Object> getObjectRow(
+        Integer objectId, Collection<String> columns)
+      throws IOException
+    {
+      Cursor cur = findRow(objectId);
+      return ((cur != null) ? cur.getCurrentRow(columns) : null);
+    }
+
+    public void getTableNames(Set<String> tableNames,
+                              boolean systemTables)
+      throws IOException
+    {
+      for(Map<String,Object> row : getTableNamesCursor().iterable(
+              SYSTEM_CATALOG_TABLE_NAME_COLUMNS)) {
+
+        String tableName = (String)row.get(CAT_COL_NAME);
+        int flags = (Integer)row.get(CAT_COL_FLAGS);
+        Short type = (Short)row.get(CAT_COL_TYPE);
+        int parentId = (Integer)row.get(CAT_COL_PARENT_ID);
+
+        if((parentId == _tableParentId) && isTableType(type) && 
+           (isSystemObject(flags) == systemTables)) {
+          tableNames.add(tableName);
+        }
+      }
+    }
+
+    protected abstract Cursor findRow(Integer parentId, String name)
+      throws IOException;
+
+    protected abstract Cursor findRow(Integer objectId) 
+      throws IOException;
+
+    protected abstract Cursor getTableNamesCursor() throws IOException;
+
+    public abstract TableInfo lookupTable(String tableName)
+      throws IOException;
+
+    protected abstract int findMaxSyntheticId() throws IOException;
+
+    public int getNextFreeSyntheticId() throws IOException
+    {
+      int maxSynthId = findMaxSyntheticId();
+      if(maxSynthId >= -1) {
+        // bummer, no more ids available
+        throw new IllegalStateException("Too many database objects!");
+      }
+      return maxSynthId + 1;
+    }
+  }
+
+  /**
+   * Normal table lookup handler, using catalog table index.
+   */
+  private final class DefaultTableFinder extends TableFinder
+  {
+    private final IndexCursor _systemCatalogCursor;
+    private IndexCursor _systemCatalogIdCursor;
+
+    private DefaultTableFinder(IndexCursor systemCatalogCursor) {
+      _systemCatalogCursor = systemCatalogCursor;
+    }
+    
+    private void initIdCursor() throws IOException {
+      if(_systemCatalogIdCursor == null) {
+        _systemCatalogIdCursor = new CursorBuilder(_systemCatalog)
+          .setIndexByColumnNames(CAT_COL_ID)
+          .toIndexCursor();
+      }
+    }
+
+    @Override
+    protected Cursor findRow(Integer parentId, String name) 
+      throws IOException 
+    {
+      return (_systemCatalogCursor.findFirstRowByEntry(parentId, name) ?
+              _systemCatalogCursor : null);
+    }
+
+    @Override
+    protected Cursor findRow(Integer objectId) throws IOException 
+    {
+      initIdCursor();
+      return (_systemCatalogIdCursor.findFirstRowByEntry(objectId) ?
+              _systemCatalogIdCursor : null);
+    }
+
+    @Override
+    public TableInfo lookupTable(String tableName) throws IOException {
+
+      if(findRow(_tableParentId, tableName) == null) {
+        return null;
+      }
+
+      Map<String,Object> row = _systemCatalogCursor.getCurrentRow(
+          SYSTEM_CATALOG_COLUMNS);
+      Integer pageNumber = (Integer)row.get(CAT_COL_ID);
+      String realName = (String)row.get(CAT_COL_NAME);
+      int flags = (Integer)row.get(CAT_COL_FLAGS);
+      Short type = (Short)row.get(CAT_COL_TYPE);
+
+      if(!isTableType(type)) {
+        return null;
+      }
+
+      String linkedDbName = (String)row.get(CAT_COL_DATABASE);
+      String linkedTableName = (String)row.get(CAT_COL_FOREIGN_NAME);
+
+      return createTableInfo(realName, pageNumber, flags, type, linkedDbName,
+                             linkedTableName);
+    }
+    
+    @Override
+    protected Cursor getTableNamesCursor() throws IOException {
+      return new CursorBuilder(_systemCatalog)
+        .setIndex(_systemCatalogCursor.getIndex())
+        .setStartEntry(_tableParentId, IndexData.MIN_VALUE)
+        .setEndEntry(_tableParentId, IndexData.MAX_VALUE)
+        .toIndexCursor();
+    }
+
+    @Override
+    protected int findMaxSyntheticId() throws IOException {
+      initIdCursor();
+      _systemCatalogIdCursor.reset();
+
+      // synthetic ids count up from min integer.  so the current, highest,
+      // in-use synthetic id is the max id < 0.
+      _systemCatalogIdCursor.findClosestRowByEntry(0);
+      if(!_systemCatalogIdCursor.moveToPreviousRow()) {
+        return Integer.MIN_VALUE;
+      }
+      Column idCol = _systemCatalog.getColumn(CAT_COL_ID);
+      return (Integer)_systemCatalogIdCursor.getCurrentRowValue(idCol);
+    }
+  }
+  
+  /**
+   * Fallback table lookup handler, using catalog table scans.
+   */
+  private final class FallbackTableFinder extends TableFinder
+  {
+    private final Cursor _systemCatalogCursor;
+
+    private FallbackTableFinder(Cursor systemCatalogCursor) {
+      _systemCatalogCursor = systemCatalogCursor;
+    }
+
+    @Override
+    protected Cursor findRow(Integer parentId, String name) 
+      throws IOException 
+    {
+      Map<String,Object> rowPat = new HashMap<String,Object>();
+      rowPat.put(CAT_COL_PARENT_ID, parentId);  
+      rowPat.put(CAT_COL_NAME, name);
+      return (_systemCatalogCursor.findFirstRow(rowPat) ?
+              _systemCatalogCursor : null);
+    }
+
+    @Override
+    protected Cursor findRow(Integer objectId) throws IOException 
+    {
+      Column idCol = _systemCatalog.getColumn(CAT_COL_ID);
+      return (_systemCatalogCursor.findFirstRow(idCol, objectId) ?
+              _systemCatalogCursor : null);
+    }
+
+    @Override
+    public TableInfo lookupTable(String tableName) throws IOException {
+
+      for(Map<String,Object> row : _systemCatalogCursor.iterable(
+              SYSTEM_CATALOG_TABLE_NAME_COLUMNS)) {
+
+        Short type = (Short)row.get(CAT_COL_TYPE);
+        if(!isTableType(type)) {
+          continue;
+        }
+
+        int parentId = (Integer)row.get(CAT_COL_PARENT_ID);
+        if(parentId != _tableParentId) {
+          continue;
+        }
+
+        String realName = (String)row.get(CAT_COL_NAME);
+        if(!tableName.equalsIgnoreCase(realName)) {
+          continue;
+        }
+
+        Integer pageNumber = (Integer)row.get(CAT_COL_ID);
+        int flags = (Integer)row.get(CAT_COL_FLAGS);
+        String linkedDbName = (String)row.get(CAT_COL_DATABASE);
+        String linkedTableName = (String)row.get(CAT_COL_FOREIGN_NAME);
+
+        return createTableInfo(realName, pageNumber, flags, type, linkedDbName,
+                               linkedTableName);
+      }
+
+      return null;
+    }
+    
+    @Override
+    protected Cursor getTableNamesCursor() throws IOException {
+      return _systemCatalogCursor;
+    }
+
+    @Override
+    protected int findMaxSyntheticId() throws IOException {
+      // find max id < 0
+      Column idCol = _systemCatalog.getColumn(CAT_COL_ID);
+      _systemCatalogCursor.reset();
+      int curMaxSynthId = Integer.MIN_VALUE;
+      while(_systemCatalogCursor.moveToNextRow()) {
+        int id = (Integer)_systemCatalogCursor.getCurrentRowValue(idCol);
+        if((id > curMaxSynthId) && (id < 0)) {
+          curMaxSynthId = id;
+        }
+      }
+      return curMaxSynthId;
+    }
+  }
+
+  /**
+   * WeakReference for a Table which holds the table pageNumber (for later
+   * cache purging).
+   */
+  private static final class WeakTableReference extends WeakReference<Table>
+  {
+    private final Integer _pageNumber;
+
+    private WeakTableReference(Integer pageNumber, Table table, 
+                               ReferenceQueue<Table> queue) {
+      super(table, queue);
+      _pageNumber = pageNumber;
+    }
+
+    public Integer getPageNumber() {
+      return _pageNumber;
+    }
+  }
+
+  /**
+   * Cache of currently in-use tables, allows re-use of existing tables.
+   */
+  private static final class TableCache
+  {
+    private final Map<Integer,WeakTableReference> _tables = 
+      new HashMap<Integer,WeakTableReference>();
+    private final ReferenceQueue<Table> _queue = new ReferenceQueue<Table>();
+
+    public Table get(Integer pageNumber) {
+      WeakTableReference ref = _tables.get(pageNumber);
+      return ((ref != null) ? ref.get() : null);
+    }
+
+    public Table put(Table table) {
+      purgeOldRefs();
+  
+      Integer pageNumber = table.getTableDefPageNumber();
+      WeakTableReference ref = new WeakTableReference(
+          pageNumber, table, _queue);
+      _tables.put(pageNumber, ref);
+
+      return table;
+    }
+
+    private void purgeOldRefs() {
+      WeakTableReference oldRef = null;
+      while((oldRef = (WeakTableReference)_queue.poll()) != null) {
+        _tables.remove(oldRef.getPageNumber());
+      }
+    }
+  }  
+}
index ad8d5020a504af295c1c28711ab3f0ce0e95b0b8..7ee1c356a6b54ea4c15682825de9a615a5642e86 100644 (file)
@@ -70,7 +70,7 @@ public class ExportUtil {
    * @see #exportAll(Database,File,String)
    * @see Builder
    */
-  public static void exportAll(Database db, File dir)
+  public static void exportAll(DatabaseImpl db, File dir)
       throws IOException {
     exportAll(db, dir, DEFAULT_FILE_EXT);
   }
@@ -90,7 +90,7 @@ public class ExportUtil {
    * @see #exportFile(Database,String,File,boolean,String,char,ExportFilter)
    * @see Builder
    */
-  public static void exportAll(Database db, File dir,
+  public static void exportAll(DatabaseImpl db, File dir,
       String ext) throws IOException {
     for (String tableName : db.getTableNames()) {
       exportFile(db, tableName, new File(dir, tableName + "." + ext), false,
@@ -115,7 +115,7 @@ public class ExportUtil {
    * @see #exportFile(Database,String,File,boolean,String,char,ExportFilter)
    * @see Builder
    */
-  public static void exportAll(Database db, File dir,
+  public static void exportAll(DatabaseImpl db, File dir,
       String ext, boolean header)
       throws IOException {
     for (String tableName : db.getTableNames()) {
@@ -147,7 +147,7 @@ public class ExportUtil {
    * @see #exportFile(Database,String,File,boolean,String,char,ExportFilter)
    * @see Builder
    */
-  public static void exportAll(Database db, File dir,
+  public static void exportAll(DatabaseImpl db, File dir,
       String ext, boolean header, String delim,
       char quote, ExportFilter filter)
       throws IOException {
@@ -172,7 +172,7 @@ public class ExportUtil {
    * @see #exportFile(Database,String,File,boolean,String,char,ExportFilter)
    * @see Builder
    */
-  public static void exportFile(Database db, String tableName,
+  public static void exportFile(DatabaseImpl db, String tableName,
       File f) throws IOException {
     exportFile(db, tableName, f, false, DEFAULT_DELIMITER, DEFAULT_QUOTE_CHAR, 
         SimpleExportFilter.INSTANCE);
@@ -201,7 +201,7 @@ public class ExportUtil {
    * @see #exportWriter(Database,String,BufferedWriter,boolean,String,char,ExportFilter)
    * @see Builder
    */
-  public static void exportFile(Database db, String tableName,
+  public static void exportFile(DatabaseImpl db, String tableName,
       File f, boolean header, String delim, char quote,
       ExportFilter filter) throws IOException {
     BufferedWriter out = null;
@@ -235,7 +235,7 @@ public class ExportUtil {
    * @see #exportWriter(Database,String,BufferedWriter,boolean,String,char,ExportFilter)
    * @see Builder
    */
-  public static void exportWriter(Database db, String tableName,
+  public static void exportWriter(DatabaseImpl db, String tableName,
       BufferedWriter out) throws IOException {
     exportWriter(db, tableName, out, false, DEFAULT_DELIMITER, 
                  DEFAULT_QUOTE_CHAR, SimpleExportFilter.INSTANCE);
@@ -263,7 +263,7 @@ public class ExportUtil {
    * @see #exportWriter(Cursor,BufferedWriter,boolean,String,char,ExportFilter)
    * @see Builder
    */
-  public static void exportWriter(Database db, String tableName,
+  public static void exportWriter(DatabaseImpl db, String tableName,
       BufferedWriter out, boolean header, String delim,
       char quote, ExportFilter filter)
       throws IOException 
@@ -406,7 +406,7 @@ public class ExportUtil {
    */
   public static class Builder
   {
-    private Database _db;
+    private DatabaseImpl _db;
     private String _tableName;
     private String _ext = DEFAULT_FILE_EXT;
     private Cursor _cursor;
@@ -415,11 +415,11 @@ public class ExportUtil {
     private ExportFilter _filter = SimpleExportFilter.INSTANCE;
     private boolean _header;
 
-    public Builder(Database db) {
+    public Builder(DatabaseImpl db) {
       this(db, null);
     }
 
-    public Builder(Database db, String tableName) {
+    public Builder(DatabaseImpl db, String tableName) {
       _db = db;
       _tableName = tableName;
     }
@@ -428,7 +428,7 @@ public class ExportUtil {
       _cursor = cursor;
     }
 
-    public Builder setDatabase(Database db) {
+    public Builder setDatabase(DatabaseImpl db) {
       _db = db;
       return this;
     }
index 6e11c600ad8938d415ffa07e45683476ec5ca3cc..9cf3bb34550ce33ac0ffa5163ed4bb10b19751d9 100644 (file)
@@ -31,9 +31,9 @@ public class GeneralIndexCodes extends GeneralLegacyIndexCodes {
 
   // stash the codes in some resource files
   private static final String CODES_FILE = 
-    Database.RESOURCE_PATH + "index_codes_gen.txt";
+    DatabaseImpl.RESOURCE_PATH + "index_codes_gen.txt";
   private static final String EXT_CODES_FILE = 
-    Database.RESOURCE_PATH + "index_codes_ext_gen.txt";
+    DatabaseImpl.RESOURCE_PATH + "index_codes_ext_gen.txt";
 
   private static final class Codes
   {
index e6d204cfcb6bde53223a23badf40c2a1d42a3644..6ddd62d10cd503016b21a2b0a018d49994c59dcf 100644 (file)
@@ -75,9 +75,9 @@ public class GeneralLegacyIndexCodes {
 
   // stash the codes in some resource files
   private static final String CODES_FILE = 
-    Database.RESOURCE_PATH + "index_codes_genleg.txt";
+    DatabaseImpl.RESOURCE_PATH + "index_codes_genleg.txt";
   private static final String EXT_CODES_FILE = 
-    Database.RESOURCE_PATH + "index_codes_ext_genleg.txt";
+    DatabaseImpl.RESOURCE_PATH + "index_codes_ext_genleg.txt";
 
   /**
    * Enum which classifies the types of char encoding strategies used when
@@ -326,7 +326,7 @@ public class GeneralLegacyIndexCodes {
 
       reader = new BufferedReader(
           new InputStreamReader(
-              Database.getResourceAsStream(codesFilePath), "US-ASCII"));
+              DatabaseImpl.getResourceAsStream(codesFilePath), "US-ASCII"));
       
       int start = asUnsignedChar(firstChar);
       int end = asUnsignedChar(lastChar);
index 0fc18028bb7e718c2eea17bfaacca4db74b7f14c..b3ccb94f73a71a541d6592925957c709e046c2f2 100644 (file)
@@ -74,7 +74,7 @@ public class ImportUtil
       List<Column> columns = new LinkedList<Column>();
       for (int i = 1; i <= md.getColumnCount(); i++) {
         Column column = new Column();
-        column.setName(Database.escapeIdentifier(md.getColumnName(i)));
+        column.setName(DatabaseImpl.escapeIdentifier(md.getColumnName(i)));
         int lengthInUnits = md.getColumnDisplaySize(i);
         column.setSQLType(md.getColumnType(i), lengthInUnits);
         DataType type = column.getType();
@@ -113,7 +113,7 @@ public class ImportUtil
    * @see #importResultSet(ResultSet,Database,String,ImportFilter)
    * @see Builder
    */
-  public static String importResultSet(ResultSet source, Database db,
+  public static String importResultSet(ResultSet source, DatabaseImpl db,
                                        String name)
     throws SQLException, IOException
   {
@@ -135,7 +135,7 @@ public class ImportUtil
    * @see #importResultSet(ResultSet,Database,String,ImportFilter,boolean)
    * @see Builder
    */
-  public static String importResultSet(ResultSet source, Database db,
+  public static String importResultSet(ResultSet source, DatabaseImpl db,
                                        String name, ImportFilter filter)
     throws SQLException, IOException
   {
@@ -157,14 +157,14 @@ public class ImportUtil
    * 
    * @see Builder
    */
-  public static String importResultSet(ResultSet source, Database db,
+  public static String importResultSet(ResultSet source, DatabaseImpl db,
                                        String name, ImportFilter filter,
                                        boolean useExistingTable)
     throws SQLException, IOException
   {
     ResultSetMetaData md = source.getMetaData();
 
-    name = Database.escapeIdentifier(name);
+    name = DatabaseImpl.escapeIdentifier(name);
     Table table = null;
     if(!useExistingTable || ((table = db.getTable(name)) == null)) {
       List<Column> columns = toColumns(md);
@@ -211,7 +211,7 @@ public class ImportUtil
    * @see #importFile(File,Database,String,String,ImportFilter)
    * @see Builder
    */
-  public static String importFile(File f, Database db, String name,
+  public static String importFile(File f, DatabaseImpl db, String name,
                                   String delim)
     throws IOException
   {
@@ -234,7 +234,7 @@ public class ImportUtil
    * @see #importReader(BufferedReader,Database,String,String,ImportFilter)
    * @see Builder
    */
-  public static String importFile(File f, Database db, String name,
+  public static String importFile(File f, DatabaseImpl db, String name,
                                   String delim, ImportFilter filter)
     throws IOException
   {
@@ -262,7 +262,7 @@ public class ImportUtil
    * @see #importReader(BufferedReader,Database,String,String,ImportFilter,boolean)
    * @see Builder
    */
-  public static String importFile(File f, Database db, String name, 
+  public static String importFile(File f, DatabaseImpl db, String name, 
                                   String delim, char quote, 
                                   ImportFilter filter,
                                   boolean useExistingTable)
@@ -292,7 +292,7 @@ public class ImportUtil
    * @see #importReader(BufferedReader,Database,String,String,char,ImportFilter,boolean,boolean)
    * @see Builder
    */
-  public static String importFile(File f, Database db, String name, 
+  public static String importFile(File f, DatabaseImpl db, String name, 
                                   String delim, char quote, 
                                   ImportFilter filter,
                                   boolean useExistingTable,
@@ -330,7 +330,7 @@ public class ImportUtil
    * @see #importReader(BufferedReader,Database,String,String,ImportFilter)
    * @see Builder
    */
-  public static String importReader(BufferedReader in, Database db, 
+  public static String importReader(BufferedReader in, DatabaseImpl db, 
                                     String name, String delim)
     throws IOException
   {
@@ -353,7 +353,7 @@ public class ImportUtil
    * @see #importReader(BufferedReader,Database,String,String,ImportFilter,boolean)
    * @see Builder
    */
-  public static String importReader(BufferedReader in, Database db, 
+  public static String importReader(BufferedReader in, DatabaseImpl db, 
                                     String name, String delim,
                                     ImportFilter filter)
     throws IOException
@@ -380,7 +380,7 @@ public class ImportUtil
    * 
    * @see Builder
    */
-  public static String importReader(BufferedReader in, Database db, 
+  public static String importReader(BufferedReader in, DatabaseImpl db, 
                                     String name, String delim,
                                     ImportFilter filter, 
                                     boolean useExistingTable)
@@ -410,7 +410,7 @@ public class ImportUtil
    * 
    * @see Builder
    */
-  public static String importReader(BufferedReader in, Database db, 
+  public static String importReader(BufferedReader in, DatabaseImpl db, 
                                     String name, String delim, char quote,
                                     ImportFilter filter,
                                     boolean useExistingTable)
@@ -439,7 +439,7 @@ public class ImportUtil
    * 
    * @see Builder
    */
-  public static String importReader(BufferedReader in, Database db, 
+  public static String importReader(BufferedReader in, DatabaseImpl db, 
                                     String name, String delim, char quote,
                                     ImportFilter filter,
                                     boolean useExistingTable, boolean header)
@@ -453,7 +453,7 @@ public class ImportUtil
     Pattern delimPat = Pattern.compile(delim);
 
     try {
-      name = Database.escapeIdentifier(name);
+      name = DatabaseImpl.escapeIdentifier(name);
       Table table = null;
       if(!useExistingTable || ((table = db.getTable(name)) == null)) {
 
@@ -590,7 +590,7 @@ public class ImportUtil
   /**
    * Returns a new table with a unique name and the given table definition.
    */
-  private static Table createUniqueTable(Database db, String name,
+  private static Table createUniqueTable(DatabaseImpl db, String name,
                                          List<Column> columns,
                                          ResultSetMetaData md, 
                                          ImportFilter filter)
@@ -613,7 +613,7 @@ public class ImportUtil
    */
   public static class Builder
   {
-    private Database _db;
+    private DatabaseImpl _db;
     private String _tableName;
     private String _delim = ExportUtil.DEFAULT_DELIMITER;
     private char _quote = ExportUtil.DEFAULT_QUOTE_CHAR;
@@ -621,16 +621,16 @@ public class ImportUtil
     private boolean _useExistingTable;
     private boolean _header = true;
 
-    public Builder(Database db) {
+    public Builder(DatabaseImpl db) {
       this(db, null);
     }
 
-    public Builder(Database db, String tableName) {
+    public Builder(DatabaseImpl db, String tableName) {
       _db = db;
       _tableName = tableName;
     }
 
-    public Builder setDatabase(Database db) {
+    public Builder setDatabaseImpl(DatabaseImpl db) {
       _db = db;
       return this;
     }
index 2c24a2d3b6d8e8907ed552af710ab0ba3d46f6f4..e3d508c9beeddb37467767948e455ecc9fd1f8e9 100644 (file)
@@ -51,7 +51,7 @@ import static com.healthmarketscience.jackcess.ByteUtil.ByteStream;
  * 
  * @author Tim McCune
  */
-public abstract class IndexData {
+public class IndexData {
   
   protected static final Log LOG = LogFactory.getLog(Index.class);
 
@@ -70,6 +70,8 @@ public abstract class IndexData {
   /** special object which will always be greater than any other value, when
       searching for an index entry range in a multi-value index */
   public static final Object MIN_VALUE = new Object();
+
+  private static final DataPage NEW_ROOT_DATA_PAGE = new RootDataPage();
   
   protected static final int INVALID_INDEX_PAGE_NUMBER = 0;          
   
@@ -176,6 +178,8 @@ public abstract class IndexData {
   private boolean _primaryKey;
   /** FIXME, for SimpleIndex, we can't write multi-page indexes or indexes using the entry compression scheme */
   private boolean _readOnly;
+  /** Cache which manages the index pages */
+  private final IndexPageCache _pageCache;
   
   protected IndexData(Table table, int number, int uniqueEntryCount,
                       int uniqueEntryCountOffset)
@@ -185,6 +189,7 @@ public abstract class IndexData {
     _uniqueEntryCount = uniqueEntryCount;
     _uniqueEntryCountOffset = uniqueEntryCountOffset;
     _maxPageEntrySize = calcMaxPageEntrySize(_table.getFormat());
+    _pageCache = new IndexPageCache(this);
   }
 
   /**
@@ -200,11 +205,7 @@ public abstract class IndexData {
        (number * format.SIZE_INDEX_DEFINITION) + 4);
     int uniqueEntryCount = tableBuffer.getInt(uniqueEntryCountOffset);
 
-    return(table.doUseBigIndex() ?
-           new BigIndexData(table, number, uniqueEntryCount,
-                            uniqueEntryCountOffset) :
-           new SimpleIndexData(table, number, uniqueEntryCount, 
-                               uniqueEntryCountOffset));
+    return new IndexData(table, number, uniqueEntryCount, uniqueEntryCountOffset);
   }
 
   public Table getTable() {
@@ -341,6 +342,13 @@ public abstract class IndexData {
     _ownedPages.addPageNumber(pageNumber);
   }
 
+  /**
+   * Used by unit tests to validate the internal status of the index.
+   */
+  void validate() throws IOException {
+    _pageCache.validate();
+  }
+
   /**
    * Returns the number of index entries in the index.  Only called by unit
    * tests.
@@ -367,7 +375,7 @@ public abstract class IndexData {
    */
   public void initialize() throws IOException {
     if(!_initialized) {
-      readIndexEntries();
+      _pageCache.setRootPageNumber(getRootPageNumber());
       _initialized = true;
     }
   }
@@ -386,7 +394,7 @@ public abstract class IndexData {
       throw new UnsupportedOperationException(
           "FIXME cannot write indexes of this type yet, see Database javadoc for info on enabling large index support");
     }
-    updateImpl();
+    _pageCache.write();
   }
 
   /**
@@ -455,7 +463,7 @@ public abstract class IndexData {
     throws IOException
   {
     ByteBuffer rootPageBuffer = creator.getPageChannel().createPageBuffer();
-    writeDataPage(rootPageBuffer, SimpleIndexData.NEW_ROOT_DATA_PAGE, 
+    writeDataPage(rootPageBuffer, NEW_ROOT_DATA_PAGE, 
                   creator.getTdefPageNumber(), creator.getFormat());
 
     for(IndexBuilder idx : creator.getIndexes()) {
@@ -855,6 +863,7 @@ public abstract class IndexData {
         throw new RuntimeException(e);
       }
     }
+    rtn.append("\n").append(_pageCache.toString());
     return rtn.toString();
   }
   
@@ -865,10 +874,6 @@ public abstract class IndexData {
     throws IOException
   {
     if(dataPage.getCompressedEntrySize() > _maxPageEntrySize) {
-      if(this instanceof SimpleIndexData) {
-        throw new UnsupportedOperationException(
-            "FIXME cannot write large index yet, see Database javadoc for info on enabling large index support");
-      }
       throw new IllegalStateException("data page is too large");
     }
     
@@ -1115,30 +1120,24 @@ public abstract class IndexData {
     
     return _entryBuffer.toByteArray();
   }  
-  
-  /**
-   * Writes the current index state to the database.  Index has already been
-   * initialized.
-   */
-  protected abstract void updateImpl() throws IOException;
-  
-  /**
-   * Reads the actual index entries.
-   */
-  protected abstract void readIndexEntries()
-    throws IOException;
 
   /**
    * Finds the data page for the given entry.
    */
-  protected abstract DataPage findDataPage(Entry entry)
-    throws IOException;
+  protected DataPage findDataPage(Entry entry)
+    throws IOException
+  {
+    return _pageCache.findCacheDataPage(entry);
+  }
   
   /**
    * Gets the data page for the pageNumber.
    */
-  protected abstract DataPage getDataPage(int pageNumber)
-    throws IOException;
+  protected DataPage getDataPage(int pageNumber)
+    throws IOException
+  {
+    return _pageCache.getCacheDataPage(pageNumber);
+  }
   
   /**
    * Flips the first bit in the byte at the given index.
@@ -2375,5 +2374,52 @@ public abstract class IndexData {
     }
   }
 
+  /**
+   * Simple implementation of a DataPage
+   */
+  private static final class RootDataPage extends DataPage {
+
+    @Override
+    public int getPageNumber() { return 0; }
+    
+    @Override
+    public boolean isLeaf() { return true; }
+    @Override
+    public void setLeaf(boolean isLeaf) { }
+
+    @Override
+    public int getPrevPageNumber() { return 0; }
+    @Override
+    public void setPrevPageNumber(int pageNumber) { }
+
+    @Override
+    public int getNextPageNumber() { return 0; }
+    @Override
+    public void setNextPageNumber(int pageNumber) { }
+
+    @Override
+    public int getChildTailPageNumber() { return 0; }
+    @Override
+    public void setChildTailPageNumber(int pageNumber) { }
+    
+    @Override
+    public int getTotalEntrySize() { return 0; }
+    @Override
+    public void setTotalEntrySize(int totalSize) { }
+
+    @Override
+    public byte[] getEntryPrefix() { return EMPTY_PREFIX; }
+    @Override
+    public void setEntryPrefix(byte[] entryPrefix) { }
+
+    @Override
+    public List<Entry> getEntries() { return Collections.emptyList(); }    
+    @Override
+    public void setEntries(List<Entry> entries) { }
+    @Override
+    public void addEntry(int idx, Entry entry) { }
+    @Override
+    public void removeEntry(int idx) { }
+  }
 
 }
index 56cb44ac3d8eb31b1881c8c3877f780996d1cf7d..3703c1b8a400193cd107c82970f03431d9032566 100644 (file)
@@ -43,7 +43,7 @@ import java.util.RandomAccess;
 import static com.healthmarketscience.jackcess.IndexData.*;
 
 /**
- * Manager of the index pages for a BigIndex.
+ * Manager of the index pages for a IndexData.
  * @author James Ahlborn
  */
 public class IndexPageCache
@@ -53,7 +53,7 @@ public class IndexPageCache
   }
 
   /** the index whose pages this cache is managing */
-  private final BigIndexData _indexData;
+  private final IndexData _indexData;
   /** the root page for the index */
   private DataPageMain _rootPage;
   /** the currently loaded pages for this index, pageNumber -> page */
@@ -63,11 +63,11 @@ public class IndexPageCache
   private final List<CacheDataPage> _modifiedPages =
     new ArrayList<CacheDataPage>();
   
-  public IndexPageCache(BigIndexData indexData) {
+  public IndexPageCache(IndexData indexData) {
     _indexData = indexData;
   }
 
-  public BigIndexData getIndexData() {
+  public IndexData getIndexData() {
     return _indexData;
   }
   
index 2a371206f31c0840c1bdf097d25edeed93ad7787..28e618c56b3b860be86a3ead953f46b24d5adb43 100644 (file)
@@ -115,24 +115,24 @@ public abstract class JetFormat {
 
   // use nested inner class to avoid problematic static init loops
   private static final class PossibleFileFormats {
-    private static final Map<String,Database.FileFormat> POSSIBLE_VERSION_3 = 
-      Collections.singletonMap((String)null, Database.FileFormat.V1997);
+    private static final Map<String,DatabaseImpl.FileFormat> POSSIBLE_VERSION_3 = 
+      Collections.singletonMap((String)null, DatabaseImpl.FileFormat.V1997);
 
-    private static final Map<String,Database.FileFormat> POSSIBLE_VERSION_4 = 
-      new HashMap<String,Database.FileFormat>();
+    private static final Map<String,DatabaseImpl.FileFormat> POSSIBLE_VERSION_4 = 
+      new HashMap<String,DatabaseImpl.FileFormat>();
 
-    private static final Map<String,Database.FileFormat> POSSIBLE_VERSION_12 = 
-      Collections.singletonMap((String)null, Database.FileFormat.V2007);
+    private static final Map<String,DatabaseImpl.FileFormat> POSSIBLE_VERSION_12 = 
+      Collections.singletonMap((String)null, DatabaseImpl.FileFormat.V2007);
 
-    private static final Map<String,Database.FileFormat> POSSIBLE_VERSION_14 = 
-      Collections.singletonMap((String)null, Database.FileFormat.V2010);
+    private static final Map<String,DatabaseImpl.FileFormat> POSSIBLE_VERSION_14 = 
+      Collections.singletonMap((String)null, DatabaseImpl.FileFormat.V2010);
 
-    private static final Map<String,Database.FileFormat> POSSIBLE_VERSION_MSISAM = 
-      Collections.singletonMap((String)null, Database.FileFormat.MSISAM);
+    private static final Map<String,DatabaseImpl.FileFormat> POSSIBLE_VERSION_MSISAM = 
+      Collections.singletonMap((String)null, DatabaseImpl.FileFormat.MSISAM);
 
     static {
-      POSSIBLE_VERSION_4.put(ACCESS_VERSION_2000, Database.FileFormat.V2000);
-      POSSIBLE_VERSION_4.put(ACCESS_VERSION_2003, Database.FileFormat.V2003);
+      POSSIBLE_VERSION_4.put(ACCESS_VERSION_2000, DatabaseImpl.FileFormat.V2000);
+      POSSIBLE_VERSION_4.put(ACCESS_VERSION_2003, DatabaseImpl.FileFormat.V2003);
     }
   }
 
@@ -494,7 +494,7 @@ public abstract class JetFormat {
 
   protected abstract boolean defineLegacyNumericIndexes();
 
-  protected abstract Map<String,Database.FileFormat> getPossibleFileFormats();
+  protected abstract Map<String,DatabaseImpl.FileFormat> getPossibleFileFormats();
 
   protected abstract boolean isSupportedDataType(DataType type);
 
@@ -708,7 +708,7 @@ public abstract class JetFormat {
     }
 
     @Override
-    protected Map<String,Database.FileFormat> getPossibleFileFormats()
+    protected Map<String,DatabaseImpl.FileFormat> getPossibleFileFormats()
     {
       return PossibleFileFormats.POSSIBLE_VERSION_3;
     }
@@ -926,7 +926,7 @@ public abstract class JetFormat {
     }
 
     @Override
-    protected Map<String,Database.FileFormat> getPossibleFileFormats()
+    protected Map<String,DatabaseImpl.FileFormat> getPossibleFileFormats()
     {
       return PossibleFileFormats.POSSIBLE_VERSION_4;
     }
@@ -948,7 +948,7 @@ public abstract class JetFormat {
     }
 
     @Override
-    protected Map<String,Database.FileFormat> getPossibleFileFormats()
+    protected Map<String,DatabaseImpl.FileFormat> getPossibleFileFormats()
     {
       return PossibleFileFormats.POSSIBLE_VERSION_MSISAM;
     }
@@ -973,7 +973,7 @@ public abstract class JetFormat {
     protected boolean defineLegacyNumericIndexes() { return false; }
 
     @Override
-    protected Map<String,Database.FileFormat> getPossibleFileFormats() {
+    protected Map<String,DatabaseImpl.FileFormat> getPossibleFileFormats() {
       return PossibleFileFormats.POSSIBLE_VERSION_12;
     }
 
@@ -1000,7 +1000,7 @@ public abstract class JetFormat {
     }
 
     @Override
-    protected Map<String,Database.FileFormat> getPossibleFileFormats() {
+    protected Map<String,DatabaseImpl.FileFormat> getPossibleFileFormats() {
       return PossibleFileFormats.POSSIBLE_VERSION_14;
     }
   }
index 719a793f130facd8581c23e378dd8af0d9bf0b80..503c439efd558ad8965bd7ee0d3cb4f94e2e78a9 100644 (file)
@@ -93,7 +93,7 @@ public class MemFileChannel extends FileChannel
    * affect the original File source.
    */
   public static MemFileChannel newChannel(File file) throws IOException {
-    return newChannel(file, Database.RW_CHANNEL_MODE);
+    return newChannel(file, DatabaseImpl.RW_CHANNEL_MODE);
   }
 
   /**
@@ -109,7 +109,7 @@ public class MemFileChannel extends FileChannel
     FileChannel in = null;
     try {
       return newChannel(in = new RandomAccessFile(
-                            file, Database.RO_CHANNEL_MODE).getChannel(),
+                            file, DatabaseImpl.RO_CHANNEL_MODE).getChannel(),
                         mode);
     } finally {
       if(in != null) {
@@ -127,7 +127,7 @@ public class MemFileChannel extends FileChannel
    * given InputStream.
    */
   public static MemFileChannel newChannel(InputStream in) throws IOException {
-    return newChannel(in, Database.RW_CHANNEL_MODE);
+    return newChannel(in, DatabaseImpl.RW_CHANNEL_MODE);
   }
 
   /**
@@ -148,7 +148,7 @@ public class MemFileChannel extends FileChannel
   public static MemFileChannel newChannel(ReadableByteChannel in) 
     throws IOException
   {
-    return newChannel(in, Database.RW_CHANNEL_MODE);
+    return newChannel(in, DatabaseImpl.RW_CHANNEL_MODE);
   }
 
   /**
index cd2a03c6a8c5d9095d0513abdb91efd3b0ce6bc6..fb02d1fff219a48bece87409a18276a49c0aab51 100644 (file)
@@ -99,7 +99,7 @@ public class PageChannel implements Channel, Flushable {
   /**
    * Does second-stage initialization, must be called after construction.
    */
-  public void initialize(Database database, CodecProvider codecProvider)
+  public void initialize(DatabaseImpl database, CodecProvider codecProvider)
     throws IOException
   {
     // initialize page en/decoding support
index dc25dc0d8710be433ff9a90332b908df9d7fc432..e6c5b2583e37cad996084b00682c0c16505bb98b 100644 (file)
@@ -77,7 +77,7 @@ public class PropertyMap implements Iterable<PropertyMap.Property>
    * @return the property with the given name, if any
    */
   public Property get(String name) {
-    return _props.get(Database.toLookupName(name));
+    return _props.get(DatabaseImpl.toLookupName(name));
   }
 
   /**
@@ -104,7 +104,7 @@ public class PropertyMap implements Iterable<PropertyMap.Property>
    * Puts a property into this map with the given information.
    */
   public void put(String name, DataType type, byte flag, Object value) {
-    _props.put(Database.toLookupName(name), 
+    _props.put(DatabaseImpl.toLookupName(name), 
                new Property(name, type, flag, value));
   }
 
index 51853ee85b5f7b3d54c6ace00e8f10b2ba86f54c..c40f1f365cf1f0b606a92fe846343cdacad961f2 100644 (file)
@@ -85,7 +85,7 @@ public class PropertyMaps implements Iterable<PropertyMap>
    *         creating if necessary
    */
   private PropertyMap get(String name, short type) {
-    String lookupName = Database.toLookupName(name);
+    String lookupName = DatabaseImpl.toLookupName(name);
     PropertyMap map = _maps.get(lookupName);
     if(map == null) {
       map = new PropertyMap(name, type);
@@ -98,7 +98,7 @@ public class PropertyMaps implements Iterable<PropertyMap>
    * Adds the given PropertyMap to this group.
    */
   public void put(PropertyMap map) {
-    _maps.put(Database.toLookupName(map.getName()), map);
+    _maps.put(DatabaseImpl.toLookupName(map.getName()), map);
   }
 
   public Iterator<PropertyMap> iterator() {
@@ -123,12 +123,12 @@ public class PropertyMaps implements Iterable<PropertyMap>
   static final class Handler
   {
     /** the current database */
-    private final Database _database;
+    private final DatabaseImpl _database;
     /** cache of PropColumns used to read/write property values */
     private final Map<DataType,PropColumn> _columns = 
       new HashMap<DataType,PropColumn>();
 
-    Handler(Database database) {
+    Handler(DatabaseImpl database) {
       _database = database;
     }
 
@@ -305,7 +305,7 @@ public class PropertyMaps implements Iterable<PropertyMap>
     private class PropColumn extends Column
     {
       @Override
-      public Database getDatabase() {
+      public DatabaseImpl getDatabase() {
         return _database;
       }
     }
diff --git a/src/java/com/healthmarketscience/jackcess/SimpleIndexData.java b/src/java/com/healthmarketscience/jackcess/SimpleIndexData.java
deleted file mode 100644 (file)
index 7a662e7..0000000
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
-Copyright (c) 2008 Health Market Science, Inc.
-
-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
-
-You can contact Health Market Science at info@healthmarketscience.com
-or at the following address:
-
-Health Market Science
-2700 Horizon Drive
-Suite 200
-King of Prussia, PA 19406
-*/
-
-package com.healthmarketscience.jackcess;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-
-
-/**
- * Simple implementation of an Access table index
- * @author Tim McCune
- */
-public class SimpleIndexData extends IndexData 
-{
-
-  static final DataPage NEW_ROOT_DATA_PAGE = 
-    new SimpleDataPage(0, true, Collections.<Entry>emptyList());
-
-
-  /** data for the single index page.  if this data came from multiple pages,
-      the index is read-only. */
-  private SimpleDataPage _dataPage;
-  
-  public SimpleIndexData(Table table, int number, int uniqueEntryCount,
-                         int uniqueEntryCountOffset)
-  {
-    super(table, number, uniqueEntryCount, uniqueEntryCountOffset);
-  }
-
-  @Override
-  protected void updateImpl() throws IOException {
-    writeDataPage(_dataPage);
-  }
-
-  @Override
-  protected void readIndexEntries()
-    throws IOException
-  {
-    // find first leaf page
-    int nextPageNumber = getRootPageNumber();
-    SimpleDataPage indexPage = null;
-    while(true) {
-      indexPage = new SimpleDataPage(nextPageNumber);
-      readDataPage(indexPage);
-
-      if(!indexPage.isLeaf()) {
-        // FIXME we can't modify this index at this point in time
-        setReadOnly();
-
-        // found another node page
-        if(!indexPage.getEntries().isEmpty()) {
-          nextPageNumber = indexPage.getEntries().get(0).getSubPageNumber();
-        } else {
-          // try tail page
-          nextPageNumber = indexPage.getChildTailPageNumber();
-        }
-        indexPage = null;
-      } else {
-        // found first leaf
-        break;
-      }
-    }
-
-    // save the first leaf page
-    _dataPage = indexPage;
-    nextPageNumber = indexPage.getNextPageNumber();
-    _dataPage.setNextPageNumber(INVALID_INDEX_PAGE_NUMBER);
-    indexPage = null;
-    
-    // read all leaf pages.
-    while(nextPageNumber != INVALID_INDEX_PAGE_NUMBER) {
-        
-      // FIXME we can't modify this index at this point in time
-      setReadOnly();
-        
-      // found another one
-      indexPage = new SimpleDataPage(nextPageNumber);
-      readDataPage(indexPage);
-
-      // since we read all the entries in sort order, we can insert them
-      // directly into the entries list
-      _dataPage.getEntries().addAll(indexPage.getEntries());
-      int totalSize = (_dataPage.getTotalEntrySize() +
-                       indexPage.getTotalEntrySize());
-      _dataPage.setTotalEntrySize(totalSize);
-      nextPageNumber = indexPage.getNextPageNumber();
-    }
-
-    // check the entry order, just to be safe
-    List<Entry> entries = _dataPage.getEntries();
-    for(int i = 0; i < (entries.size() - 1); ++i) {
-      Entry e1 = entries.get(i);
-      Entry e2 = entries.get(i + 1);
-      if(e1.compareTo(e2) > 0) {
-        throw new IOException("Unexpected order in index entries, " +
-                              e1 + " is greater than " + e2);
-      }
-    }
-  }
-
-  @Override
-  protected DataPage findDataPage(Entry entry)
-    throws IOException
-  {
-    return _dataPage;
-  }
-
-  @Override
-  protected DataPage getDataPage(int pageNumber)
-    throws IOException
-  {
-    throw new UnsupportedOperationException();
-  }
-
-  /**
-   * Simple implementation of a DataPage
-   */
-  private static final class SimpleDataPage extends DataPage {
-    private final int _pageNumber;
-    private boolean _leaf;
-    private int _nextPageNumber;
-    private int _totalEntrySize;
-    private int _childTailPageNumber;
-    private List<Entry> _entries;
-
-    private SimpleDataPage(int pageNumber) {
-      this(pageNumber, false, null);
-    }
-
-    private SimpleDataPage(int pageNumber, boolean leaf, List<Entry> entries)
-    {
-      _pageNumber = pageNumber;
-      _leaf = leaf;
-      _entries = entries;
-    }
-    
-    @Override
-    public int getPageNumber() {
-      return _pageNumber;
-    }
-    
-    @Override
-    public boolean isLeaf() {
-      return _leaf;
-    }
-    @Override
-    public void setLeaf(boolean isLeaf) {
-      _leaf = isLeaf;
-    }
-
-    @Override
-    public int getPrevPageNumber() { return 0; }
-    @Override
-    public void setPrevPageNumber(int pageNumber) {
-      // ignored
-    }
-    @Override
-    public int getNextPageNumber() {
-      return _nextPageNumber;
-    }
-    @Override
-    public void setNextPageNumber(int pageNumber) {
-      _nextPageNumber = pageNumber;
-    }
-    @Override
-    public int getChildTailPageNumber() { 
-      return _childTailPageNumber;
-    }
-    @Override
-    public void setChildTailPageNumber(int pageNumber) {
-      _childTailPageNumber = pageNumber;
-    }
-    
-    @Override
-    public int getTotalEntrySize() {
-      return _totalEntrySize;
-    }
-    @Override
-    public void setTotalEntrySize(int totalSize) {
-      _totalEntrySize = totalSize;
-    }
-    @Override
-    public byte[] getEntryPrefix() {
-      return EMPTY_PREFIX;
-    }
-    @Override
-    public void setEntryPrefix(byte[] entryPrefix) {
-      // ignored
-    }
-
-    @Override
-    public List<Entry> getEntries() {
-      return _entries;
-    }
-    
-    @Override
-    public void setEntries(List<Entry> entries) {      
-      _entries = entries;
-    }
-
-    @Override
-    public void addEntry(int idx, Entry entry) {
-      _entries.add(idx, entry);
-      _totalEntrySize += entry.size();
-    }
-
-    @Override
-    public void removeEntry(int idx) {
-      Entry oldEntry = _entries.remove(idx);
-      _totalEntrySize -= oldEntry.size();
-    }
-    
-  }
-  
-}
index 67b31f9a34e664356a0ff42e32375f05c4123022..18303df56311fd234eece31666cb94ae8fcd76b4 100644 (file)
@@ -116,7 +116,7 @@ public class Table
     };
 
   /** owning database */
-  private final Database _database;
+  private final DatabaseImpl _database;
   /** additional table flags from the catalog entry */
   private int _flags;
   /** Type of the table (either TYPE_SYSTEM or TYPE_USER) */
@@ -175,8 +175,6 @@ public class Table
   /** page buffer used to write out-of-line "long value" data */
   private final TempPageHolder _longValueBufferH =
     TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
-  /** "big index support" is optional */
-  private final boolean _useBigIndex;
   /** optional error handler to use when row errors are encountered */
   private ErrorHandler _tableErrorHandler;
   /** properties for this table */
@@ -201,7 +199,6 @@ public class Table
     _database = null;
     _tableDefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
     _name = null;
-    _useBigIndex = true;
     setColumns(columns);
     _fkEnforcer = null;
   }
@@ -211,18 +208,15 @@ public class Table
    * @param tableBuffer Buffer to read the table with
    * @param pageNumber Page number of the table definition
    * @param name Table name
-   * @param useBigIndex whether or not "big index support" should be enabled
-   *                    for the table
    */
-  protected Table(Database database, ByteBuffer tableBuffer,
-                  int pageNumber, String name, int flags, boolean useBigIndex)
+  protected Table(DatabaseImpl database, ByteBuffer tableBuffer,
+                  int pageNumber, String name, int flags)
     throws IOException
   {
     _database = database;
     _tableDefPageNumber = pageNumber;
     _name = name;
     _flags = flags;
-    _useBigIndex = useBigIndex; 
     readTableDefinition(loadCompleteTableDefinitionBuffer(tableBuffer));
     _fkEnforcer = new FKEnforcer(this);
   }
@@ -240,14 +234,7 @@ public class Table
    * @usage _general_method_
    */
   public boolean isHidden() {
-    return((_flags & Database.HIDDEN_OBJECT_FLAG) != 0);
-  }
-
-  /**
-   * @usage _advanced_method_
-   */
-  public boolean doUseBigIndex() {
-    return _useBigIndex;
+    return((_flags & DatabaseImpl.HIDDEN_OBJECT_FLAG) != 0);
   }
 
   /**
@@ -267,7 +254,7 @@ public class Table
   /**
    * @usage _general_method_
    */
-  public Database getDatabase() {
+  public DatabaseImpl getDatabase() {
     return _database;
   }
   
@@ -1416,7 +1403,7 @@ public class Table
    * expected to be given in the order that the Columns are listed by the
    * {@link #getColumns} method.  This is by default the storage order of the
    * Columns in the database, however this order can be influenced by setting
-   * the ColumnOrder via {@link Database#setColumnOrder} prior to opening the
+   * the ColumnOrder via {@link DatabaseImpl#setColumnOrder} prior to opening the
    * Table.  The {@link #asRow} method can be used to easily convert a row Map into the
    * appropriate row array for this Table.
    * <p>
index 51e8697c29896c896bb90f12adcda5a4857e46e0..c1c8496494bb4e1c0246f869fbcadd4f685d1339 100644 (file)
@@ -56,7 +56,7 @@ public class TableBuilder {
     _name = name;
     _escapeIdentifiers = escapeIdentifiers;
     if(_escapeIdentifiers) {
-      _name = Database.escapeIdentifier(_name);
+      _name = DatabaseImpl.escapeIdentifier(_name);
     }
   }
 
@@ -66,7 +66,7 @@ public class TableBuilder {
    */
   public TableBuilder addColumn(Column column) {
     if(_escapeIdentifiers) {
-      column.setName(Database.escapeIdentifier(column.getName()));
+      column.setName(DatabaseImpl.escapeIdentifier(column.getName()));
     }
     _columns.add(column);
     return this;
@@ -84,9 +84,9 @@ public class TableBuilder {
    */
   public TableBuilder addIndex(IndexBuilder index) {
     if(_escapeIdentifiers) {
-      index.setName(Database.escapeIdentifier(index.getName()));
+      index.setName(DatabaseImpl.escapeIdentifier(index.getName()));
       for(IndexBuilder.Column col : index.getColumns()) {
-        col.setName(Database.escapeIdentifier(col.getName()));
+        col.setName(DatabaseImpl.escapeIdentifier(col.getName()));
       }
     }
     _indexes.add(index);
@@ -113,11 +113,11 @@ public class TableBuilder {
   }
   
   /**
-   * Escapes the new table's name using {@link Database#escapeIdentifier}.
+   * Escapes the new table's name using {@link DatabaseImpl#escapeIdentifier}.
    */
   public TableBuilder escapeName()
   {
-    _name = Database.escapeIdentifier(_name);
+    _name = DatabaseImpl.escapeIdentifier(_name);
     return this;
   }
 
@@ -125,7 +125,7 @@ public class TableBuilder {
    * Creates a new Table in the given Database with the currently configured
    * attributes.
    */
-  public Table toTable(Database db)
+  public Table toTable(DatabaseImpl db)
     throws IOException
   {
     db.createTable(_name, _columns, _indexes);
index 75aab7c7a6f88c8d39bec82d0e2148ed031202b7..bc458dee614c71e1598a3f46833a54bbd4430e14 100644 (file)
@@ -37,7 +37,7 @@ import java.util.Set;
  */
 class TableCreator 
 {
-  private final Database _database;
+  private final DatabaseImpl _database;
   private final String _name;
   private final List<Column> _columns;
   private final List<IndexBuilder> _indexes;
@@ -48,7 +48,7 @@ class TableCreator
   private int _indexCount;
   private int _logicalIndexCount;
 
-  public TableCreator(Database database, String name, List<Column> columns,
+  public TableCreator(DatabaseImpl database, String name, List<Column> columns,
                       List<IndexBuilder> indexes) {
     _database = database;
     _name = name;
@@ -132,7 +132,7 @@ class TableCreator
     Table.writeTableDefinition(this);
 
     // update the database with the new table info
-    _database.addNewTable(_name, _tdefPageNumber, Database.TYPE_TABLE, null, null);
+    _database.addNewTable(_name, _tdefPageNumber, DatabaseImpl.TYPE_TABLE, null, null);
   }
 
   /**
@@ -140,7 +140,7 @@ class TableCreator
    */
   private void validate() {
 
-    Database.validateIdentifierName(
+    DatabaseImpl.validateIdentifierName(
         _name, getFormat().MAX_TABLE_NAME_LENGTH, "table");
     
     if((_columns == null) || _columns.isEmpty()) {
index 931891e529878983ed1cfd4790cec96606aabe6f..920ce25533e0d6c19d24e282287087f90b73467b 100644 (file)
@@ -51,7 +51,7 @@ public class UsageMap
   private static final int INVALID_BIT_INDEX = -1;
   
   /** owning database */
-  private final Database _database;
+  private final DatabaseImpl _database;
   /** Page number of the map table declaration */
   private final int _tablePageNum;
   /** Offset of the data page at which the usage map data starts */
@@ -82,7 +82,7 @@ public class UsageMap
    * @param pageNum Page number that this usage map is contained in
    * @param rowStart Offset at which the declaration starts in the buffer
    */
-  private UsageMap(Database database, ByteBuffer tableBuffer,
+  private UsageMap(DatabaseImpl database, ByteBuffer tableBuffer,
                    int pageNum, short rowStart)
     throws IOException
   {
@@ -98,7 +98,7 @@ public class UsageMap
     }
   }
 
-  public Database getDatabase() {
+  public DatabaseImpl getDatabase() {
     return _database;
   }
   
@@ -117,7 +117,7 @@ public class UsageMap
    * @return Either an InlineUsageMap or a ReferenceUsageMap, depending on
    *         which type of map is found
    */
-  public static UsageMap read(Database database, int pageNum,
+  public static UsageMap read(DatabaseImpl database, int pageNum,
                               int rowNum, boolean assumeOutOfRangeBitsOn)
     throws IOException
   {
index 0a4b25535d2fdc5ad9fc4dd5d82a375535c81a56..21b7b5d3463c17c7da2363042ef9c1752e226ac8 100644 (file)
@@ -31,7 +31,7 @@ import java.util.Map;
 import com.healthmarketscience.jackcess.Column;
 import com.healthmarketscience.jackcess.CursorBuilder;
 import com.healthmarketscience.jackcess.DataType;
-import com.healthmarketscience.jackcess.Database;
+import com.healthmarketscience.jackcess.DatabaseImpl;
 import com.healthmarketscience.jackcess.IndexCursor;
 import com.healthmarketscience.jackcess.JetFormat;
 import com.healthmarketscience.jackcess.PageChannel;
@@ -108,7 +108,7 @@ public abstract class ComplexColumnInfo<V extends ComplexValue>
     int complexTypeId = buffer.getInt(
         offset + column.getFormat().OFFSET_COLUMN_COMPLEX_ID);
 
-    Database db = column.getDatabase();
+    DatabaseImpl db = column.getDatabase();
     Table complexColumns = db.getSystemComplexColumns();
     IndexCursor cursor = IndexCursor.createCursor(
         complexColumns, complexColumns.getPrimaryKeyIndex());
@@ -162,7 +162,7 @@ public abstract class ComplexColumnInfo<V extends ComplexValue>
     return _column;
   }
 
-  public Database getDatabase() {
+  public DatabaseImpl getDatabase() {
     return getColumn().getDatabase();
   }